diff options
375 files changed, 9181 insertions, 5440 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index b5e22f687a64..a60ced5835ea 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -107,6 +107,7 @@ aconfig_declarations_group { "com.android.server.flags.services-aconfig-java", "com.android.text.flags-aconfig-java", "com.android.window.flags.window-aconfig-java", + "configinfra_framework_flags_java_lib", "conscrypt_exported_aconfig_flags_lib", "device_policy_aconfig_flags_lib", "display_flags_lib", @@ -522,7 +523,10 @@ aconfig_declarations { package: "android.companion.virtualdevice.flags", container: "system", exportable: true, - srcs: ["core/java/android/companion/virtual/flags/*.aconfig"], + srcs: [ + "core/java/android/companion/virtual/flags/flags.aconfig", + "core/java/android/companion/virtual/flags/launched_flags.aconfig", + ], } java_aconfig_library { @@ -547,7 +551,7 @@ aconfig_declarations { name: "android.companion.virtual.flags-aconfig", package: "android.companion.virtual.flags", container: "system", - srcs: ["core/java/android/companion/virtual/*.aconfig"], + srcs: ["core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig"], } // InputMethod @@ -1584,6 +1588,13 @@ java_aconfig_library { } java_aconfig_library { + name: "android.app.appfunctions.flags-aconfig-java-host", + aconfig_declarations: "android.app.appfunctions.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + host_supported: true, +} + +java_aconfig_library { name: "android.app.appfunctions.exported-flags-aconfig-java", aconfig_declarations: "android.app.appfunctions.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], @@ -1824,12 +1835,6 @@ java_aconfig_library { min_sdk_version: "30", apex_available: [ "//apex_available:platform", - "com.android.adservices", - "com.android.cellbroadcast", - "com.android.devicelock", - "com.android.extservices", - "com.android.healthfitness", - "com.android.mediaprovider", "com.android.permission", ], } diff --git a/Android.bp b/Android.bp index a1f6e3079804..9d8c8a69fc18 100644 --- a/Android.bp +++ b/Android.bp @@ -408,7 +408,6 @@ java_defaults { "bouncycastle-repackaged-unbundled", "com.android.sysprop.foldlockbehavior", "com.android.sysprop.view", - "configinfra_framework_flags_java_lib", "framework-internal-utils", "dynamic_instrumentation_manager_aidl-java", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed @@ -537,45 +536,6 @@ java_library { }), } -// This is identical to "framework-minus-apex" but with "jarjar_shards" hardcodd. -// (also "stem" is commented out to avoid a conflict with the "framework-minus-apex") -// TODO(b/383559945) This module is just for local testing / verification. It's not used -// by anything. Remove it once we roll out RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX. -java_library { - name: "framework-minus-apex_jarjar-sharded", - 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", - "-Xep:AndroidFrameworkUid:ERROR", - ], - }, - lint: { - baseline_filename: "lint-baseline.xml", - warning_checks: [ - "FlaggedApi", - ], - }, - jarjar_prefix: "com.android.internal.hidden_from_bootclasspath", - jarjar_shards: "10", -} - java_library { name: "framework-minus-apex-intdefs", defaults: ["framework-minus-apex-with-libs-defaults"], diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java index df6e3c836256..e790874ebc61 100644 --- a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java +++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java @@ -43,7 +43,7 @@ public class AconfigPackagePerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - @Parameterized.Parameters(name = "isPlatform={0}") + @Parameterized.Parameters(name = "isPlatform_{0}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][] {{false}, {true}}); } @@ -60,10 +60,9 @@ public class AconfigPackagePerfTest { } } - @Parameterized.Parameter(0) - // if this variable is true, then the test query flags from system/product/vendor // if this variable is false, then the test query flags from updatable partitions + @Parameterized.Parameter(0) public boolean mIsPlatform; @Test diff --git a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java index a12121fd13f7..5d39ccc882a8 100644 --- a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.om.OverlayManager; -import android.os.UserHandle; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.TestPackageInstaller; @@ -127,7 +126,7 @@ public class OverlayManagerPerfTest { private void assertSetEnabled(boolean enabled, Context context, Stream<String> packagesStream) { final var overlayPackages = packagesStream.toList(); overlayPackages.forEach( - name -> sOverlayManager.setEnabled(name, enabled, UserHandle.SYSTEM)); + name -> sOverlayManager.setEnabled(name, enabled, context.getUser())); // Wait for the overlay changes to propagate final var endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(20); @@ -174,7 +173,7 @@ public class OverlayManagerPerfTest { // Disable the overlay and remove the idmap for the next iteration of the test state.pauseTiming(); assertSetEnabled(false, sContext, packageName); - sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser()); state.resumeTiming(); } } @@ -189,7 +188,7 @@ public class OverlayManagerPerfTest { // Disable the overlay and remove the idmap for the next iteration of the test state.pauseTiming(); assertSetEnabled(false, sContext, packageName); - sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser()); state.resumeTiming(); } } diff --git a/boot/preloaded-classes b/boot/preloaded-classes index b83bd4e4d401..9926aef91ee1 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -6470,6 +6470,7 @@ android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats android.os.flagging.AconfigPackage +android.os.flagging.PlatformAconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/config/preloaded-classes b/config/preloaded-classes index e53c78f65877..bdd95f8e67ae 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6474,6 +6474,7 @@ android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats android.os.flagging.AconfigPackage +android.os.flagging.PlatformAconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/core/api/current.txt b/core/api/current.txt index 59eb31ad9a24..c4109392d6bd 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8947,18 +8947,19 @@ package android.app.assist { method public android.content.ClipData getClipData(); method public android.os.Bundle getExtras(); method public android.content.Intent getIntent(); + method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") @Nullable public android.net.Uri getSessionTransferUri(); method public String getStructuredData(); method public android.net.Uri getWebUri(); method public boolean isAppProvidedIntent(); method public boolean isAppProvidedWebUri(); method public void setClipData(android.content.ClipData); method public void setIntent(android.content.Intent); + method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public void setSessionTransferUri(@Nullable android.net.Uri); method public void setStructuredData(String); method public void setWebUri(android.net.Uri); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR; field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA"; - field @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public static final String EXTRA_SESSION_TRANSFER_WEB_URI = "android.app.assist.extra.SESSION_TRANSFER_WEB_URI"; } public class AssistStructure implements android.os.Parcelable { @@ -9190,8 +9191,9 @@ package android.app.blob { package android.app.jank { @FlaggedApi("android.app.jank.detailed_app_jank_metrics_api") public final class AppJankStats { - ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram); + ctor public AppJankStats(int, @NonNull String, @Nullable String, @Nullable String, @Nullable String, long, long, @NonNull android.app.jank.RelativeFrameTimeHistogram); method public long getJankyFrameCount(); + method @Nullable public String getNavigationComponent(); method @NonNull public android.app.jank.RelativeFrameTimeHistogram getRelativeFrameTimeHistogram(); method public long getTotalFrameCount(); method public int getUid(); @@ -33657,16 +33659,23 @@ package android.os { } @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams { - ctor public CpuHeadroomParams(); method public int getCalculationType(); - method @IntRange(from=0x32, to=0x2710) public long getCalculationWindowMillis(); - method public void setCalculationType(int); - method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int); - method public void setTids(@NonNull int...); + method public long getCalculationWindowMillis(); + method @NonNull public int[] getTids(); + method @NonNull public android.os.CpuHeadroomParams.Builder toBuilder(); field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 } + public static final class CpuHeadroomParams.Builder { + ctor public CpuHeadroomParams.Builder(); + ctor public CpuHeadroomParams.Builder(@NonNull android.os.CpuHeadroomParams); + method @NonNull public android.os.CpuHeadroomParams build(); + method @NonNull public android.os.CpuHeadroomParams.Builder setCalculationType(int); + method @NonNull public android.os.CpuHeadroomParams.Builder setCalculationWindowMillis(@IntRange(from=1) int); + method @NonNull public android.os.CpuHeadroomParams.Builder setTids(@NonNull int...); + } + public final class CpuUsageInfo implements android.os.Parcelable { method public int describeContents(); method public long getActive(); @@ -33915,13 +33924,20 @@ package android.os { } @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams { - ctor public GpuHeadroomParams(); method public int getCalculationType(); - method @IntRange(from=0x32, to=0x2710) public int getCalculationWindowMillis(); - method public void setCalculationType(int); - method public void setCalculationWindowMillis(@IntRange(from=0x32, to=0x2710) int); + method public int getCalculationWindowMillis(); field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1 field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0 + field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710 + field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32 + } + + public static final class GpuHeadroomParams.Builder { + ctor public GpuHeadroomParams.Builder(); + ctor public GpuHeadroomParams.Builder(@NonNull android.os.GpuHeadroomParams); + method @NonNull public android.os.GpuHeadroomParams build(); + method @NonNull public android.os.GpuHeadroomParams.Builder setCalculationType(int); + method @NonNull public android.os.GpuHeadroomParams.Builder setCalculationWindowMillis(@IntRange(from=1) int); } public class Handler { @@ -35187,9 +35203,12 @@ package android.os.health { public class SystemHealthManager { method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getCpuHeadroom(@Nullable android.os.CpuHeadroomParams); + method @FlaggedApi("android.os.cpu_gpu_headrooms") @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getCpuHeadroomCalculationWindowRange(); method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getCpuHeadroomMinIntervalMillis(); method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getGpuHeadroom(@Nullable android.os.GpuHeadroomParams); + method @FlaggedApi("android.os.cpu_gpu_headrooms") @NonNull public android.util.Pair<java.lang.Integer,java.lang.Integer> getGpuHeadroomCalculationWindowRange(); method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getGpuHeadroomMinIntervalMillis(); + method @FlaggedApi("android.os.cpu_gpu_headrooms") @IntRange(from=1) public int getMaxCpuHeadroomTidsSize(); method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PowerMonitorReadings,java.lang.RuntimeException>); method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>); method public android.os.health.HealthStats takeMyUidSnapshot(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f82aecbd6d44..a775c2bc1891 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1290,7 +1290,6 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); - method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int); method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point); method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean); @@ -8095,16 +8094,16 @@ package android.media.soundtrigger { method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID); method public int getDetectionServiceOperationsTimeout(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int getModelState(@NonNull java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public boolean isRecognitionActive(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int stopRecognition(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int unloadSoundModel(@NonNull java.util.UUID); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index ed8042d45243..a988acf1f4a9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2,7 +2,7 @@ package android { public static final class Manifest.permission { - field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"; + field public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -536,6 +536,7 @@ package android.app { method @Nullable public android.graphics.Bitmap getBitmap(); method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int); method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean); + method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int); method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>); method public boolean isLockscreenLiveWallpaperEnabled(); method @Nullable public android.graphics.Rect peekBitmapDimensions(); @@ -2113,6 +2114,11 @@ package android.media { method public boolean isAidlHal(); } + public static final class MediaMuxer.OutputFormat { + field public static final int MUXER_OUTPUT_FIRST = 0; // 0x0 + field public static final int MUXER_OUTPUT_LAST = 4; // 0x4 + } + public final class MediaRoute2Info implements android.os.Parcelable { method @NonNull public String getOriginalId(); } @@ -4532,7 +4538,6 @@ package android.window { field public final int displayId; field public final boolean isDuplicateTouchToWallpaper; field public final boolean isFocusable; - field public final boolean isPreventSplitting; field public final boolean isTouchable; field public final boolean isTrustedOverlay; field public final boolean isVisible; diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 349b4edffc32..fe23517f77ba 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1977,6 +1977,8 @@ Todo: android.window.WindowContainerTransaction#setActivityWindowingMode(android Documentation mentions 'TODO' +UnflaggedApi: android.Manifest.permission#ACCESSIBILITY_MOTION_EVENT_OBSERVING: + New API must be flagged with @FlaggedApi: field android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING UnflaggedApi: android.Manifest.permission#MANAGE_REMOTE_AUTH: New API must be flagged with @FlaggedApi: field android.Manifest.permission.MANAGE_REMOTE_AUTH UnflaggedApi: android.Manifest.permission#RESERVED_FOR_TESTING_SIGNATURE: @@ -2057,6 +2059,10 @@ UnflaggedApi: android.media.AudioManager#getFocusFadeOutDurationForTest(): New API must be flagged with @FlaggedApi: method android.media.AudioManager.getFocusFadeOutDurationForTest() UnflaggedApi: android.media.AudioManager#getFocusUnmuteDelayAfterFadeOutForTest(): New API must be flagged with @FlaggedApi: method android.media.AudioManager.getFocusUnmuteDelayAfterFadeOutForTest() +UnflaggedApi: android.media.MediaMuxer.OutputFormat#MUXER_OUTPUT_FIRST: + New API must be flagged with @FlaggedApi: field android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_FIRST +UnflaggedApi: android.media.MediaMuxer.OutputFormat#MUXER_OUTPUT_LAST: + New API must be flagged with @FlaggedApi: field android.media.MediaMuxer.OutputFormat.MUXER_OUTPUT_LAST UnflaggedApi: android.media.RingtoneSelection: New API must be flagged with @FlaggedApi: class android.media.RingtoneSelection UnflaggedApi: android.media.RingtoneSelection#DEFAULT_SELECTION_URI_STRING: diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 8614bde775ad..b198811416cd 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1273,8 +1273,8 @@ public class Activity extends ContextThemeWrapper * Requests to show the “Open in browser” education. “Open in browser” is a feature * within the app header that allows users to switch from an app to the web. The feature * is made available when an application is opened by a user clicking a link or when a - * link is provided by an application. Links can be provided by utilizing - * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or + * link is provided by an application. Links can be provided by calling + * {@link AssistContent#setSessionTransferUri} or * {@link AssistContent#setWebUri}. * * <p>This method should be utilized when an activity wants to nudge the user to switch @@ -1287,7 +1287,7 @@ public class Activity extends ContextThemeWrapper * disruptive to the user to show the education and when it is optimal to switch the user to a * browser session. Before requesting to show the education, developers should assert that they * have set a link that can be used by the "Open in browser" feature through either - * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or + * {@link AssistContent#setSessionTransferUri} or * {@link AssistContent#setWebUri} so that users are navigated to a relevant page if they choose * to switch to the browser. If a URI is not set using either method, "Open in browser" will * utilize a generic link if available which will direct users to the homepage of the site @@ -1296,7 +1296,7 @@ public class Activity extends ContextThemeWrapper * the user will not be provided with the option to switch to the browser and the education will * not be shown if requested. * - * @see android.app.assist.AssistContent#EXTRA_SESSION_TRANSFER_WEB_URI + * @see android.app.assist.AssistContent#setSessionTransferUri */ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) public final void requestOpenInBrowserEducation() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 717a2acb4b4a..1f3e6559a695 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -105,6 +105,7 @@ import android.content.pm.ServiceInfo; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.ResourceTimer; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.loader.ResourcesLoader; @@ -5253,6 +5254,7 @@ public final class ActivityThread extends ClientTransactionHandler Resources.dumpHistory(pw, ""); pw.flush(); + ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh"); if (info.finishCallback != null) { info.finishCallback.sendResult(null); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 24594ab41100..614e2aaf42e8 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -774,8 +774,9 @@ public class Notification implements Parcelable /** * Bit to be bitwise-ored into the {@link #flags} field that should be - * set by the system if this notification is a promoted ongoing notification, either via a - * user setting or allowlist. + * set by the system if this notification is a promoted ongoing notification, both because it + * {@link #hasPromotableCharacteristics()} and the user has not disabled the feature for this + * app. * * Applications cannot set this flag directly, but the posting app and * {@link android.service.notification.NotificationListenerService} can read it. diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 2e6f3e1c7f0a..57549847f05d 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -753,7 +753,7 @@ public class UiModeManager { * <p> * The mode can be one of: * <ul> - * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into + * <li><em>{@link #MODE_NIGHT_NO}</em> sets the device into * {@code notnight} mode</li> * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into * {@code night} mode</li> @@ -889,7 +889,7 @@ public class UiModeManager { * <p> * The mode can be one of: * <ul> - * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into + * <li><em>{@link #MODE_NIGHT_NO}</em> sets the device into * {@code notnight} mode</li> * <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into * {@code night} mode</li> diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 360376da618c..73ecc7199686 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1690,7 +1690,7 @@ public class WallpaperManager { * @hide */ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) - @SystemApi + @TestApi @RequiresPermission(READ_WALLPAPER_INTERNAL) @NonNull public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) { diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index af035cb630dc..75589fa47892 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -160,16 +160,6 @@ flag { } flag { - name: "fix_race_condition_in_tie_profile_lock" - namespace: "enterprise" - description: "Fix race condition in tieProfileLockIfNecessary()" - bug: "355905501" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "quiet_mode_credential_bug_fix" namespace: "enterprise" description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java index 3e3ca2488bd3..adf8c94ff8d5 100644 --- a/core/java/android/app/assist/AssistContent.java +++ b/core/java/android/app/assist/AssistContent.java @@ -1,6 +1,7 @@ package android.app.assist; import android.annotation.FlaggedApi; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.Intent; @@ -30,31 +31,6 @@ public class AssistContent implements Parcelable { public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA"; - /** - * This extra can be optionally supplied in the {@link #getExtras} bundle to provide a - * {@link Uri} which will be utilized when transitioning a user's session to another surface. - * - * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the - * "Open in browser" feature will use this URI to transition the current session from one - * surface to the other. Apps may choose to encode session or user information into this - * URI in order to provide a better session transfer experience. - * - * <p>Unlike {@link #setWebUri}, this URI will not be used for features where the user might - * accidentally share it with another user. However, developers should not encode - * authentication credentials into this URI, because it will be surfaced in the browser URL - * bar and may be copied and shared from there. - * - * <p>When providing this extra, developers should still continue to provide - * {@link #setWebUri} for backwards compatibility with features such as - * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing"> - * recents URL sharing</a> which do not benefit from a session-transfer web URI. - * - * @see android.app.Activity#requestOpenInBrowserEducation() - */ - @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) - public static final String EXTRA_SESSION_TRANSFER_WEB_URI = - "android.app.assist.extra.SESSION_TRANSFER_WEB_URI"; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean mIsAppProvidedIntent = false; private boolean mIsAppProvidedWebUri = false; @@ -66,6 +42,7 @@ public class AssistContent implements Parcelable { private ClipData mClipData; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private Uri mUri; + private Uri mSessionTransferUri; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final Bundle mExtras; @@ -200,6 +177,41 @@ public class AssistContent implements Parcelable { } /** + * This method can be used to provide a {@link Uri} which will be utilized when transitioning a + * user's session to another surface. + * + * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the + * "Open in browser" feature will use this URI to transition the current session from one + * surface to the other. Apps may choose to encode session or user information into this + * URI in order to provide a better session transfer experience. However, while this URI will + * only be available to the system and not other applications, developers should not encode + * authentication credentials into this URI, because it will be surfaced in the browser URL bar + * and may be copied and shared from there. + * + * <p>When providing this URI, developers should still continue to provide + * {@link #setWebUri} for backwards compatibility with features such as + * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing"> + * recents URL sharing</a> which facilitate link sharing with other users and would not benefit + * from a session-transfer URI. + * + * @see android.app.Activity#requestOpenInBrowserEducation() + */ + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) + public void setSessionTransferUri(@Nullable Uri uri) { + mSessionTransferUri = uri; + } + + /** + * Return the content's session transfer web URI as per + * {@link #setSessionTransferUri(android.net.Uri)}, or null if there is none. + */ + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) + @Nullable + public Uri getSessionTransferUri() { + return mSessionTransferUri; + } + + /** * Return Bundle for extra vendor-specific data that can be modified and examined. */ public Bundle getExtras() { @@ -218,6 +230,9 @@ public class AssistContent implements Parcelable { mUri = Uri.CREATOR.createFromParcel(in); } if (in.readInt() != 0) { + mSessionTransferUri = Uri.CREATOR.createFromParcel(in); + } + if (in.readInt() != 0) { mStructuredData = in.readString(); } mIsAppProvidedIntent = in.readInt() == 1; @@ -245,6 +260,12 @@ public class AssistContent implements Parcelable { } else { dest.writeInt(0); } + if (mSessionTransferUri != null) { + dest.writeInt(1); + mSessionTransferUri.writeToParcel(dest, flags); + } else { + dest.writeInt(0); + } if (mStructuredData != null) { dest.writeInt(1); dest.writeString(mStructuredData); diff --git a/core/java/android/app/jank/AppJankStats.java b/core/java/android/app/jank/AppJankStats.java index 6ef6a44ddfbb..a8ebc383b7b5 100644 --- a/core/java/android/app/jank/AppJankStats.java +++ b/core/java/android/app/jank/AppJankStats.java @@ -57,6 +57,8 @@ public final class AppJankStats { // Histogram of relative frame times encoded in predetermined buckets. private RelativeFrameTimeHistogram mRelativeFrameTimeHistogram; + // Navigation component associated to this stat. + private String mNavigationComponent; /** Used to indicate no widget category has been set. */ public static final String WIDGET_CATEGORY_UNSPECIFIED = "unspecified"; @@ -158,6 +160,8 @@ public final class AppJankStats { * * @param appUid the Uid of the App that is collecting jank stats. * @param widgetId the widget id that frames will be associated to. + * @param navigationComponent the intended navigation target within the activity, this could be + * a navigation destination, screen and/or pane. * @param widgetCategory a category used to organize widgets in a structured way that indicates * they serve a similar purpose or perform related functions. Must be * prefixed with WIDGET_CATEGORY_ and have a suffix of one of the @@ -172,14 +176,14 @@ public final class AppJankStats { * @param jankyFrames the total number of janky frames that were counted for this stat. * @param relativeFrameTimeHistogram the histogram with predefined buckets. See * {@link #getRelativeFrameTimeHistogram()} for details. - * */ - public AppJankStats(int appUid, @NonNull String widgetId, + public AppJankStats(int appUid, @NonNull String widgetId, @Nullable String navigationComponent, @Nullable @WidgetCategory String widgetCategory, @Nullable @WidgetState String widgetState, long totalFrames, long jankyFrames, @NonNull RelativeFrameTimeHistogram relativeFrameTimeHistogram) { mUid = appUid; mWidgetId = widgetId; + mNavigationComponent = navigationComponent; mWidgetCategory = widgetCategory != null ? widgetCategory : WIDGET_CATEGORY_UNSPECIFIED; mWidgetState = widgetState != null ? widgetState : WIDGET_STATE_UNSPECIFIED; mTotalFrames = totalFrames; @@ -254,4 +258,13 @@ public final class AppJankStats { public @NonNull RelativeFrameTimeHistogram getRelativeFrameTimeHistogram() { return mRelativeFrameTimeHistogram; } + + /** + * Returns the navigation component if it exists that this stat applies to. + * + * @return the navigation component if it exists that this stat applies to. + */ + public @Nullable String getNavigationComponent() { + return mNavigationComponent; + } } diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig index 46da4a3d99bc..eae50624539e 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig @@ -1,24 +1,18 @@ -# Do not add new flags to this file. +# Do not modify this file. # -# Due to "virtual" keyword in the package name flags -# added to this file cannot be accessed from C++ -# code. +# Due to "virtual" keyword in the package name flags added to this file cannot +# be accessed from C++ code. # # Use frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig -# instead. +# instead for new flags. +# +# All of the remaining flags here have been used for API flagging and are +# therefore exported and should not be deleted. package: "android.companion.virtual.flags" container: "system" flag { - name: "enable_native_vdm" - namespace: "virtual_devices" - description: "Enable native VDM service" - bug: "303535376" - is_fixed_read_only: true -} - -flag { name: "dynamic_policy" is_exported: true namespace: "virtual_devices" diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index de01280f293f..84af84072f1b 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -1,17 +1,11 @@ +# VirtualDeviceManager flags # -# Copyright (C) 2023 The Android Open Source Project +# This file contains flags guarding features that are in development. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Once a flag is launched or abandoned and there are no more references to it in +# the codebase, it should be either: +# - deleted, or +# - moved to launched_flags.aconfig if it was launched and used for API flagging. package: "android.companion.virtualdevice.flags" container: "system" diff --git a/core/java/android/companion/virtual/flags/launched_flags.aconfig b/core/java/android/companion/virtual/flags/launched_flags.aconfig new file mode 100644 index 000000000000..ee896319bb72 --- /dev/null +++ b/core/java/android/companion/virtual/flags/launched_flags.aconfig @@ -0,0 +1,6 @@ +# This file contains the launched VirtualDeviceManager flags from the +# "android.companion.virtualdevice.flags" package that cannot be deleted because +# they have been used for API flagging. + +package: "android.companion.virtualdevice.flags" +container: "system" diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 0333942b7f3e..9d11710a2cad 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -17,6 +17,7 @@ package android.content.pm; import android.Manifest; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -30,6 +31,7 @@ import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; +import android.util.ArrayMap; import android.util.AtomicFile; import android.util.AttributeSet; import android.util.IntArray; @@ -45,11 +47,11 @@ import com.android.internal.util.ArrayUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; -import libcore.io.IoUtils; - import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -94,6 +96,9 @@ public abstract class RegisteredServicesCache<V> { @GuardedBy("mServicesLock") private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2); + @GuardedBy("mServicesLock") + private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>(); + private static class UserServices<V> { @GuardedBy("mServicesLock") final Map<V, Integer> persistentServices = Maps.newHashMap(); @@ -323,13 +328,16 @@ public abstract class RegisteredServicesCache<V> { public final ComponentName componentName; @UnsupportedAppUsage public final int uid; + public final long lastUpdateTime; /** @hide */ - public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) { + public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName, + long lastUpdateTime) { this.type = type; this.componentInfo = componentInfo; this.componentName = componentName; this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1; + this.lastUpdateTime = lastUpdateTime; } @Override @@ -490,7 +498,7 @@ public abstract class RegisteredServicesCache<V> { final List<ResolveInfo> resolveInfos = queryIntentServices(userId); for (ResolveInfo resolveInfo : resolveInfos) { try { - ServiceInfo<V> info = parseServiceInfo(resolveInfo); + ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId); if (info == null) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString()); continue; @@ -638,13 +646,31 @@ public abstract class RegisteredServicesCache<V> { } @VisibleForTesting - protected ServiceInfo<V> parseServiceInfo(ResolveInfo service) + protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId) throws XmlPullParserException, IOException { android.content.pm.ServiceInfo si = service.serviceInfo; ComponentName componentName = new ComponentName(si.packageName, si.name); PackageManager pm = mContext.getPackageManager(); + // Check if the service has been in the service cache. + long lastUpdateTime = -1; + if (Flags.optimizeParsingInRegisteredServicesCache()) { + try { + PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId); + lastUpdateTime = packageInfo.lastUpdateTime; + + ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime); + if (serviceInfo != null) { + return serviceInfo; + } + } catch (NameNotFoundException | SecurityException e) { + Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e); + } + } + XmlResourceParser parser = null; try { parser = si.loadXmlMetaData(pm, mMetaDataName); @@ -670,8 +696,13 @@ public abstract class RegisteredServicesCache<V> { if (v == null) { return null; } - final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo; - return new ServiceInfo<V>(v, serviceInfo, componentName); + ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime); + if (Flags.optimizeParsingInRegisteredServicesCache()) { + synchronized (mServicesLock) { + mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo); + } + } + return serviceInfo; } catch (NameNotFoundException e) { throw new XmlPullParserException( "Unable to load resources for pacakge " + si.packageName); @@ -841,4 +872,28 @@ public abstract class RegisteredServicesCache<V> { mContext.unregisterReceiver(mExternalReceiver); mContext.unregisterReceiver(mUserRemovedReceiver); } + + private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) { + StringBuilder sb = new StringBuilder(serviceInfo.packageName); + sb.append('-'); + sb.append(serviceInfo.name); + return sb.toString(); + } + + private ServiceInfo<V> getServiceInfoFromServiceCache( + @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) { + String serviceCacheKey = getServiceCacheKey(serviceInfo); + synchronized (mServicesLock) { + ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey); + if (serviceCache == null) { + return null; + } + if (serviceCache.lastUpdateTime == lastUpdateTime) { + return serviceCache; + } + // The service is not latest, remove it from the cache. + mServiceInfoCaches.remove(serviceCacheKey); + return null; + } + } } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 7bba06c87813..e4b8c90d381d 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -383,3 +383,11 @@ flag { bug: "334024639" description: "Feature flag to check whether a given UID can access a content provider" } + +flag { + name: "optimize_parsing_in_registered_services_cache" + namespace: "package_manager_service" + description: "Feature flag to optimize RegisteredServicesCache ServiceInfo parsing by using caches." + bug: "319137634" + is_fixed_read_only: true +} diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 908999b64961..b938aac811fd 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -25,6 +25,7 @@ import android.content.res.loader.ResourcesProvider; import android.ravenwood.annotation.RavenwoodClassLoadHook; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -50,6 +51,7 @@ import java.util.Objects; @RavenwoodKeepWholeClass @RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public final class ApkAssets { + private static final boolean DEBUG = false; /** * The apk assets contains framework resource values specified by the system. @@ -134,6 +136,17 @@ public final class ApkAssets { @Nullable private final AssetsProvider mAssets; + @NonNull + private String mName; + + private static final int UPTODATE_FALSE = 0; + private static final int UPTODATE_TRUE = 1; + private static final int UPTODATE_ALWAYS_TRUE = 2; + + // Start with the only value that may change later and would force a native call to + // double check it. + private int mPreviousUpToDateResult = UPTODATE_TRUE; + /** * Creates a new ApkAssets instance from the given path on disk. * @@ -304,7 +317,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, path); Objects.requireNonNull(path, "path"); mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); @@ -313,7 +326,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, friendlyName); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); @@ -323,7 +336,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, friendlyName); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); @@ -331,16 +344,17 @@ public final class ApkAssets { } private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { - this(FORMAT_APK, flags, assets); + this(FORMAT_APK, flags, assets, "empty"); mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; } private ApkAssets(@FormatType int format, @PropertyFlags int flags, - @Nullable AssetsProvider assets) { + @Nullable AssetsProvider assets, @NonNull String name) { mFlags = flags; mAssets = assets; mIsOverlay = format == FORMAT_IDMAP; + if (DEBUG) mName = name; } @UnsupportedAppUsage @@ -421,13 +435,41 @@ public final class ApkAssets { } } + private static double intervalMs(long beginNs, long endNs) { + return (endNs - beginNs) / 1000000.0; + } + /** * Returns false if the underlying APK was changed since this ApkAssets was loaded. */ public boolean isUpToDate() { + // This function is performance-critical - it's called multiple times on every Resources + // object creation, and on few other cache accesses - so it's important to avoid the native + // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE + // and FALSE). + if (mPreviousUpToDateResult != UPTODATE_TRUE) { + return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE; + } + final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs; + if (DEBUG) beforeTs = System.nanoTime(); + final int res; synchronized (this) { - return nativeIsUpToDate(mNativePtr); + if (DEBUG) afterLockTs = System.nanoTime(); + res = nativeIsUpToDate(mNativePtr); + if (DEBUG) afterNativeTs = System.nanoTime(); + } + if (DEBUG) { + afterUnlockTs = System.nanoTime(); + if (afterUnlockTs - beforeTs >= 10L * 1000000) { + Log.d("ApkAssets", "isUpToDate(" + mName + ") took " + + intervalMs(beforeTs, afterUnlockTs) + + " ms: " + intervalMs(beforeTs, afterLockTs) + + " / " + intervalMs(afterLockTs, afterNativeTs) + + " / " + intervalMs(afterNativeTs, afterUnlockTs)); + } } + mPreviousUpToDateResult = res; + return res != UPTODATE_FALSE; } public boolean isSystem() { @@ -487,7 +529,7 @@ public final class ApkAssets { private static native @NonNull String nativeGetAssetPath(long ptr); private static native @NonNull String nativeGetDebugName(long ptr); private static native long nativeGetStringBlock(long ptr); - @CriticalNative private static native boolean nativeIsUpToDate(long ptr); + @CriticalNative private static native int nativeIsUpToDate(long ptr); private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, String overlayableName) throws IOException; diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java index d51f64ce8106..2d1bf4d9d296 100644 --- a/core/java/android/content/res/ResourceTimer.java +++ b/core/java/android/content/res/ResourceTimer.java @@ -17,13 +17,10 @@ package android.content.res; import android.annotation.NonNull; -import android.annotation.Nullable; - import android.app.AppProtoEnums; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemClock; import android.text.TextUtils; @@ -33,6 +30,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.Arrays; @@ -277,38 +275,40 @@ public final class ResourceTimer { * Update the metrics information and dump it. * @hide */ - public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) { - FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); - PrintWriter pw = new FastPrintWriter(fout); - synchronized (sLock) { - if (!sEnabled || (sConfig == null)) { + public static void dumpTimers(@NonNull FileDescriptor fd, String... args) { + try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) { + pw.println("\nDumping ResourceTimers"); + + final boolean enabled; + synchronized (sLock) { + enabled = sEnabled && sConfig != null; + } + if (!enabled) { pw.println(" Timers are not enabled in this process"); - pw.flush(); return; } - } - // Look for the --refresh switch. If the switch is present, then sTimers is updated. - // Otherwise, the current value of sTimers is displayed. - boolean refresh = Arrays.asList(args).contains("-refresh"); - - synchronized (sLock) { - update(refresh); - long runtime = sLastUpdated - sProcessStart; - pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName()); - for (int i = 0; i < sTimers.length; i++) { - Timer t = sTimers[i]; - if (t.count != 0) { - String name = sConfig.timers[i]; - pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s " - + "largest=%s\n", - name, t.count, t.total / t.count, t.mintime, t.maxtime, - packedString(t.percentile), - packedString(t.largest)); + // Look for the --refresh switch. If the switch is present, then sTimers is updated. + // Otherwise, the current value of sTimers is displayed. + boolean refresh = Arrays.asList(args).contains("-refresh"); + + synchronized (sLock) { + update(refresh); + long runtime = sLastUpdated - sProcessStart; + pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName()); + for (int i = 0; i < sTimers.length; i++) { + Timer t = sTimers[i]; + if (t.count != 0) { + String name = sConfig.timers[i]; + pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s " + + "largest=%s\n", + name, t.count, t.total / t.count, t.mintime, t.maxtime, + packedString(t.percentile), + packedString(t.largest)); + } } } } - pw.flush(); } // Enable (or disabled) the runtime timers. Note that timers are disabled by default. This diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 2d3d25217357..868429c30631 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -16,8 +16,6 @@ package android.hardware; -import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED; -import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; import static android.content.Context.DEVICE_ID_DEFAULT; @@ -164,11 +162,7 @@ public class SystemSensorManager extends SensorManager { // initialize the sensor list for (int index = 0;; ++index) { Sensor sensor = new Sensor(); - if (android.companion.virtual.flags.Flags.enableNativeVdm()) { - if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break; - } else { - if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break; - } + if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break; mFullSensorsList.add(sensor); mHandleToSensor.put(sensor.getHandle(), sensor); } @@ -555,11 +549,7 @@ public class SystemSensorManager extends SensorManager { } private List<Sensor> createRuntimeSensorListLocked(int deviceId) { - if (android.companion.virtual.flags.Flags.vdmPublicApis()) { - setupVirtualDeviceListener(); - } else { - setupRuntimeSensorBroadcastReceiver(); - } + setupVirtualDeviceListener(); List<Sensor> list = new ArrayList<>(); nativeGetRuntimeSensors(mNativeInstance, deviceId, list); mFullRuntimeSensorListByDevice.put(deviceId, list); @@ -570,35 +560,6 @@ public class SystemSensorManager extends SensorManager { return list; } - private void setupRuntimeSensorBroadcastReceiver() { - if (mRuntimeSensorBroadcastReceiver == null) { - mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) { - synchronized (mFullRuntimeSensorListByDevice) { - final int deviceId = intent.getIntExtra( - EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT); - List<Sensor> removedSensors = - mFullRuntimeSensorListByDevice.removeReturnOld(deviceId); - if (removedSensors != null) { - for (Sensor s : removedSensors) { - cleanupSensorConnection(s); - } - } - mRuntimeSensorListByDeviceByType.remove(deviceId); - } - } - } - }; - - IntentFilter filter = new IntentFilter("virtual_device_removed"); - filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED); - mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter, - Context.RECEIVER_NOT_EXPORTED); - } - } - private void setupVirtualDeviceListener() { if (mVirtualDeviceListener != null) { return; diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 0e2c05f92e7c..1d2f133ee759 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -679,8 +679,7 @@ public final class DisplayTopology implements Parcelable { } /** - * Tests whether two brightness float values are within a small enough tolerance - * of each other. + * Tests whether two float values are within a small enough tolerance of each other. * @param a first float to compare * @param b second float to compare * @return whether the two values are within a small enough tolerance value diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 8da630c95135..b380e259577c 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -78,6 +78,24 @@ public class InputSettings { public static final int DEFAULT_POINTER_SPEED = 0; /** + * Pointer Speed: The minimum (slowest) mouse scrolling speed (-7). + * @hide + */ + public static final int MIN_MOUSE_SCROLLING_SPEED = -7; + + /** + * Pointer Speed: The maximum (fastest) mouse scrolling speed (7). + * @hide + */ + public static final int MAX_MOUSE_SCROLLING_SPEED = 7; + + /** + * Pointer Speed: The default mouse scrolling speed (0). + * @hide + */ + public static final int DEFAULT_MOUSE_SCROLLING_SPEED = 0; + + /** * Bounce Keys Threshold: The default value of the threshold (500 ms). * * @hide @@ -650,6 +668,54 @@ public class InputSettings { } /** + * Gets the mouse scrolling speed. + * + * The returned value only applies when mouse scrolling acceleration is not enabled. + * + * @param context The application context. + * @return The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} and + * {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value + * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}. + * + * @hide + */ + public static int getMouseScrollingSpeed(@NonNull Context context) { + if (!isMouseScrollingAccelerationFeatureFlagEnabled()) { + return 0; + } + + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SCROLLING_SPEED, DEFAULT_MOUSE_SCROLLING_SPEED, + UserHandle.USER_CURRENT); + } + + /** + * Sets the mouse scrolling speed, and saves it in the settings. + * + * The new speed will only apply when mouse scrolling acceleration is not enabled. + * + * @param context The application context. + * @param speed The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} + * and {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value + * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}. + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setMouseScrollingSpeed(@NonNull Context context, int speed) { + if (isMouseScrollingAccelerationEnabled(context)) { + return; + } + + if (speed < MIN_MOUSE_SCROLLING_SPEED || speed > MAX_MOUSE_SCROLLING_SPEED) { + throw new IllegalArgumentException("speed out of range"); + } + + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SCROLLING_SPEED, speed, UserHandle.USER_CURRENT); + } + + /** * Whether mouse vertical scrolling is reversed. This applies only to connected mice. * * @param context The application context. diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 0cd320981c93..9181bd0cb2ed 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -697,6 +697,7 @@ public final class ContextHubManager { * * @param endpointId Statically generated ID for an endpoint. * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. + * @throws UnsupportedOperationException If the operation is not supported. */ @FlaggedApi(Flags.FLAG_OFFLOAD_API) @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @@ -733,6 +734,7 @@ public final class ContextHubManager { * cannot be null or empty. * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery. * @throws IllegalArgumentException if the serviceDescriptor is empty/null. + * @throws UnsupportedOperationException If the operation is not supported. */ @FlaggedApi(Flags.FLAG_OFFLOAD_API) @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java index 072c012d6db9..e51197607520 100644 --- a/core/java/android/os/CpuHeadroomParams.java +++ b/core/java/android/os/CpuHeadroomParams.java @@ -28,15 +28,11 @@ import java.util.Arrays; /** * Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}. + * + * <p>This class is immutable and one should use the {@link Builder} to build a new instance. */ @FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS) public final class CpuHeadroomParams { - final CpuHeadroomParamsInternal mInternal; - - public CpuHeadroomParams() { - mInternal = new CpuHeadroomParamsInternal(); - } - /** @hide */ @IntDef(flag = false, prefix = {"CPU_HEADROOM_CALCULATION_TYPE_"}, value = { CPU_HEADROOM_CALCULATION_TYPE_MIN, // 0 @@ -47,107 +43,188 @@ public final class CpuHeadroomParams { } /** - * Calculates the headroom based on minimum value over a device-defined window. + * The headroom calculation type bases on minimum value over a specified window. */ public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; /** - * Calculates the headroom based on average value over a device-defined window. + * The headroom calculation type bases on average value over a specified window. */ public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; - private static final int CALCULATION_WINDOW_MILLIS_MIN = 50; - private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000; - private static final int MAX_TID_COUNT = 5; + /** @hide */ + public final CpuHeadroomParamsInternal mInternal; + + private CpuHeadroomParams() { + mInternal = new CpuHeadroomParamsInternal(); + } + + public static final class Builder { + private int mCalculationType = -1; + private int mCalculationWindowMillis = -1; + private int[] mTids = null; + + public Builder() { + } + + /** + * Returns a new builder copy with the same values as the params. + */ + public Builder(@NonNull CpuHeadroomParams params) { + if (params.mInternal.calculationType >= 0) { + mCalculationType = params.mInternal.calculationType; + } + if (params.mInternal.calculationWindowMillis >= 0) { + mCalculationWindowMillis = params.mInternal.calculationWindowMillis; + } + if (params.mInternal.tids != null) { + mTids = Arrays.copyOf(params.mInternal.tids, params.mInternal.tids.length); + } + } + + /** + * Sets the headroom calculation type. + * <p> + * + * @throws IllegalArgumentException if the type is invalid. + */ + @NonNull + public Builder setCalculationType( + @CpuHeadroomCalculationType int calculationType) { + switch (calculationType) { + case CPU_HEADROOM_CALCULATION_TYPE_MIN: + case CPU_HEADROOM_CALCULATION_TYPE_AVERAGE: { + mCalculationType = calculationType; + return this; + } + } + throw new IllegalArgumentException("Invalid calculation type: " + calculationType); + } + + /** + * Sets the headroom calculation window size in milliseconds. + * <p> + * + * @param windowMillis the window size in milliseconds ranges from + * {@link SystemHealthManager#getCpuHeadroomCalculationWindowRange()}. + * The smaller the window size, the larger fluctuation in the headroom + * value should be expected. The default value can be retrieved from + * the {@link CpuHeadroomParams#getCalculationWindowMillis}. The device + * will try to use the closest feasible window size to this param. + * @throws IllegalArgumentException if the window is invalid. + */ + @NonNull + public Builder setCalculationWindowMillis(@IntRange(from = 1) int windowMillis) { + if (windowMillis <= 0) { + throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); + } + mCalculationWindowMillis = windowMillis; + return this; + } + + /** + * Sets the thread TIDs to track. + * <p> + * The TIDs should belong to the same of the process that will make the headroom call. And + * they should not have different core affinity. + * <p> + * If not set or set to empty, the headroom will be based on the PID of the process making + * the call. + * + * @param tids non-null list of TIDs, where maximum size can be read from + * {@link SystemHealthManager#getMaxCpuHeadroomTidsSize()}. + * @throws IllegalArgumentException if the TID is not positive. + */ + @NonNull + public Builder setTids(@NonNull int... tids) { + for (int tid : tids) { + if (tid <= 0) { + throw new IllegalArgumentException("Invalid TID: " + tid); + } + } + mTids = tids; + return this; + } + + /** + * Builds the {@link CpuHeadroomParams} object. + */ + @NonNull + public CpuHeadroomParams build() { + CpuHeadroomParams params = new CpuHeadroomParams(); + if (mCalculationType >= 0) { + params.mInternal.calculationType = (byte) mCalculationType; + } + if (mCalculationWindowMillis >= 0) { + params.mInternal.calculationWindowMillis = mCalculationWindowMillis; + } + if (mTids != null) { + params.mInternal.tids = mTids; + } + return params; + } + } /** - * Sets the headroom calculation type. - * <p> - * - * @throws IllegalArgumentException if the type is invalid. + * Returns a new builder with the same values as this object. */ - public void setCalculationType(@CpuHeadroomCalculationType int calculationType) { - switch (calculationType) { - case CPU_HEADROOM_CALCULATION_TYPE_MIN: - case CPU_HEADROOM_CALCULATION_TYPE_AVERAGE: - mInternal.calculationType = (byte) calculationType; - return; - } - throw new IllegalArgumentException("Invalid calculation type: " + calculationType); + @NonNull + public Builder toBuilder() { + return new Builder(this); } /** * Gets the headroom calculation type. - * Default to {@link #CPU_HEADROOM_CALCULATION_TYPE_MIN} if not set. + * <p> + * This will return the default value chosen by the device if not set. */ public @CpuHeadroomCalculationType int getCalculationType() { @CpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) { - case CPU_HEADROOM_CALCULATION_TYPE_MIN, CPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> - mInternal.calculationType; + case CPU_HEADROOM_CALCULATION_TYPE_MIN, + CPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> mInternal.calculationType; default -> CPU_HEADROOM_CALCULATION_TYPE_MIN; }; return validatedType; } /** - * Sets the headroom calculation window size in milliseconds. - * <p> - * - * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the - * window size, the larger fluctuation in the headroom value should be - * expected. The default value can be retrieved from the - * {@link #getCalculationWindowMillis}. The device will try to use the - * closest feasible window size to this param. - * @throws IllegalArgumentException if the window size is not in allowed range. - */ - public void setCalculationWindowMillis( - @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = - CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { - if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN - || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) { - throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); - } - mInternal.calculationWindowMillis = windowMillis; - } - - /** * Gets the headroom calculation window size in milliseconds. * <p> - * This will return the default value chosen by the device if the params is not set. + * This will return the default value chosen by the device if not set. */ - public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = - CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() { + public long getCalculationWindowMillis() { return mInternal.calculationWindowMillis; } /** - * Sets the thread TIDs to track. - * <p> - * The TIDs should belong to the same of the process that will the headroom call. And they - * should not have different core affinity. + * Gets the TIDs to track. * <p> - * If not set, the headroom will be based on the PID of the process making the call. - * - * @param tids non-empty list of TIDs, maximum 5. - * @throws IllegalArgumentException if the list size is not in allowed range or TID is not - * positive. + * This will return a copy of the TIDs in the params, or null if the params is not set. */ - public void setTids(@NonNull int... tids) { - if (tids.length == 0 || tids.length > MAX_TID_COUNT) { - throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length); - } - for (int tid : tids) { - if (tid <= 0) { - throw new IllegalArgumentException("Invalid TID: " + tid); - } - } - mInternal.tids = Arrays.copyOf(tids, tids.length); + @NonNull + public int[] getTids() { + return mInternal.tids == null ? null : Arrays.copyOf(mInternal.tids, mInternal.tids.length); } - /** - * @hide - */ - public CpuHeadroomParamsInternal getInternal() { - return mInternal; + @Override + public String toString() { + return "CpuHeadroomParams{" + + "calculationType=" + mInternal.calculationType + + ", calculationWindowMillis=" + mInternal.calculationWindowMillis + + ", tids=" + Arrays.toString(mInternal.tids) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CpuHeadroomParams that = (CpuHeadroomParams) o; + return mInternal.equals(that.mInternal); + } + + @Override + public int hashCode() { + return mInternal.hashCode(); } } diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java index 126ee8cce3d0..5c5e7bb5ccaf 100644 --- a/core/java/android/os/GpuHeadroomParams.java +++ b/core/java/android/os/GpuHeadroomParams.java @@ -19,6 +19,7 @@ package android.os; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; import android.os.health.SystemHealthManager; import java.lang.annotation.Retention; @@ -26,15 +27,11 @@ import java.lang.annotation.RetentionPolicy; /** * Headroom request params used by {@link SystemHealthManager#getGpuHeadroom(GpuHeadroomParams)}. + * + * <p>This class is immutable and one should use the {@link Builder} to build a new instance. */ @FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS) public final class GpuHeadroomParams { - final GpuHeadroomParamsInternal mInternal; - - public GpuHeadroomParams() { - mInternal = new GpuHeadroomParamsInternal(); - } - /** @hide */ @IntDef(flag = false, prefix = {"GPU_HEADROOM_CALCULATION_TYPE_"}, value = { GPU_HEADROOM_CALCULATION_TYPE_MIN, // 0 @@ -45,82 +42,153 @@ public final class GpuHeadroomParams { } /** - * Calculates the headroom based on minimum value over a device-defined window. + * The headroom calculation type bases on minimum value over a specified window. */ public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; /** - * Calculates the headroom based on average value over a device-defined window. + * The headroom calculation type bases on average value over a specified window. */ public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; - private static final int CALCULATION_WINDOW_MILLIS_MIN = 50; - private static final int CALCULATION_WINDOW_MILLIS_MAX = 10000; + /** + * The minimum size of the window to compute the headroom over. + */ + public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; /** - * Sets the headroom calculation type. - * <p> - * - * @throws IllegalArgumentException if the type is invalid. + * The maximum size of the window to compute the headroom over. + */ + public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; + + /** + * @hide + */ + public final GpuHeadroomParamsInternal mInternal; + + /** + * @hide */ - public void setCalculationType(@GpuHeadroomCalculationType int calculationType) { - switch (calculationType) { - case GPU_HEADROOM_CALCULATION_TYPE_MIN: - case GPU_HEADROOM_CALCULATION_TYPE_AVERAGE: - mInternal.calculationType = (byte) calculationType; - return; + private GpuHeadroomParams() { + mInternal = new GpuHeadroomParamsInternal(); + } + + public static final class Builder { + private int mCalculationType = -1; + private int mCalculationWindowMillis = -1; + + public Builder() { + } + + /** + * Returns a new builder with the same values as this object. + */ + public Builder(@NonNull GpuHeadroomParams params) { + if (params.mInternal.calculationType >= 0) { + mCalculationType = params.mInternal.calculationType; + } + if (params.mInternal.calculationWindowMillis >= 0) { + mCalculationWindowMillis = params.mInternal.calculationWindowMillis; + } + } + + /** + * Sets the headroom calculation type. + * <p> + * + * @throws IllegalArgumentException if the type is invalid. + */ + @NonNull + public Builder setCalculationType( + @GpuHeadroomCalculationType int calculationType) { + switch (calculationType) { + case GPU_HEADROOM_CALCULATION_TYPE_MIN: + case GPU_HEADROOM_CALCULATION_TYPE_AVERAGE: { + mCalculationType = calculationType; + return this; + } + } + throw new IllegalArgumentException("Invalid calculation type: " + calculationType); + } + + /** + * Sets the headroom calculation window size in milliseconds. + * <p> + * + * @param windowMillis the window size in milliseconds ranges from + * {@link SystemHealthManager#getGpuHeadroomCalculationWindowRange()}. + * The smaller the window size, the larger fluctuation in the headroom + * value should be expected. The default value can be retrieved from + * the {@link GpuHeadroomParams#getCalculationWindowMillis}. The device + * will try to use the closest feasible window size to this param. + * @throws IllegalArgumentException if the window is invalid. + */ + @NonNull + public Builder setCalculationWindowMillis(@IntRange(from = 1) int windowMillis) { + if (windowMillis <= 0) { + throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); + } + mCalculationWindowMillis = windowMillis; + return this; + } + + /** + * Builds the {@link GpuHeadroomParams} object. + */ + @NonNull + public GpuHeadroomParams build() { + GpuHeadroomParams params = new GpuHeadroomParams(); + if (mCalculationType >= 0) { + params.mInternal.calculationType = (byte) mCalculationType; + } + if (mCalculationWindowMillis >= 0) { + params.mInternal.calculationWindowMillis = mCalculationWindowMillis; + } + return params; } - throw new IllegalArgumentException("Invalid calculation type: " + calculationType); } /** * Gets the headroom calculation type. - * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if the params is not set. + * <p> + * This will return the default value chosen by the device if not set. */ public @GpuHeadroomCalculationType int getCalculationType() { @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) { - case GPU_HEADROOM_CALCULATION_TYPE_MIN, GPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> - mInternal.calculationType; + case GPU_HEADROOM_CALCULATION_TYPE_MIN, + GPU_HEADROOM_CALCULATION_TYPE_AVERAGE -> mInternal.calculationType; default -> GPU_HEADROOM_CALCULATION_TYPE_MIN; }; return validatedType; } /** - * Sets the headroom calculation window size in milliseconds. - * <p> - * - * @param windowMillis the window size in milliseconds ranges from [50, 10000]. The smaller the - * window size, the larger fluctuation in the headroom value should be - * expected. The default value can be retrieved from the - * {@link #getCalculationWindowMillis}. The device will try to use the - * closest feasible window size to this param. - * @throws IllegalArgumentException if the window is invalid. - */ - public void setCalculationWindowMillis( - @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = - CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) { - if (windowMillis < CALCULATION_WINDOW_MILLIS_MIN - || windowMillis > CALCULATION_WINDOW_MILLIS_MAX) { - throw new IllegalArgumentException("Invalid calculation window: " + windowMillis); - } - mInternal.calculationWindowMillis = windowMillis; - } - - /** * Gets the headroom calculation window size in milliseconds. * <p> * This will return the default value chosen by the device if not set. */ - public @IntRange(from = CALCULATION_WINDOW_MILLIS_MIN, to = - CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() { + public int getCalculationWindowMillis() { return mInternal.calculationWindowMillis; } - /** - * @hide - */ - public GpuHeadroomParamsInternal getInternal() { - return mInternal; + @Override + public String toString() { + return "GpuHeadroomParams{" + + "calculationType=" + mInternal.calculationType + + ", calculationWindowMillis=" + mInternal.calculationWindowMillis + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GpuHeadroomParams that = (GpuHeadroomParams) o; + return mInternal.equals(that.mInternal); + } + + @Override + public int hashCode() { + return mInternal.hashCode(); } } diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl index 5128e9173358..243254526f60 100644 --- a/core/java/android/os/IHintManager.aidl +++ b/core/java/android/os/IHintManager.aidl @@ -72,6 +72,7 @@ interface IHintManager { parcelable HintManagerClientData { int powerHalVersion; int maxGraphicsPipelineThreads; + int maxCpuHeadroomThreads; long preferredRateNanos; SupportInfo supportInfo; } @@ -88,4 +89,6 @@ interface IHintManager { * passing back a bundle of support and configuration information. */ HintManagerClientData registerClient(in IHintManagerClient client); + + HintManagerClientData getClientData(); } diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index cd79e416531a..a1e9cf25e3e1 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -18,6 +18,7 @@ package android.os.health; import android.annotation.FlaggedApi; import android.annotation.FloatRange; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; @@ -42,6 +43,8 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SynchronousResultReceiver; +import android.util.Pair; +import android.util.Slog; import com.android.internal.app.IBatteryStats; import com.android.server.power.optimization.Flags; @@ -69,15 +72,41 @@ import java.util.function.Consumer; * plugged in (e.g. using {@link android.app.job.JobScheduler JobScheduler}), and * while that can affect charging rates, it is still preferable to actually draining * the battery. + * <p> + * <b>CPU/GPU Usage</b><br> + * CPU/GPU headroom APIs are designed to be best used by applications with consistent and intense + * workload such as games to query the remaining capacity headroom over a short period and perform + * optimization accordingly. Due to the nature of the fast job scheduling and frequency scaling of + * CPU and GPU, the headroom by nature will have "TOCTOU" problem which makes it less suitable for + * apps with inconsistent or low workload to take any useful action but simply monitoring. And to + * avoid oscillation it's not recommended to adjust workload too frequent (on each polling request) + * or too aggressively. As the headroom calculation is more based on reflecting past history usage + * than predicting future capacity. Take game as an example, if the API returns CPU headroom of 0 in + * one scenario (especially if it's constant across multiple calls), or some value significantly + * smaller than other scenarios, then it can reason that the recent performance result is more CPU + * bottlenecked. Then reducing the CPU workload intensity can help reserve some headroom to handle + * the load variance better, which can result in less frame drops or smooth FPS value. On the other + * hand, if the API returns large CPU headroom constantly, the app can be more confident to increase + * the workload and expect higher possibility of device meeting its performance expectation. + * App can also use thermal APIs to read the current thermal status and headroom first, then poll + * the CPU and GPU headroom if the device is (about to) getting thermal throttled. If the CPU/GPU + * headrooms provide enough significance such as one valued at 0 while the other at 100, then it can + * be used to infer that reducing CPU workload could be more efficient to cool down the device. + * There is a caveat that the power controller may scale down the frequency of the CPU and GPU due + * to thermal and other reasons, which can result in a higher than usual percentage usage of the + * capacity. */ @SystemService(Context.SYSTEM_HEALTH_SERVICE) public class SystemHealthManager { + private static final String TAG = "SystemHealthManager"; @NonNull private final IBatteryStats mBatteryStats; @Nullable private final IPowerStatsService mPowerStats; @Nullable private final IHintManager mHintManager; + @Nullable + private final IHintManager.HintManagerClientData mHintManagerClientData; private List<PowerMonitor> mPowerMonitorsInfo; private final Object mPowerMonitorsLock = new Object(); private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000; @@ -109,29 +138,72 @@ public class SystemHealthManager { mBatteryStats = batteryStats; mPowerStats = powerStats; mHintManager = hintManager; + IHintManager.HintManagerClientData data = null; + if (mHintManager != null) { + try { + data = mHintManager.getClientData(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get hint manager client data", e); + } + } + mHintManagerClientData = data; } /** - * Provides an estimate of global available CPU headroom. + * Provides an estimate of available CPU capacity headroom of the device. + * <p> + * The value can be used by the calling application to determine if the workload was CPU bound + * and then take action accordingly to ensure that the workload can be completed smoothly. It + * can also be used with the thermal status and headroom to determine if reducing the CPU bound + * workload can help reduce the device temperature to avoid thermal throttling. * <p> + * If the params are valid, each call will perform at least one synchronous binder transaction + * that can take more than 1ms. So it's not recommended to call or wait for this on critical + * threads. Some devices may implement this as an on-demand API with lazy initialization, so the + * caller should expect higher latency when making the first call (especially with non-default + * params) since app starts or after changing params, as the device may need to change its data + * collection. * - * @param params params to customize the CPU headroom calculation, null to use default params. - * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable. - * A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be - * granted. + * @param params params to customize the CPU headroom calculation, or null to use default. + * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable due to + * server error or not enough user CPU workload. + * Each valid value ranges from [0, 100], where 0 indicates no more cpu resources can be + * granted * @throws UnsupportedOperationException if the API is unsupported. + * @throws IllegalArgumentException if the params are invalid. * @throws SecurityException if the TIDs of the params don't belong to the same process. * @throws IllegalStateException if the TIDs of the params don't have the same affinity setting. */ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom( @Nullable CpuHeadroomParams params) { - if (mHintManager == null) { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { throw new UnsupportedOperationException(); } + if (params != null) { + if (params.mInternal.tids != null && (params.mInternal.tids.length == 0 + || params.mInternal.tids.length + > mHintManagerClientData.maxCpuHeadroomThreads)) { + throw new IllegalArgumentException( + "Invalid number of TIDs: " + params.mInternal.tids.length); + } + if (params.mInternal.calculationWindowMillis + < mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis + || params.mInternal.calculationWindowMillis + > mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis) { + throw new IllegalArgumentException( + "Invalid calculation window: " + + params.mInternal.calculationWindowMillis + ", expect range: [" + + mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis + + ", " + + mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis + + "]"); + } + } try { final CpuHeadroomResult ret = mHintManager.getCpuHeadroom( - params != null ? params.getInternal() : new CpuHeadroomParamsInternal()); + params != null ? params.mInternal : new CpuHeadroomParamsInternal()); if (ret == null || ret.getTag() != CpuHeadroomResult.globalHeadroom) { return Float.NaN; } @@ -141,27 +213,69 @@ public class SystemHealthManager { } } - + /** + * Gets the maximum number of TIDs this device supports for getting CPU headroom. + * <p> + * See {@link CpuHeadroomParams#setTids(int...)}. + * + * @return the maximum size of TIDs supported + * @throws UnsupportedOperationException if the CPU headroom API is unsupported. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + public @IntRange(from = 1) int getMaxCpuHeadroomTidsSize() { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { + throw new UnsupportedOperationException(); + } + return mHintManagerClientData.maxCpuHeadroomThreads; + } /** - * Provides an estimate of global available GPU headroom of the device. + * Provides an estimate of available GPU capacity headroom of the device. + * <p> + * The value can be used by the calling application to determine if the workload was GPU bound + * and then take action accordingly to ensure that the workload can be completed smoothly. It + * can also be used with the thermal status and headroom to determine if reducing the GPU bound + * workload can help reduce the device temperature to avoid thermal throttling. * <p> + * If the params are valid, each call will perform at least one synchronous binder transaction + * that can take more than 1ms. So it's not recommended to call or wait for this on critical + * threads. Some devices may implement this as an on-demand API with lazy initialization, so the + * caller should expect higher latency when making the first call (especially with non-default + * params) since app starts or after changing params, as the device may need to change its data + * collection. * - * @param params params to customize the GPU headroom calculation, null to use default params. + * @param params params to customize the GPU headroom calculation, or null to use default. * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable. - * A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be + * Each valid value ranges from [0, 100], where 0 indicates no more cpu resources can be * granted. * @throws UnsupportedOperationException if the API is unsupported. + * @throws IllegalArgumentException if the params are invalid. */ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom( @Nullable GpuHeadroomParams params) { - if (mHintManager == null) { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { throw new UnsupportedOperationException(); } + if (params != null) { + if (params.mInternal.calculationWindowMillis + < mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis + || params.mInternal.calculationWindowMillis + > mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis) { + throw new IllegalArgumentException( + "Invalid calculation window: " + + params.mInternal.calculationWindowMillis + ", expect range: [" + + mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis + + ", " + + mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis + + "]"); + } + } try { final GpuHeadroomResult ret = mHintManager.getGpuHeadroom( - params != null ? params.getInternal() : new GpuHeadroomParamsInternal()); + params != null ? params.mInternal : new GpuHeadroomParamsInternal()); if (ret == null || ret.getTag() != GpuHeadroomResult.globalHeadroom) { return Float.NaN; } @@ -172,7 +286,51 @@ public class SystemHealthManager { } /** - * Minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in + * Gets the range of the calculation window size for CPU headroom. + * <p> + * In API version 36, the range will be a superset of [50, 10000]. + * <p> + * See {@link CpuHeadroomParams#setCalculationWindowMillis(int)}. + * + * @return the range of the calculation window size supported in milliseconds. + * @throws UnsupportedOperationException if the CPU headroom API is unsupported. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + @NonNull + public Pair<Integer, Integer> getCpuHeadroomCalculationWindowRange() { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { + throw new UnsupportedOperationException(); + } + return new Pair<>( + mHintManagerClientData.supportInfo.headroom.cpuMinCalculationWindowMillis, + mHintManagerClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis); + } + + /** + * Gets the range of the calculation window size for GPU headroom. + * <p> + * In API version 36, the range will be a superset of [50, 10000]. + * <p> + * See {@link GpuHeadroomParams#setCalculationWindowMillis(int)}. + * + * @return the range of the calculation window size supported in milliseconds. + * @throws UnsupportedOperationException if the GPU headroom API is unsupported. + */ + @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) + @NonNull + public Pair<Integer, Integer> getGpuHeadroomCalculationWindowRange() { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { + throw new UnsupportedOperationException(); + } + return new Pair<>( + mHintManagerClientData.supportInfo.headroom.gpuMinCalculationWindowMillis, + mHintManagerClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis); + } + + /** + * Gets minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in * milliseconds. * <p> * The {@link #getCpuHeadroom(CpuHeadroomParams)} API may return cached result if called more @@ -182,7 +340,8 @@ public class SystemHealthManager { */ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) public long getCpuHeadroomMinIntervalMillis() { - if (mHintManager == null) { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) { throw new UnsupportedOperationException(); } try { @@ -193,7 +352,7 @@ public class SystemHealthManager { } /** - * Minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in + * Gets minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in * milliseconds. * <p> * The {@link #getGpuHeadroom(GpuHeadroomParams)} API may return cached result if called more @@ -203,7 +362,8 @@ public class SystemHealthManager { */ @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS) public long getGpuHeadroomMinIntervalMillis() { - if (mHintManager == null) { + if (mHintManager == null || mHintManagerClientData == null + || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) { throw new UnsupportedOperationException(); } try { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c94526bcdcd7..2656a7b58c8d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6372,6 +6372,19 @@ public final class Settings { "mouse_pointer_acceleration_enabled"; /** + * Mouse scrolling speed setting. + * + * This is an integer value in a range between -7 and +7, so there are 15 possible values. + * The setting only applies when mouse scrolling acceleration is not enabled. + * -7 = slowest + * 0 = default speed + * +7 = fastest + * + * @hide + */ + public static final String MOUSE_SCROLLING_SPEED = "mouse_scrolling_speed"; + + /** * Pointer fill style, specified by * {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants. * @@ -6623,6 +6636,7 @@ public final class Settings { PRIVATE_SETTINGS.add(MOUSE_POINTER_ACCELERATION_ENABLED); PRIVATE_SETTINGS.add(PREFERRED_REGION); PRIVATE_SETTINGS.add(MOUSE_SCROLLING_ACCELERATION); + PRIVATE_SETTINGS.add(MOUSE_SCROLLING_SPEED); } /** @@ -17395,13 +17409,6 @@ public final class Settings { /** - * Whether back preview animations are played when user does a back gesture or presses - * the back button. - * @hide - */ - public static final String ENABLE_BACK_ANIMATION = "enable_back_animation"; - - /** * An allow list of packages for which the user has granted the permission to communicate * across profiles. * diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index 5c38a1597433..195896dc8edf 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -81,7 +81,7 @@ public final class InputEventConsistencyVerifier { // Bitfield of pointer ids that are currently down. // Assumes that the largest possible pointer id is 31, which is potentially subject to change. - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + // (See MAX_POINTER_ID in frameworks/native/include/input/input.h) private int mTouchEventStreamPointers; // The device id and source of the current stream of touch events. diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 6cd4a4033adf..3e529ccf064a 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -57,7 +57,7 @@ public final class InputWindowHandle { InputConfig.NO_INPUT_CHANNEL, InputConfig.NOT_FOCUSABLE, InputConfig.NOT_TOUCHABLE, - InputConfig.PREVENT_SPLITTING, + InputConfig.DEPRECATED_PREVENT_SPLITTING, InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER, InputConfig.IS_WALLPAPER, InputConfig.PAUSE_DISPATCHING, diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 833f2d98554e..e665c08c63e4 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -160,6 +160,10 @@ public final class SurfaceControl implements Parcelable { float l, float t, float r, float b); private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, float cornerRadius); + private static native void nativeSetClientDrawnCornerRadius(long transactionObj, + long nativeObject, float clientDrawnCornerRadius); + private static native void nativeSetClientDrawnShadows(long transactionObj, + long nativeObject, float clientDrawnShadows); private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject, int blurRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, @@ -3654,6 +3658,66 @@ public final class SurfaceControl implements Parcelable { return this; } + + /** + * Disables corner radius of a {@link SurfaceControl}. When the radius set by + * {@link Transaction#setCornerRadius(SurfaceControl, float)} is equal to + * clientDrawnCornerRadius the corner radius drawn by SurfaceFlinger is disabled. + * + * @param sc SurfaceControl + * @param clientDrawnCornerRadius Corner radius drawn by the client + * @return Itself. + * @hide + */ + @NonNull + public Transaction setClientDrawnCornerRadius(@NonNull SurfaceControl sc, + float clientDrawnCornerRadius) { + checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setClientDrawnCornerRadius", this, sc, "clientDrawnCornerRadius=" + + clientDrawnCornerRadius); + } + if (Flags.ignoreCornerRadiusAndShadows()) { + nativeSetClientDrawnCornerRadius(mNativeObject, sc.mNativeObject, + clientDrawnCornerRadius); + } else { + Log.w(TAG, "setClientDrawnCornerRadius was called but" + + "ignore_corner_radius_and_shadows flag is disabled"); + } + + return this; + } + + /** + * Disables shadows of a {@link SurfaceControl}. When the radius set by + * {@link Transaction#setClientDrawnShadows(SurfaceControl, float)} is equal to + * clientDrawnShadowRadius the shadows drawn by SurfaceFlinger is disabled. + * + * @param sc SurfaceControl + * @param clientDrawnShadowRadius Shadow radius drawn by the client + * @return Itself. + * @hide + */ + @NonNull + public Transaction setClientDrawnShadows(@NonNull SurfaceControl sc, + float clientDrawnShadowRadius) { + checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setClientDrawnShadows", this, sc, + "clientDrawnShadowRadius=" + clientDrawnShadowRadius); + } + if (Flags.ignoreCornerRadiusAndShadows()) { + nativeSetClientDrawnShadows(mNativeObject, sc.mNativeObject, + clientDrawnShadowRadius); + } else { + Log.w(TAG, "setClientDrawnShadows was called but" + + "ignore_corner_radius_and_shadows flag is disabled"); + } + return this; + } + /** * Sets the background blur radius of the {@link SurfaceControl}. * diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 617e4762ebf0..25bd713d9191 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -50,7 +50,6 @@ import android.window.TrustedPresentationThresholds; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.policy.PhoneWindow; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -376,8 +375,7 @@ public final class WindowManagerGlobal { if (context != null && wparams.type > LAST_APPLICATION_WINDOW) { final TypedArray styles = context.obtainStyledAttributes(R.styleable.Window); - if (PhoneWindow.isOptingOutEdgeToEdgeEnforcement( - context.getApplicationInfo(), true /* local */, styles)) { + if (styles.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)) { wparams.privateFlags |= PRIVATE_FLAG_OPT_OUT_EDGE_TO_EDGE; } styles.recycle(); @@ -455,6 +453,7 @@ public final class WindowManagerGlobal { try { root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { + Log.e(TAG, "Couldn't add view: " + view, e); final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); // BadTokenException or InvalidDisplayException, clean up. if (viewIndex >= 0) { diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 1f341caa8ed3..6d2c0d0061dd 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -30,7 +30,8 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public interface WindowManagerPolicyConstants { - // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h and + // Policy flags. These flags are also defined in + // frameworks/native/include/input/Input.h and // frameworks/native/libs/input/android/os/IInputConstants.aidl int FLAG_WAKE = 0x00000001; int FLAG_VIRTUAL = 0x00000002; diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig index b3bd92b37357..c871d568e625 100644 --- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -45,3 +45,10 @@ flag { description: "If true, the APIs to manage content protection device policy will be enabled." bug: "319477846" } + +flag { + name: "exported_settings_activity_enabled" + namespace: "content_protection" + description: "If true, the content protection Settings Activity will be exported for launching externally." + bug: "385310141" +} diff --git a/core/java/android/widget/flags/flags.aconfig b/core/java/android/widget/flags/flags.aconfig index 88eb0438ec4b..83a4c8676a38 100644 --- a/core/java/android/widget/flags/flags.aconfig +++ b/core/java/android/widget/flags/flags.aconfig @@ -2,7 +2,7 @@ package: "android.widget.flags" container: "system" flag { name: "enable_fading_view_group" - namespace: "system_performance" + namespace: "wear_systems" description: "FRP screen during OOBE must have fading and scaling animation in Wear Watches" bug: "348515581" metadata { diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java index ac9bec305fff..6461f2a0fcda 100644 --- a/core/java/android/window/WindowInfosListenerForTest.java +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -103,12 +103,6 @@ public class WindowInfosListenerForTest { public final boolean isFocusable; /** - * True if the window is preventing splitting - */ - @SuppressLint("UnflaggedApi") // The API is only used for tests. - public final boolean isPreventSplitting; - - /** * True if the window duplicates touches received to wallpaper. */ @SuppressLint("UnflaggedApi") // The API is only used for tests. @@ -133,8 +127,6 @@ public class WindowInfosListenerForTest { this.transform = transform; this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0; this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0; - this.isPreventSplitting = (inputConfig - & InputConfig.PREVENT_SPLITTING) != 0; this.isDuplicateTouchToWallpaper = (inputConfig & InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0; this.isWatchOutsideTouch = (inputConfig diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index bb4770768cb1..8ff2e6aebdd0 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -91,6 +91,14 @@ flag { } flag { + name: "ignore_corner_radius_and_shadows" + namespace: "window_surfaces" + description: "Ignore the corner radius and shadows of a SurfaceControl" + bug: "375624570" + is_fixed_read_only: true +} # ignore_corner_radius_and_shadows + +flag { name: "jank_api" namespace: "window_surfaces" description: "Adds the jank data listener to AttachedSurfaceControl" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 091f86ec9234..de3e0d3faf43 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -113,13 +113,6 @@ flag { } flag { - name: "predictive_back_system_anims" - namespace: "systemui" - description: "Predictive back for system animations" - bug: "320510464" -} - -flag { name: "remove_activity_starter_dream_callback" namespace: "windowing_frontend" description: "Avoid a race with DreamManagerService callbacks for isDreaming by checking Activity state directly" @@ -423,7 +416,7 @@ flag { flag { name: "port_window_size_animation" - namespace: "systemui" + namespace: "windowing_frontend" description: "Port window-resize animation from legacy to shell" bug: "384976265" } diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index d62c8f378af0..73c2265d5f6e 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -53,21 +53,21 @@ oneway interface IKeyguardService { * * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. - * @param cameraGestureTriggered whether the camera gesture was triggered between - * {@link #onStartedGoingToSleep} and this method; if it's been - * triggered, we shouldn't lock the device. + * @param powerButtonLaunchGestureTriggered whether the power button double tap gesture was + * triggered between {@link #onStartedGoingToSleep} and this + * method; if it's been triggered, we shouldn't lock the device. */ - void onFinishedGoingToSleep(int pmSleepReason, boolean cameraGestureTriggered); + void onFinishedGoingToSleep(int pmSleepReason, boolean powerButtonLaunchGestureTriggered); /** * Called when the device has started waking up. * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up, * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. - * @param cameraGestureTriggered Whether we're waking up due to a power button double tap - * gesture. + * @param powerButtonLaunchGestureTriggered Whether we're waking up due to a power button + * double tap gesture. */ - void onStartedWakingUp(int pmWakeReason, boolean cameraGestureTriggered); + void onStartedWakingUp(int pmWakeReason, boolean powerButtonLaunchGestureTriggered); /** * Called when the device has finished waking up. diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 67e358a47cce..e0529b339d4a 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -184,14 +184,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private static final long ENFORCE_EDGE_TO_EDGE = 309578419; /** - * Disable opting out the edge-to-edge enforcement. - * {@link Build.VERSION_CODES#BAKLAVA} or above. - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) - private static final long DISABLE_OPT_OUT_EDGE_TO_EDGE = 377864165; - - /** * Override the layout in display cutout mode behavior. This will only apply if the edge to edge * is not enforced. */ @@ -458,7 +450,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { */ public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local, TypedArray windowStyle) { - return !isOptingOutEdgeToEdgeEnforcement(info, local, windowStyle) + return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false) && (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION || (Flags.enforceEdgeToEdge() && (local // Calling this doesn't require a permission. @@ -467,26 +459,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)))); } - /** - * Returns whether the given application is opting out edge-to-edge enforcement. - * - * @param info The application to query. - * @param local Whether this is called from the process of the given application. - * @param windowStyle The style of the window. - * @return {@code true} if the edge-to-edge enforcement is opting out. Otherwise, {@code false}. - */ - public static boolean isOptingOutEdgeToEdgeEnforcement(ApplicationInfo info, boolean local, - TypedArray windowStyle) { - final boolean disabled = (Flags.disableOptOutEdgeToEdge() && (local - // Calling this doesn't require a permission. - ? CompatChanges.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE) - // Calling this requires permissions. - : info.isChangeEnabled(DISABLE_OPT_OUT_EDGE_TO_EDGE))); - return !disabled && windowStyle.getBoolean( - R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false /* default */); - - } - @Override public final void setContainer(Window container) { super.setContainer(container); @@ -2514,7 +2486,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { TypedArray a = getWindowStyle(); WindowManager.LayoutParams params = getAttributes(); - ApplicationInfo appInfo = getContext().getApplicationInfo(); if (false) { System.out.println("From style:"); @@ -2526,7 +2497,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { System.out.println(s); } - mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(appInfo, true /* local */, a); + mEdgeToEdgeEnforced = isEdgeToEdgeEnforced( + getContext().getApplicationInfo(), true /* local */, a); if (mEdgeToEdgeEnforced) { getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; mDecorFitsSystemWindows = false; @@ -2535,7 +2507,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // mNavigationBarColor is not reset here because it might be used to draw the scrim. } if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE) - && !isOptingOutEdgeToEdgeEnforcement(appInfo, true /* local */, a)) { + && !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, + false /* defValue */)) { getAttributes().privateFlags |= PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; } diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java index 8df3f2abcafd..e522b508b44b 100644 --- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -94,14 +94,21 @@ public final class RavenwoodEnvironment { /** Used for testing */ @Disabled - @ChangeId public static final long TEST_COMPAT_ID_2 = 368131701L; + @ChangeId + public static final long TEST_COMPAT_ID_2 = 368131701L; /** Used for testing */ @EnabledAfter(targetSdkVersion = S) - @ChangeId public static final long TEST_COMPAT_ID_3 = 368131659L; + @ChangeId + public static final long TEST_COMPAT_ID_3 = 368131659L; /** Used for testing */ @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE) - @ChangeId public static final long TEST_COMPAT_ID_4 = 368132057L; + @ChangeId + public static final long TEST_COMPAT_ID_4 = 368132057L; + + /** Used for testing */ + @ChangeId + public static final long TEST_COMPAT_ID_5 = 387558811L; } } diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 1e7bfe32ba79..e6364a96bd9f 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -111,9 +111,8 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass class LoaderAssetsProvider : public AssetsProvider { public: static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) { - return (!assets_provider) ? EmptyAssetsProvider::Create() - : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider( - env, assets_provider)); + return std::unique_ptr<AssetsProvider>{ + assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr}; } bool ForEachFile(const std::string& /* root_path */, @@ -129,8 +128,8 @@ class LoaderAssetsProvider : public AssetsProvider { return debug_name_; } - bool IsUpToDate() const override { - return true; + UpToDate IsUpToDate() const override { + return UpToDate::Always; } ~LoaderAssetsProvider() override { @@ -212,7 +211,7 @@ class LoaderAssetsProvider : public AssetsProvider { auto string_result = static_cast<jstring>(env->CallObjectMethod( assets_provider_, gAssetsProviderOffsets.toString)); ScopedUtfChars str(env, string_result); - debug_name_ = std::string(str.c_str(), str.size()); + debug_name_ = std::string(str.c_str()); } // The global reference to the AssetsProvider @@ -243,10 +242,10 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags); break; case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()), - std::move(loader_assets), - property_flags); - break; + apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; case FORMAT_DIRECTORY: { auto assets = MultiAssetsProvider::Create(std::move(loader_assets), DirectoryAssetsProvider::Create(path.c_str())); @@ -316,10 +315,11 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t break; } case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable( - AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */), - std::move(loader_assets), property_flags); - break; + apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd), + nullptr /* path */), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); @@ -386,12 +386,15 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ break; } case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable( - AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */, - static_cast<off64_t>(offset), - static_cast<off64_t>(length)), - std::move(loader_assets), property_flags); - break; + apk_assets = + ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd), + nullptr /* path */, + static_cast<off64_t>(offset), + static_cast<off64_t>( + length)), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); @@ -408,13 +411,16 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ } static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) { - auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags); - if (apk_assets == nullptr) { - const std::string error_msg = - base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider); - jniThrowException(env, "java/io/IOException", error_msg.c_str()); - return 0; - } + auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create( + LoaderAssetsProvider::Create(env, assets_provider)), + flags); + if (apk_assets == nullptr) { + const std::string error_msg = + base::StringPrintf("Failed to load empty assets with provider %p", + (void*)assets_provider); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } return CreateGuardedApkAssets(std::move(apk_assets)); } @@ -443,10 +449,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); } -static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { +static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr)); auto apk_assets = scoped_apk_assets->get(); - return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE; + return (jint)apk_assets->IsUpToDate(); } static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { @@ -558,7 +564,7 @@ static const JNINativeMethod gApkAssetsMethods[] = { {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName}, {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, // @CriticalNative - {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate}, {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;", (void*)NativeGetOverlayableInfo}, diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 57bfc7086ed5..b2649a471b8a 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -198,7 +198,7 @@ static jobject NativeGetOverlayableMap(JNIEnv* env, jclass /*clazz*/, jlong ptr, auto assetmanager = LockAndStartAssetManager(ptr); const ScopedUtfChars package_name_utf8(env, package_name); CHECK(package_name_utf8.c_str() != nullptr); - const std::string std_package_name(package_name_utf8.c_str()); + const std::string_view std_package_name(package_name_utf8.c_str()); const std::unordered_map<std::string, std::string>* map = nullptr; assetmanager->ForEachPackage([&](const std::string& this_package_name, uint8_t package_id) { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0c243d1dc185..6f69e4005b80 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1113,6 +1113,22 @@ static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionOb transaction->setCornerRadius(ctrl, cornerRadius); } +static void nativeSetClientDrawnCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat clientDrawnCornerRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setClientDrawnCornerRadius(ctrl, clientDrawnCornerRadius); +} + +static void nativeSetClientDrawnShadows(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat clientDrawnShadowRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setClientDrawnShadowRadius(ctrl, clientDrawnShadowRadius); +} + static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint blurRadius) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -2547,6 +2563,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetCrop }, {"nativeSetCornerRadius", "(JJF)V", (void*)nativeSetCornerRadius }, + {"nativeSetClientDrawnCornerRadius", "(JJF)V", + (void*) nativeSetClientDrawnCornerRadius }, + {"nativeSetClientDrawnShadows", "(JJF)V", + (void*) nativeSetClientDrawnShadows }, {"nativeSetBackgroundBlurRadius", "(JJI)V", (void*)nativeSetBackgroundBlurRadius }, {"nativeSetLayerStack", "(JJI)V", diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index 0d99200f4e6f..64c9f540a97b 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -229,6 +229,7 @@ message SystemSettingsProto { optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto scrolling_acceleration = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto pointer_acceleration_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto scrolling_speed = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Mouse mouse = 38; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index dc954718d623..ed021b64f7a0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5405,10 +5405,9 @@ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" android:protectionLevel="signature" /> - <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing") - @hide - @TestApi - Allows an accessibility service to observe motion events without consuming them. --> + <!-- @TestApi Allows an accessibility service to observe motion events + without consuming them. + @hide --> <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" android:protectionLevel="signature" /> diff --git a/core/res/res/layout/notification_2025_expand_button.xml b/core/res/res/layout/notification_2025_expand_button.xml index c8263c26f38f..1c367544c90a 100644 --- a/core/res/res/layout/notification_2025_expand_button.xml +++ b/core/res/res/layout/notification_2025_expand_button.xml @@ -22,6 +22,7 @@ android:layout_height="wrap_content" android:layout_gravity="top|end" android:contentDescription="@string/expand_button_content_description_collapsed" + android:padding="@dimen/notification_2025_margin" > <LinearLayout diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index c827dcb16584..f108ce5bd1b9 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -168,7 +168,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml index ce38c1645fb1..6f3c15adb082 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_call.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml @@ -70,7 +70,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index 0021b8384847..bd17a3a0a74e 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -189,7 +189,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index f3e4ce13ff4b..edbebb17f825 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -193,7 +193,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" /> </FrameLayout> diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml index 6be5a1cee7f9..24b6ad09d3f7 100644 --- a/core/res/res/layout/notification_2025_template_conversation.xml +++ b/core/res/res/layout/notification_2025_template_conversation.xml @@ -152,7 +152,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" /> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml index 3f34eb3cbf0c..0c07053d428a 100644 --- a/core/res/res/layout/notification_2025_template_header.xml +++ b/core/res/res/layout/notification_2025_template_header.xml @@ -85,7 +85,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|end" - android:layout_margin="@dimen/notification_2025_margin" android:layout_alignParentEnd="true" /> <include layout="@layout/notification_close_button" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 45a5d85a097d..c50c5e9d3341 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4244,12 +4244,19 @@ is non-interactive. --> <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> - <!-- Allow the gesture to double tap the power button to trigger a target action. --> - <bool name="config_doubleTapPowerGestureEnabled">true</bool> - <!-- Default target action for double tap of the power button gesture. + <!-- Controls the double tap power button gesture to trigger a target action. + 0: Gesture is disabled + 1: Launch camera mode, allowing the user to disable/enable the double tap power gesture + from launching the camera application. + 2: Multi target mode, allowing the user to select one of the targets defined in + config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double + tap power gesture from triggering the selected target action. + --> + <integer name="config_doubleTapPowerGestureMode">2</integer> + <!-- Default target action for double tap of the power button gesture in multi target mode. 0: Launch camera 1: Launch wallet --> - <integer name="config_defaultDoubleTapPowerGestureAction">0</integer> + <integer name="config_doubleTapPowerGestureMultiTargetDefaultAction">0</integer> <!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos experience while the device is non-interactive. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a18f923d625b..24e7057320ff 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3167,8 +3167,8 @@ <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" /> <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" /> <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> - <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" /> - <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" /> + <java-symbol type="integer" name="config_doubleTapPowerGestureMode" /> + <java-symbol type="integer" name="config_doubleTapPowerGestureMultiTargetDefaultAction" /> <java-symbol type="bool" name="config_emergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index bb5380e1312d..06cd44e6544d 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253|6568" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253|6568|999" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -63,8 +63,8 @@ <!-- Burkina Faso: 1-4 digits (standard system default, not country specific) --> <shortcode country="bf" pattern="\\d{1,4}" free="3558" /> - <!-- Bulgaria: 4-5 digits, plus EU --> - <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" /> + <!-- Bulgaria: 4-6 digits, plus EU --> + <shortcode country="bg" pattern="\\d{4,6}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490|162055" /> <!-- Bahrain: 1-5 digits (standard system default, not country specific) --> <shortcode country="bh" pattern="\\d{1,5}" free="81181|85999" /> @@ -81,18 +81,21 @@ <!-- Canada: 5-6 digits --> <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" free="455677|24470" /> + <!-- DR Congo: 1-6 digits, known premium codes listed --> + <shortcode country="cd" pattern="\\d{1,6}" free="444123" /> + <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf --> <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" /> <!-- Chile: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="cl" pattern="\\d{4,5}" free="9963|9240|1038" /> + <shortcode country="cl" pattern="\\d{4,5}" free="9963|9240|1038|4848" /> <!-- China: premium shortcodes start with "1066", free shortcodes start with "1065": http://clients.txtnation.com/entries/197192-china-premium-sms-short-code-requirements --> <shortcode country="cn" premium="1066.*" free="1065.*" /> <!-- Colombia: 1-6 digits (not confirmed) --> - <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" /> + <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289|890119" /> <!-- Costa Rica --> <shortcode country="cr" pattern="\\d{1,6}" free="466453" /> @@ -116,6 +119,9 @@ <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) --> <shortcode country="do" pattern="\\d{1,6}" free="912892|912" /> + <!-- Algeria: 1-5 digits, known premium codes listed --> + <shortcode country="dz" pattern="\\d{1,5}" free="63071" /> + <!-- Ecuador: 1-6 digits (standard system default, not country specific) --> <shortcode country="ec" pattern="\\d{1,6}" free="466453|18512" /> @@ -123,20 +129,23 @@ http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht --> <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" /> - <!-- Egypt: 4-5 digits, known codes listed --> - <shortcode country="eg" pattern="\\d{4,5}" free="1499|10020" /> + <!-- Egypt: 4-6 digits, known codes listed --> + <shortcode country="eg" pattern="\\d{4,6}" free="1499|10020|100158" /> <!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU. http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain --> <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}|22791|222145|22189" /> + <!-- Ethiopia: 1-4 digits, known codes listed --> + <shortcode country="et" pattern="\\d{1,4}" free="8527" /> + <!-- Finland: 5-6 digits, premium 0600, 0700: http://en.wikipedia.org/wiki/Telephone_numbers_in_Finland --> <shortcode country="fi" pattern="\\d{5,6}" premium="0600.*|0700.*|171(?:59|63)" free="116\\d{3}|14789|17110" /> <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU: http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements, visual voicemail code for Orange: 21101 --> - <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051|33033" /> + <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051|33033|21727" /> <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU: http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf, @@ -179,17 +188,17 @@ <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" /> <!-- Iran: 4-8 digits, known premium codes listed --> - <shortcode country="ir" pattern="\\d{4,8}" free="700791|700792|100016|30008360" /> + <shortcode country="ir" pattern="\\d{4,8}" free="700792|100016|30008360" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> <shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" /> <!-- Jordan: 1-5 digits (standard system default, not country specific) --> - <shortcode country="jo" pattern="\\d{1,5}" free="99066" /> + <shortcode country="jo" pattern="\\d{1,5}" free="99066|99390" /> <!-- Japan: 8083 used by SOFTBANK_DCB_2 --> - <shortcode country="jp" pattern="\\d{1,5}" free="8083" /> + <shortcode country="jp" pattern="\\d{1,9}" free="8083|00050320" /> <!-- Kenya: 5 digits, known premium codes listed --> <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023|24088|23054" /> @@ -206,6 +215,9 @@ <!-- Kuwait: 1-5 digits (standard system default, not country specific) --> <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991|50976|7112" /> + <!-- Lesotho: 4-5 digits, known codes listed --> + <shortcode country="ls" pattern="\\d{4,5}" free="32012" /> + <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU --> <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399|1324" /> @@ -222,11 +234,14 @@ <!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed --> <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> + <!-- Mali: 1-5 digits, known codes listed --> + <shortcode country="ml" pattern="\\d{1,5}" free="36098" /> + <!-- Mongolia : 1-6 digits (standard system default, not country specific) --> <shortcode country="mn" pattern="\\d{1,6}" free="44444|45678|445566" /> <!-- Malawi: 1-5 digits (standard system default, not country specific) --> - <shortcode country="mw" pattern="\\d{1,5}" free="4276|4305" /> + <shortcode country="mw" pattern="\\d{1,5}" free="4276|4305|4326" /> <!-- Mozambique: 1-5 digits (standard system default, not country specific) --> <shortcode country="mz" pattern="\\d{1,5}" free="1714" /> @@ -323,11 +338,14 @@ <!-- Tajikistan: 4 digits, known premium codes listed --> <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" /> + <!-- Timor-Leste 1-5 digits, known codes listed --> + <shortcode country="tl" pattern="\\d{1,5}" free="46645" /> + <!-- Tanzania: 1-5 digits (standard system default, not country specific) --> - <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234|15324|15610" /> + <shortcode country="tz" pattern="\\d{1,5}" free="15046|15324|15610" /> - <!-- Tunisia: 5 digits, known premium codes listed --> - <shortcode country="tn" pattern="\\d{5}" free="85799" /> + <!-- Tunisia: 1-6 digits, known premium codes listed --> + <shortcode country="tn" pattern="\\d{1,6}" free="85799|772024" /> <!-- Turkey --> <shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" /> @@ -336,7 +354,7 @@ <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" /> <!-- Uganda(UG): 4 digits (standard system default, not country specific) --> - <shortcode country="ug" pattern="\\d{4}" free="8000|8009" /> + <shortcode country="ug" pattern="\\d{4}" free="8009" /> <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm), visual voicemail code for T-Mobile: 122 --> @@ -349,7 +367,7 @@ <shortcode country="ve" pattern="\\d{1,6}" free="538352" /> <!-- Vietnam: 1-6 digits (standard system default, not country specific) --> - <shortcode country="vn" pattern="\\d{1,6}" free="5001|9055|8079|90002|118989" /> + <shortcode country="vn" pattern="\\d{1,6}" free="5001|9055|90002|118989|46645" /> <!-- Mayotte (French Territory): 1-5 digits (not confirmed) --> <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 3bbb9519d016..1b6746ca4b63 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -313,6 +313,7 @@ android_ravenwood_test { "res/xml/power_profile_test_modem.xml", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } test_module_config { diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java index 37ef6cba8814..939bf2ec4b0a 100644 --- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java @@ -207,7 +207,8 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { final ComponentInfo info = new ComponentInfo(); info.applicationInfo = new ApplicationInfo(); info.applicationInfo.uid = uid; - return new RegisteredServicesCache.ServiceInfo<>(type, info, null); + return new RegisteredServicesCache.ServiceInfo<>(type, info, null /* componentName */, + 0 /* lastUpdateTime */); } private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) { @@ -301,7 +302,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { @Override protected ServiceInfo<TestServiceType> parseServiceInfo( - ResolveInfo resolveInfo) throws XmlPullParserException, IOException { + ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException { int size = mServices.size(); for (int i = 0; i < size; i++) { Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i); diff --git a/core/tests/coretests/src/android/security/advancedprotection/OWNERS b/core/tests/coretests/src/android/security/advancedprotection/OWNERS new file mode 100644 index 000000000000..9bf5e58c01a9 --- /dev/null +++ b/core/tests/coretests/src/android/security/advancedprotection/OWNERS @@ -0,0 +1 @@ +file:platform/frameworks/base:main:/core/java/android/security/advancedprotection/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java index 5613caf4427e..d26bb35e5481 100644 --- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java @@ -120,7 +120,9 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, progressMax, isStyledByProgress); - int fadedRed = 0x7FFF0000; + // Colors with 40% opacity + int fadedRed = 0x66FF0000; + List<Part> expected = new ArrayList<>(List.of(new Segment(1f, fadedRed, true))); assertThat(parts).isEqualTo(expected); @@ -199,8 +201,8 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, progressMax, isStyledByProgress); - // Colors with 50% opacity - int fadedGreen = 0x7F00FF00; + // Colors with 40% opacity + int fadedGreen = 0x6600FF00; List<Part> expected = new ArrayList<>(List.of( new Segment(0.50f, Color.RED), @@ -223,9 +225,9 @@ public class NotificationProgressBarTest { int progressMax = 100; boolean isStyledByProgress = true; - // Colors with 50% opacity - int fadedBlue = 0x7F0000FF; - int fadedYellow = 0x7FFFFF00; + // Colors with 40% opacity + int fadedBlue = 0x660000FF; + int fadedYellow = 0x66FFFF00; List<Part> expected = new ArrayList<>(List.of( new Segment(0.15f, Color.BLUE), @@ -261,9 +263,9 @@ public class NotificationProgressBarTest { List<Part> parts = NotificationProgressBar.processAndConvertToDrawableParts( segments, points, progress, progressMax, isStyledByProgress); - // Colors with 50% opacity - int fadedGreen = 0x7F00FF00; - int fadedYellow = 0x7FFFFF00; + // Colors with 40% opacity + int fadedGreen = 0x6600FF00; + int fadedYellow = 0x66FFFF00; List<Part> expected = new ArrayList<>(List.of( new Segment(0.15f, Color.RED), diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp index ed99a1f5cc4a..9197dec00d25 100644 --- a/core/tests/systemproperties/Android.bp +++ b/core/tests/systemproperties/Android.bp @@ -44,4 +44,5 @@ android_ravenwood_test { "src/**/*.java", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp index 7cf49ab5c376..5011f7ac1e7c 100644 --- a/core/tests/utiltests/Android.bp +++ b/core/tests/utiltests/Android.bp @@ -69,4 +69,5 @@ android_ravenwood_test { "src/com/android/internal/util/**/*.java", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } diff --git a/libs/WindowManager/Shell/multivalentTests/Android.bp b/libs/WindowManager/Shell/multivalentTests/Android.bp index eecf199a3ec2..03076c0940a4 100644 --- a/libs/WindowManager/Shell/multivalentTests/Android.bp +++ b/libs/WindowManager/Shell/multivalentTests/Android.bp @@ -35,7 +35,6 @@ android_app { android_robolectric_test { name: "WMShellRobolectricTests", instrumentation_for: "WindowManagerShellRobolectric", - upstream: true, java_resource_dirs: [ "robolectric/config", ], diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 4300e84e8044..2ca011bfe000 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -16,10 +16,12 @@ package com.android.wm.shell.shared; +import static android.app.WindowConfiguration.windowingModeToString; +import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; + import android.annotation.IntDef; import android.app.ActivityManager.RecentTaskInfo; import android.app.TaskInfo; -import android.app.WindowConfiguration; import android.os.Parcel; import android.os.Parcelable; @@ -28,11 +30,14 @@ import androidx.annotation.Nullable; import com.android.wm.shell.shared.split.SplitBounds; +import kotlin.collections.CollectionsKt; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * Simple container for recent tasks which should be presented as a single task within the @@ -43,11 +48,13 @@ public class GroupedTaskInfo implements Parcelable { public static final int TYPE_FULLSCREEN = 1; public static final int TYPE_SPLIT = 2; public static final int TYPE_FREEFORM = 3; + public static final int TYPE_MIXED = 4; @IntDef(prefix = {"TYPE_"}, value = { TYPE_FULLSCREEN, TYPE_SPLIT, - TYPE_FREEFORM + TYPE_FREEFORM, + TYPE_MIXED }) public @interface GroupType {} @@ -64,7 +71,7 @@ public class GroupedTaskInfo implements Parcelable { * TYPE_SPLIT: Contains the two split roots of each side * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode */ - @NonNull + @Nullable protected final List<TaskInfo> mTasks; /** @@ -85,6 +92,14 @@ public class GroupedTaskInfo implements Parcelable { protected final int[] mMinimizedTaskIds; /** + * Only set for TYPE_MIXED. + * + * The mixed set of task infos in this group. + */ + @Nullable + protected final List<GroupedTaskInfo> mGroupedTasks; + + /** * Create new for a stack of fullscreen tasks */ public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { @@ -111,18 +126,41 @@ public class GroupedTaskInfo implements Parcelable { minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } + /** + * Create new for a group of grouped task infos, those grouped task infos may not be mixed + * themselves (ie. multiple depths of mixed grouped task infos are not allowed). + */ + public static GroupedTaskInfo forMixed(@NonNull List<GroupedTaskInfo> groupedTasks) { + if (groupedTasks.isEmpty()) { + throw new IllegalArgumentException("Expected non-empty grouped task list"); + } + if (groupedTasks.stream().anyMatch(task -> task.mType == TYPE_MIXED)) { + throw new IllegalArgumentException("Unexpected grouped task list"); + } + return new GroupedTaskInfo(groupedTasks); + } + private GroupedTaskInfo( @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { mTasks = tasks; + mGroupedTasks = null; mSplitBounds = splitBounds; mType = type; mMinimizedTaskIds = minimizedFreeformTaskIds; ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); } + private GroupedTaskInfo(@NonNull List<GroupedTaskInfo> groupedTasks) { + mTasks = null; + mGroupedTasks = groupedTasks; + mSplitBounds = null; + mType = TYPE_MIXED; + mMinimizedTaskIds = null; + } + private void ensureAllMinimizedIdsPresent( @NonNull List<TaskInfo> tasks, @Nullable int[] minimizedFreeformTaskIds) { @@ -141,26 +179,47 @@ public class GroupedTaskInfo implements Parcelable { for (int i = 0; i < numTasks; i++) { mTasks.add(new TaskInfo(parcel)); } + mGroupedTasks = parcel.createTypedArrayList(GroupedTaskInfo.CREATOR); mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); mMinimizedTaskIds = parcel.createIntArray(); } /** - * Get primary {@link RecentTaskInfo} + * If TYPE_MIXED, returns the root of the grouped tasks + * For all other types, returns this task itself + */ + @NonNull + public GroupedTaskInfo getBaseGroupedTask() { + if (mType == TYPE_MIXED) { + return mGroupedTasks.getFirst(); + } + return this; + } + + /** + * Get primary {@link TaskInfo}. + * + * @throws IllegalStateException if the group is TYPE_MIXED. */ @NonNull public TaskInfo getTaskInfo1() { + if (mType == TYPE_MIXED) { + throw new IllegalStateException("No indexed tasks for a mixed task"); + } return mTasks.getFirst(); } /** - * Get secondary {@link RecentTaskInfo}. + * Get secondary {@link TaskInfo}, used primarily for TYPE_SPLIT. * - * Used in split screen. + * @throws IllegalStateException if the group is TYPE_MIXED. */ @Nullable public TaskInfo getTaskInfo2() { + if (mType == TYPE_MIXED) { + throw new IllegalStateException("No indexed tasks for a mixed task"); + } if (mTasks.size() > 1) { return mTasks.get(1); } @@ -172,9 +231,7 @@ public class GroupedTaskInfo implements Parcelable { */ @Nullable public TaskInfo getTaskById(int taskId) { - return mTasks.stream() - .filter(task -> task.taskId == taskId) - .findFirst().orElse(null); + return CollectionsKt.firstOrNull(getTaskInfoList(), taskInfo -> taskInfo.taskId == taskId); } /** @@ -182,35 +239,59 @@ public class GroupedTaskInfo implements Parcelable { */ @NonNull public List<TaskInfo> getTaskInfoList() { - return mTasks; + if (mType == TYPE_MIXED) { + return CollectionsKt.flatMap(mGroupedTasks, groupedTaskInfo -> groupedTaskInfo.mTasks); + } else { + return mTasks; + } } /** * @return Whether this grouped task contains a task with the given {@code taskId}. */ public boolean containsTask(int taskId) { - return mTasks.stream() - .anyMatch((task -> task.taskId == taskId)); + return getTaskById(taskId) != null; } /** - * Return {@link SplitBounds} if this is a split screen entry or {@code null} + * Returns whether the group is of the given type, if this is a TYPE_MIXED group, then returns + * whether the root task info is of the given type. + */ + public boolean isBaseType(@GroupType int type) { + return getBaseGroupedTask().mType == type; + } + + /** + * Return {@link SplitBounds} if this is a split screen entry or {@code null}. Only valid for + * TYPE_SPLIT. */ @Nullable public SplitBounds getSplitBounds() { + if (mType == TYPE_MIXED) { + throw new IllegalStateException("No split bounds for a mixed task"); + } return mSplitBounds; } /** - * Get type of this recents entry. One of {@link GroupType} + * Get type of this recents entry. One of {@link GroupType}. + * Note: This is deprecated, callers should use `isBaseType()` and not make assumptions about + * specific group types */ + @Deprecated @GroupType public int getType() { return mType; } + /** + * Returns the set of minimized task ids, only valid for TYPE_FREEFORM. + */ @Nullable public int[] getMinimizedTaskIds() { + if (mType == TYPE_MIXED) { + throw new IllegalStateException("No minimized task ids for a mixed task"); + } return mMinimizedTaskIds; } @@ -222,67 +303,64 @@ public class GroupedTaskInfo implements Parcelable { GroupedTaskInfo other = (GroupedTaskInfo) obj; return mType == other.mType && Objects.equals(mTasks, other.mTasks) + && Objects.equals(mGroupedTasks, other.mGroupedTasks) && Objects.equals(mSplitBounds, other.mSplitBounds) && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds); } @Override public int hashCode() { - return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); + return Objects.hash(mType, mTasks, mGroupedTasks, mSplitBounds, + Arrays.hashCode(mMinimizedTaskIds)); } @Override public String toString() { StringBuilder taskString = new StringBuilder(); - for (int i = 0; i < mTasks.size(); i++) { - if (i == 0) { - taskString.append("Task"); - } else { - taskString.append(", Task"); + if (mType == TYPE_MIXED) { + taskString.append("GroupedTasks=" + mGroupedTasks.stream() + .map(GroupedTaskInfo::toString) + .collect(Collectors.joining(",\n\t", "[\n\t", "\n]"))); + } else { + taskString.append("Tasks=" + mTasks.stream() + .map(taskInfo -> getTaskInfoDumpString(taskInfo)) + .collect(Collectors.joining(", ", "[", "]"))); + if (mSplitBounds != null) { + taskString.append(", SplitBounds=").append(mSplitBounds); } - taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i))); + taskString.append(", Type=" + typeToString(mType)); + taskString.append(", Minimized Task IDs=" + Arrays.toString(mMinimizedTaskIds)); } - if (mSplitBounds != null) { - taskString.append(", SplitBounds: ").append(mSplitBounds); - } - taskString.append(", Type="); - switch (mType) { - case TYPE_FULLSCREEN: - taskString.append("TYPE_FULLSCREEN"); - break; - case TYPE_SPLIT: - taskString.append("TYPE_SPLIT"); - break; - case TYPE_FREEFORM: - taskString.append("TYPE_FREEFORM"); - break; - } - taskString.append(", Minimized Task IDs: "); - taskString.append(Arrays.toString(mMinimizedTaskIds)); return taskString.toString(); } - private String getTaskInfo(TaskInfo taskInfo) { + private String getTaskInfoDumpString(TaskInfo taskInfo) { if (taskInfo == null) { return null; } + final boolean isExcluded = (taskInfo.baseIntent.getFlags() + & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0; return "id=" + taskInfo.taskId - + " baseIntent=" + - (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null - ? taskInfo.baseIntent.getComponent().flattenToString() - : "null") - + " winMode=" + WindowConfiguration.windowingModeToString( - taskInfo.getWindowingMode()); + + " winMode=" + windowingModeToString(taskInfo.getWindowingMode()) + + " visReq=" + taskInfo.isVisibleRequested + + " vis=" + taskInfo.isVisible + + " excluded=" + isExcluded + + " baseIntent=" + + (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null + ? taskInfo.baseIntent.getComponent().flattenToShortString() + : "null"); } @Override public void writeToParcel(Parcel parcel, int flags) { // We don't use the parcel list methods because we want to only write the TaskInfo state // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated - parcel.writeInt(mTasks.size()); - for (int i = 0; i < mTasks.size(); i++) { + final int tasksSize = mTasks != null ? mTasks.size() : 0; + parcel.writeInt(tasksSize); + for (int i = 0; i < tasksSize; i++) { mTasks.get(i).writeTaskToParcel(parcel, flags); } + parcel.writeTypedList(mGroupedTasks); parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); parcel.writeIntArray(mMinimizedTaskIds); @@ -293,6 +371,16 @@ public class GroupedTaskInfo implements Parcelable { return 0; } + private String typeToString(@GroupType int type) { + return switch (type) { + case TYPE_FULLSCREEN -> "FULLSCREEN"; + case TYPE_SPLIT -> "SPLIT"; + case TYPE_FREEFORM -> "FREEFORM"; + case TYPE_MIXED -> "MIXED"; + default -> "UNKNOWN"; + }; + } + public static final Creator<GroupedTaskInfo> CREATOR = new Creator() { @Override public GroupedTaskInfo createFromParcel(Parcel in) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt index 06a55d3dbbd0..08079d94fcee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.apptoweb import android.app.assist.AssistContent -import android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI import android.content.Context import android.content.Intent import android.content.Intent.ACTION_VIEW @@ -113,5 +112,5 @@ fun getDomainVerificationUserState( * Returns the web uri from the given [AssistContent]. */ fun AssistContent.getSessionWebUri(): Uri? { - return extras.getParcelable(EXTRA_SESSION_TRANSFER_WEB_URI) ?: webUri + return sessionTransferUri ?: webUri } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index e96bc02c1198..8dabd54a01ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -28,7 +28,6 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.window.flags.Flags.unifyBackNavigationTransition; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; @@ -40,23 +39,17 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskInfo; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.provider.Settings.Global; import android.util.Log; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; @@ -92,7 +85,6 @@ import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.TransitionUtil; -import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -102,7 +94,6 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; /** @@ -111,14 +102,7 @@ import java.util.function.Predicate; public class BackAnimationController implements RemoteCallable<BackAnimationController>, ConfigurationChangeListener { private static final String TAG = "ShellBackPreview"; - private static final int SETTING_VALUE_OFF = 0; - private static final int SETTING_VALUE_ON = 1; - public static final boolean IS_ENABLED = - SystemProperties.getInt("persist.wm.debug.predictive_back", - SETTING_VALUE_ON) == SETTING_VALUE_ON; - - /** Predictive back animation developer option */ - private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); + /** * Max duration to wait for an animation to finish before triggering the real back. */ @@ -148,11 +132,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mReceivedNullNavigationInfo = false; private final IActivityTaskManager mActivityTaskManager; private final Context mContext; - private final ContentResolver mContentResolver; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mShellExecutor; - private final Handler mBgHandler; private final WindowManager mWindowManager; private final Transitions mTransitions; @VisibleForTesting @@ -245,7 +227,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, - @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, @@ -256,10 +237,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont shellInit, shellController, shellExecutor, - backgroundHandler, ActivityTaskManager.getService(), context, - context.getContentResolver(), backAnimationBackground, shellBackAnimationRegistry, shellCommandHandler, @@ -272,10 +251,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, - @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, Context context, - ContentResolver contentResolver, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, @@ -285,10 +262,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; mContext = context; - mContentResolver = contentResolver; mRequirePointerPilfer = context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer); - mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; mShellBackAnimationRegistry = shellBackAnimationRegistry; @@ -305,8 +280,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onInit() { - setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); - updateEnableAnimationFromFlags(); createAdapter(); mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR, this::createExternalInterface, this); @@ -314,42 +287,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellController.addConfigurationChangeListener(this); } - private void setupAnimationDeveloperSettingsObserver( - @NonNull ContentResolver contentResolver, - @NonNull @ShellBackgroundThread final Handler backgroundHandler) { - if (predictiveBackSystemAnims()) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " - + "developer settings flag is ignored and no content observer registered"); - return; - } - ContentObserver settingsObserver = new ContentObserver(backgroundHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - updateEnableAnimationFromFlags(); - } - }; - contentResolver.registerContentObserver( - Global.getUriFor(Global.ENABLE_BACK_ANIMATION), - false, settingsObserver, UserHandle.USER_SYSTEM - ); - } - - /** - * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the - * aconfig flag and the developer settings flag - */ - @ShellBackgroundThread - private void updateEnableAnimationFromFlags() { - boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled(); - mEnableAnimations.set(isEnabled); - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); - } - - private boolean isDeveloperSettingEnabled() { - return Global.getInt(mContext.getContentResolver(), - Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON; - } - public BackAnimation getBackAnimationImpl() { return mBackAnimation; } @@ -617,14 +554,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void startBackNavigation(@NonNull BackTouchTracker touchTracker) { try { startLatencyTracking(); - final BackAnimationAdapter adapter = mEnableAnimations.get() - ? mBackAnimationAdapter : null; - if (adapter != null && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) { - adapter.updateSupportedAnimators( + if (mBackAnimationAdapter != null + && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) { + mBackAnimationAdapter.updateSupportedAnimators( mShellBackAnimationRegistry.getSupportedAnimators()); } mBackNavigationInfo = mActivityTaskManager.startBackNavigation( - mNavigationObserver, adapter); + mNavigationObserver, mBackAnimationAdapter); onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker); } catch (RemoteException remoteException) { Log.e(TAG, "Failed to initAnimation", remoteException); @@ -696,9 +632,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private boolean shouldDispatchToAnimator() { - return mEnableAnimations.get() - && mBackNavigationInfo != null - && mBackNavigationInfo.isPrepareRemoteAnimation(); + return mBackNavigationInfo != null && mBackNavigationInfo.isPrepareRemoteAnimation(); } private void tryPilferPointers() { @@ -1093,7 +1027,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont () -> mShellExecutor.execute(this::onBackAnimationFinished)); if (mApps.length >= 1) { - mCurrentTracker.updateStartLocation(); BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); dispatchOnBackStarted(mActiveCallback, startEvent); if (startEvent.getSwipeEdge() == EDGE_NONE) { @@ -1194,7 +1127,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ private void dump(PrintWriter pw, String prefix) { pw.println(prefix + "BackAnimationController state:"); - pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get()); pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); 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 1408b6efc7f9..2600bcc18f72 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 @@ -108,7 +108,6 @@ import com.android.wm.shell.recents.TaskStackTransitionObserver; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; -import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -438,29 +437,24 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler, BackAnimationBackground backAnimationBackground, Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, Transitions transitions, @ShellMainThread Handler handler ) { - if (BackAnimationController.IS_ENABLED) { return shellBackAnimationRegistry.map( (animations) -> new BackAnimationController( shellInit, shellController, shellExecutor, - backgroundHandler, context, backAnimationBackground, animations, shellCommandHandler, transitions, handler)); - } - return Optional.empty(); } @BindsOptionalOf diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 03f388c9f1c9..9e2b9b20be16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -117,6 +117,7 @@ public abstract class Pip2Module { PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { if (!PipUtils.isPip2ExperimentEnabled()) { return Optional.empty(); @@ -126,7 +127,7 @@ public abstract class Pip2Module { displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController, - mainExecutor)); + pipUiEventLogger, mainExecutor)); } } @@ -188,11 +189,11 @@ public abstract class Pip2Module { FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, Optional<PipPerfHintController> pipPerfHintControllerOptional, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, + PipUiEventLogger pipUiEventLogger) { return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm, floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional, - pipBoundsAlgorithm, pipTransitionState); + pipTransitionState, pipUiEventLogger); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 536dc2a58534..a4620d5a4dfe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -339,7 +339,7 @@ class DesktopImmersiveController( .setWindowCrop(leash, endBounds.width(), endBounds.height()) .apply() onTaskResizeAnimationListener?.onAnimationEnd(taskId) - finishCallback.onTransitionFinished(null /* wct */) + finishCallback.onTransitionFinished(/* wct= */ null) } ) addUpdateListener { animation -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index ceef69969d9a..e8f9a789bb98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -306,7 +306,7 @@ class DesktopModeEventLogger { fun logTaskInfoStateInit() { logTaskUpdate( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD, - /* session_id */ 0, + sessionId = 0, TaskUpdate( visibleTaskCount = 0, instanceId = 0, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index cd37113666bb..32ee319a053b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -274,7 +274,7 @@ public class DesktopModeVisualIndicator { lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL; final WindowlessWindowManager windowManager = new WindowlessWindowManager( mTaskInfo.configuration, mLeash, - null /* hostInputToken */); + /* hostInputToken= */ null); mViewHost = new SurfaceControlViewHost(mContext, mDisplayController.getDisplay(mTaskInfo.displayId), windowManager, "DesktopModeVisualIndicator"); @@ -338,7 +338,7 @@ public class DesktopModeVisualIndicator { if (mCurrentType == NO_INDICATOR) { fadeInIndicator(newType); } else if (newType == NO_INDICATOR) { - fadeOutIndicator(null /* finishCallback */); + fadeOutIndicator(/* finishCallback= */ null); } else { final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, 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 d180ea7b79ff..ee817b34b24a 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 @@ -762,7 +762,7 @@ class DesktopTasksController( return } val wct = WindowContainerTransaction() - wct.reorder(taskInfo.token, true /* onTop */, true /* includingParents */) + wct.reorder(taskInfo.token, /* onTop= */ true, /* includingParents= */ true) startLaunchTransition( transitionType = TRANSIT_TO_FRONT, wct = wct, @@ -884,7 +884,7 @@ class DesktopTasksController( } else if (Flags.enableMoveToNextDisplayShortcut()) { applyFreeformDisplayChange(wct, task, displayId) } - wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) + wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) if (Flags.enableDisplayFocusInShellTransitions()) { // Bring the destination display to top with includingParents=true, so that the // destination display gains the display focus, which makes the top task in the display @@ -896,7 +896,7 @@ class DesktopTasksController( performDesktopExitCleanupIfNeeded(task.taskId, task.displayId, wct) } - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) + transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } /** @@ -1672,7 +1672,7 @@ class DesktopTasksController( requestedTaskId, splitPosition, options.toBundle(), - null, /* hideTaskToken */ + /* hideTaskToken= */ null, ) } } @@ -1709,8 +1709,8 @@ class DesktopTasksController( fillIn, splitPosition, options.toBundle(), - null /* hideTaskToken */, - true /* forceLaunchNewTask */, + /* hideTaskToken= */ null, + /* forceLaunchNewTask= */ true, splitIndex, ) } @@ -1961,7 +1961,7 @@ class DesktopTasksController( wct.setBounds(taskInfo.token, initialBounds) } wct.setWindowingMode(taskInfo.token, targetWindowingMode) - wct.reorder(taskInfo.token, true /* onTop */) + wct.reorder(taskInfo.token, /* onTop= */ true) if (useDesktopOverrideDensity()) { wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) } @@ -2796,7 +2796,7 @@ class DesktopTasksController( controller, "visibleTaskCount", { controller -> result[0] = controller.visibleTaskCount(displayId) }, - true, /* blocking */ + /* blocking= */ true, ) return result[0] } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 0330a5f0c4e7..c2dd4d28305b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -234,7 +234,7 @@ class DesktopTasksLimiter( // If it's a running task, reorder it to back. taskIdToMinimize ?.let { shellTaskOrganizer.getRunningTaskInfo(it) } - ?.let { wct.reorder(it.token, false /* onTop */) } + ?.let { wct.reorder(it.token, /* onTop= */ false) } return taskIdToMinimize } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 72c064248988..1380a9ca164f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -131,7 +131,7 @@ sealed class DragToDesktopTransitionHandler( val pendingIntent = PendingIntent.getActivityAsUser( context.createContextAsUser(taskUser, /* flags= */ 0), - 0 /* requestCode */, + /* requestCode= */ 0, launchHomeIntent, FLAG_MUTABLE or FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or FILL_IN_COMPONENT, options.toBundle(), @@ -234,7 +234,7 @@ sealed class DragToDesktopTransitionHandler( val wct = WindowContainerTransaction() restoreWindowOrder(wct, state) state.startTransitionFinishTransaction?.apply() - state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) requestSplitFromScaledTask(splitPosition, wct) clearState() } else { @@ -440,7 +440,7 @@ sealed class DragToDesktopTransitionHandler( val wct = WindowContainerTransaction() restoreWindowOrder(wct) state.startTransitionFinishTransaction?.apply() - state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) requestSplitSelect(wct, taskInfo, splitPosition) } return true @@ -492,7 +492,7 @@ sealed class DragToDesktopTransitionHandler( finishTransaction = startTransactionFinishT, ) // Call finishCallback to merge animation before startTransitionFinishCb is called - finishCallback.onTransitionFinished(null /* wct */) + finishCallback.onTransitionFinished(/* wct= */ null) animateEndDragToDesktop(startTransaction = t, startTransitionFinishCb) } else if (isCancelTransition) { info.changes.forEach { change -> @@ -500,8 +500,8 @@ sealed class DragToDesktopTransitionHandler( startTransactionFinishT.show(change.leash) } t.apply() - finishCallback.onTransitionFinished(null /* wct */) - startTransitionFinishCb.onTransitionFinished(null /* wct */) + finishCallback.onTransitionFinished(/* wct= */ null) + startTransitionFinishCb.onTransitionFinished(/* wct= */ null) clearState() } } @@ -653,7 +653,7 @@ sealed class DragToDesktopTransitionHandler( interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) } else if (state.cancelTransitionToken == transition) { state.draggedTaskChange?.leash?.let { state.startTransitionFinishTransaction?.show(it) } - state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null) clearState() } else { // This transition being aborted is neither the start, nor the cancel transition, so @@ -741,19 +741,19 @@ sealed class DragToDesktopTransitionHandler( // TODO(b/322852244): investigate why even though these "other" tasks are // reordered in front of home and behind the translucent dragged task, its // surface is not visible on screen. - wct.reorder(wc, true /* toTop */) + wct.reorder(wc, /* onTop= */ true) } val wc = state.draggedTaskChange?.container ?: error("Dragged task should be non-null before cancelling") // Then the dragged task a the very top. - wct.reorder(wc, true /* toTop */) + wct.reorder(wc, /* onTop= */ true) } is TransitionState.FromSplit -> { val wc = state.splitRootChange?.container ?: error("Split root should be non-null before cancelling") - wct.reorder(wc, true /* toTop */) + wct.reorder(wc, /* onTop= */ true) } } val homeWc = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 8c6d5f5c6660..562b26014bf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -59,6 +59,7 @@ import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -98,6 +99,7 @@ public class PipController implements ConfigurationChangeListener, private final PipTouchHandler mPipTouchHandler; private final PipAppOpsListener mPipAppOpsListener; private final PhonePipMenuController mPipMenuController; + private final PipUiEventLogger mPipUiEventLogger; private final ShellExecutor mMainExecutor; private final PipImpl mImpl; private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); @@ -143,6 +145,7 @@ public class PipController implements ConfigurationChangeListener, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor) { mContext = context; mShellCommandHandler = shellCommandHandler; @@ -160,6 +163,7 @@ public class PipController implements ConfigurationChangeListener, mPipTouchHandler = pipTouchHandler; mPipAppOpsListener = pipAppOpsListener; mPipMenuController = pipMenuController; + mPipUiEventLogger = pipUiEventLogger; mMainExecutor = mainExecutor; mImpl = new PipImpl(); @@ -187,6 +191,7 @@ public class PipController implements ConfigurationChangeListener, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -197,7 +202,7 @@ public class PipController implements ConfigurationChangeListener, displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController, - mainExecutor); + pipUiEventLogger, mainExecutor); } public PipImpl getPipImpl() { @@ -238,18 +243,6 @@ public class PipController implements ConfigurationChangeListener, }); mPipAppOpsListener.setCallback(mPipTouchHandler.getMotionHelper()); - mPipTransitionState.addPipTransitionStateChangedListener( - (oldState, newState, extra) -> { - if (newState == PipTransitionState.ENTERED_PIP) { - final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo(); - if (taskInfo != null && taskInfo.topActivity != null) { - mPipAppOpsListener.onActivityPinned( - taskInfo.topActivity.getPackageName()); - } - } else if (newState == PipTransitionState.EXITED_PIP) { - mPipAppOpsListener.onActivityUnpinned(); - } - }); } private ExternalInterfaceBinder createExternalInterface() { @@ -446,14 +439,25 @@ public class PipController implements ConfigurationChangeListener, mPipTransitionState.setSwipePipToHomeState(overlay, appBounds); break; case PipTransitionState.ENTERED_PIP: + final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo(); + if (taskInfo != null && taskInfo.topActivity != null) { + mPipAppOpsListener.onActivityPinned(taskInfo.topActivity.getPackageName()); + mPipUiEventLogger.setTaskInfo(taskInfo); + } if (mPipTransitionState.isInSwipePipToHomeTransition()) { + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER); mPipTransitionState.resetSwipePipToHomeState(); + } else { + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER); } for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { listener.accept(true /* inPip */); } break; case PipTransitionState.EXITED_PIP: + mPipAppOpsListener.onActivityUnpinned(); + mPipUiEventLogger.setTaskInfo(null); for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { listener.accept(false /* inPip */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 37296531ee34..9babe9e9e4eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -43,20 +43,20 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.pip.PipAppOpsListener; -import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip2.animation.PipResizeAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; +import java.util.Optional; + import kotlin.Unit; import kotlin.jvm.functions.Function0; -import java.util.Optional; - /** * A helper to animate and manipulate the PiP. */ @@ -80,12 +80,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private static final float DISMISS_CIRCLE_PERCENT = 0.85f; private final Context mContext; - private @NonNull PipBoundsState mPipBoundsState; - private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm; - private @NonNull PipScheduler mPipScheduler; - private @NonNull PipTransitionState mPipTransitionState; - private PhonePipMenuController mMenuController; - private PipSnapAlgorithm mSnapAlgorithm; + @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final PipScheduler mPipScheduler; + @NonNull private final PipTransitionState mPipTransitionState; + @NonNull private final PipUiEventLogger mPipUiEventLogger; + private final PhonePipMenuController mMenuController; + private final PipSnapAlgorithm mSnapAlgorithm; /** The region that all of PIP must stay within. */ private final Rect mFloatingAllowedArea = new Rect(); @@ -168,10 +168,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, Optional<PipPerfHintController> pipPerfHintControllerOptional, - PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger) { mContext = context; mPipBoundsState = pipBoundsState; - mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipScheduler = pipScheduler; mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; @@ -185,6 +184,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this); + mPipUiEventLogger = pipUiEventLogger; } void init() { @@ -850,9 +850,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, if (mPipBoundsState.getBounds().left < 0 && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) { mPipBoundsState.setStashed(STASH_TYPE_LEFT); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT); } else if (mPipBoundsState.getBounds().left >= 0 && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) { mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT); } mMenuController.hideMenu(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl index 964e5fd62a5f..af1679f2d175 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl @@ -36,12 +36,6 @@ import com.android.internal.os.IResultReceiver; interface IRecentsAnimationController { /** - * Takes a screenshot of the task associated with the given {@param taskId}. Only valid for the - * current set of task ids provided to the handler. - */ - TaskSnapshot screenshotTask(int taskId); - - /** * Sets the final surface transaction on a Task. This is used by Launcher to notify the system * that animating Activity to PiP has completed and the associated task surface should be * updated accordingly. This should be called before `finish` diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 032dac9ff3a2..76496b06a4dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1227,19 +1227,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, } @Override - public TaskSnapshot screenshotTask(int taskId) { - try { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.screenshotTask: taskId=%d", mInstanceId, taskId); - return ActivityTaskManager.getService().takeTaskSnapshot(taskId, - true /* updateCache */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to screenshot task", e); - } - return null; - } - - @Override public void setInputConsumerEnabled(boolean enabled) { mExecutor.execute(() -> { if (mFinishCB == null || !enabled) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 3f65d9318692..1264c013faf5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -231,6 +231,7 @@ internal class AppHandleViewHolder( fun disposeStatusBarInputLayer() { if (!statusBarInputLayerExists) return statusBarInputLayerExists = false + statusBarInputLayer?.view?.setOnTouchListener(null) handler.post { statusBarInputLayer?.releaseView() statusBarInputLayer = null diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt index 40ecdecde4e7..805f4c2fe7f8 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt @@ -67,6 +67,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_PIP2) +@FlakyTest(bugId = 386333280) open class FromSplitScreenEnterPipOnUserLeaveHintTest(flicker: LegacyFlickerTest) : EnterPipTransition(flicker) { override val pipApp: PipAppHelper = PipAppHelper(instrumentation) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 47ee7bb20199..bbdb90f0a37c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -61,9 +61,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.provider.Settings; import android.testing.AndroidTestingRunner; -import android.testing.TestableContentResolver; import android.testing.TestableLooper; import android.view.IRemoteAnimationRunner; import android.view.KeyEvent; @@ -84,7 +82,6 @@ import android.window.WindowContainerToken; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; -import com.android.internal.util.test.FakeSettingsProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; @@ -109,7 +106,6 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class BackAnimationControllerTest extends ShellTestCase { - private static final String ANIMATION_ENABLED = "1"; private final TestShellExecutor mShellExecutor = new TestShellExecutor(); private ShellInit mShellInit; @@ -148,8 +144,6 @@ public class BackAnimationControllerTest extends ShellTestCase { private Transitions.TransitionHandler mTakeoverHandler; private BackAnimationController mController; - private TestableContentResolver mContentResolver; - private TestableLooper mTestableLooper; private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation; private CrossTaskBackAnimation mCrossTaskBackAnimation; @@ -166,11 +160,6 @@ public class BackAnimationControllerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(InputManager.class, mInputManager); mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; - mContentResolver = new TestableContentResolver(mContext); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, - ANIMATION_ENABLED); - mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler); @@ -187,10 +176,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellInit, mShellController, mShellExecutor, - new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver, mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, @@ -342,47 +329,6 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test - public void animationDisabledFromSettings() throws RemoteException { - // Toggle the setting off - Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); - ShellInit shellInit = new ShellInit(mShellExecutor); - mController = - new BackAnimationController( - shellInit, - mShellController, - mShellExecutor, - new Handler(mTestableLooper.getLooper()), - mActivityTaskManager, - mContext, - mContentResolver, - mAnimationBackground, - mShellBackAnimationRegistry, - mShellCommandHandler, - mTransitions, - mHandler); - shellInit.init(); - registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - - ArgumentCaptor<BackMotionEvent> backEventCaptor = - ArgumentCaptor.forClass(BackMotionEvent.class); - - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, - /* enableAnimation = */ false, - /* isAnimationCallback = */ false); - - triggerBackGesture(); - releaseBackGesture(); - - verify(mAppCallback, times(1)).onBackInvoked(); - - verify(mAnimatorCallback, never()).onBackStarted(any()); - verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture()); - verify(mAnimatorCallback, never()).onBackInvoked(); - verify(mBackAnimationRunner, never()).onAnimationStart( - anyInt(), any(), any(), any(), any()); - } - - @Test public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt index db00f41f723b..04f9ada8a9d7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt @@ -142,7 +142,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() { changeMode: Int = WindowManager.TRANSIT_CLOSE, task: RunningTaskInfo, ): TransitionInfo = - TransitionInfo(type, 0 /* flags */).apply { + TransitionInfo(type, /* flags= */ 0).apply { addChange( TransitionInfo.Change(mock(), closingTaskLeash).apply { mode = changeMode diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt index d14c6402982d..c705f5a5ac87 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt @@ -153,7 +153,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() { changeMode: Int = WindowManager.TRANSIT_TO_BACK, task: RunningTaskInfo, ): TransitionInfo = - TransitionInfo(type, 0 /* flags */).apply { + TransitionInfo(type, /* flags= */ 0).apply { addChange( TransitionInfo.Change(mock(), closingTaskLeash).apply { mode = changeMode diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index 3cf84d92a625..372e47ce6a17 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -777,7 +777,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { task: RunningTaskInfo, withWallpaper: Boolean = false, ): TransitionInfo = - TransitionInfo(WindowManager.TRANSIT_CLOSE, 0 /* flags */).apply { + TransitionInfo(WindowManager.TRANSIT_CLOSE, /* flags= */ 0).apply { addChange( TransitionInfo.Change(mock(), closingTaskLeash).apply { mode = changeMode diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index e032616e7d43..da27c08920dc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -1267,7 +1267,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Set task as systemUI package val systemUIPackageName = context.resources.getString(com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") val task = setUpFullscreenTask().apply { baseActivity = baseComponent @@ -1284,7 +1284,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Set task as systemUI package val systemUIPackageName = context.resources.getString(com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") val task = setUpFullscreenTask().apply { baseActivity = baseComponent @@ -1757,12 +1757,12 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - val wallpaperChange = - hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() } - assertThat(wallpaperChange).isNotNull() - assertThat(wallpaperChange!!.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) - } + val wallpaperChange = + getLatestWct(type = TRANSIT_CHANGE).hierarchyOps.find { op -> + op.container == wallpaperToken.asBinder() + } + assertNotNull(wallpaperChange) + assertThat(wallpaperChange.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK) } @Test @@ -1792,15 +1792,13 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - val taskChange = changes[task.token.asBinder()] - assertThat(taskChange).isNotNull() - // To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin - // to the right margin and the ratio of the top margin to bottom margin are also - // preserved. - assertThat(taskChange!!.configuration.windowConfiguration.bounds) - .isEqualTo(Rect(240, 160, 560, 400)) - } + val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()] + assertNotNull(taskChange) + // To preserve DP size, pixel size is changed to 320x240. The ratio of the left margin + // to the right margin and the ratio of the top margin to bottom margin are also + // preserved. + assertThat(taskChange.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(240, 160, 560, 400)) } @Test @@ -1831,12 +1829,10 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - val taskChange = changes[task.token.asBinder()] - assertThat(taskChange).isNotNull() - assertThat(taskChange!!.configuration.windowConfiguration.bounds) - .isEqualTo(Rect(960, 480, 1280, 720)) - } + val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()] + assertNotNull(taskChange) + assertThat(taskChange.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(960, 480, 1280, 720)) } @Test @@ -1864,13 +1860,11 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - val taskChange = changes[task.token.asBinder()] - assertThat(taskChange).isNotNull() - // DP size is preserved. The window is centered in the destination display. - assertThat(taskChange!!.configuration.windowConfiguration.bounds) - .isEqualTo(Rect(320, 120, 960, 600)) - } + val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()] + assertNotNull(taskChange) + // DP size is preserved. The window is centered in the destination display. + assertThat(taskChange.configuration.windowConfiguration.bounds) + .isEqualTo(Rect(320, 120, 960, 600)) } @Test @@ -1903,14 +1897,12 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToNextDisplay(task.taskId) - with(getLatestWct(type = TRANSIT_CHANGE)) { - val taskChange = changes[task.token.asBinder()] - assertThat(taskChange).isNotNull() - assertThat(taskChange!!.configuration.windowConfiguration.bounds.left).isAtLeast(0) - assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0) - assertThat(taskChange.configuration.windowConfiguration.bounds.right).isAtMost(640) - assertThat(taskChange.configuration.windowConfiguration.bounds.bottom).isAtMost(480) - } + val taskChange = getLatestWct(type = TRANSIT_CHANGE).changes[task.token.asBinder()] + assertNotNull(taskChange) + assertThat(taskChange.configuration.windowConfiguration.bounds.left).isAtLeast(0) + assertThat(taskChange.configuration.windowConfiguration.bounds.top).isAtLeast(0) + assertThat(taskChange.configuration.windowConfiguration.bounds.right).isAtMost(640) + assertThat(taskChange.configuration.windowConfiguration.bounds.bottom).isAtMost(480) } @Test @@ -2722,7 +2714,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Set task as systemUI package val systemUIPackageName = context.resources.getString(com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") val task = setUpFreeformTask().apply { baseActivity = baseComponent @@ -2743,7 +2735,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Set task as systemUI package val systemUIPackageName = context.resources.getString(com.android.internal.R.string.config_systemUi) - val baseComponent = ComponentName(systemUIPackageName, /* class */ "") + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") val task = setUpFullscreenTask().apply { baseActivity = baseComponent @@ -3376,11 +3368,11 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, mockSurface, - Point(100, -100), /* position */ - PointF(200f, -200f), /* inputCoordinate */ - Rect(100, -100, 500, 1000), /* currentDragBounds */ - Rect(0, 50, 2000, 2000), /* validDragArea */ - Rect() /* dragStartBounds */, + position = Point(100, -100), + inputCoordinate = PointF(200f, -200f), + currentDragBounds = Rect(100, -100, 500, 1000), + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), motionEvent, desktopWindowDecoration, ) @@ -3415,11 +3407,11 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, mockSurface, - Point(100, 200), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - currentDragBounds, /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, + position = Point(100, 200), + inputCoordinate = PointF(200f, 300f), + currentDragBounds = currentDragBounds, + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), motionEvent, desktopWindowDecoration, ) @@ -3459,11 +3451,11 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - Rect(100, 50, 500, 1000), /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, + position = Point(100, 50), + inputCoordinate = PointF(200f, 300f), + currentDragBounds = Rect(100, 50, 500, 1000), + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), motionEvent, desktopWindowDecoration, ) @@ -3498,11 +3490,11 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ + position = Point(100, 50), + inputCoordinate = PointF(200f, 300f), currentDragBounds, - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), motionEvent, desktopWindowDecoration, ) @@ -3555,11 +3547,11 @@ class DesktopTasksControllerTest : ShellTestCase() { spyController.onDragPositioningEnd( task, mockSurface, - Point(100, 50), /* position */ - PointF(200f, 300f), /* inputCoordinate */ - currentDragBounds, /* currentDragBounds */ - Rect(0, 50, 2000, 2000) /* validDragArea */, - Rect() /* dragStartBounds */, + position = Point(100, 50), + inputCoordinate = PointF(200f, 300f), + currentDragBounds = currentDragBounds, + validDragArea = Rect(0, 50, 2000, 2000), + dragStartBounds = Rect(), motionEvent, desktopWindowDecoration, ) @@ -5053,7 +5045,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task: RunningTaskInfo?, @WindowManager.TransitionType type: Int = TRANSIT_OPEN, ): TransitionRequestInfo { - return TransitionRequestInfo(type, task, null /* remoteTransition */) + return TransitionRequestInfo(type, task, /* remoteTransition= */ null) } private companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 52602f22fd4b..c8214b3838e2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -193,10 +193,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter .getTransitionObserver() .onTransitionReady( - Binder() /* transition */, + /* transition= */ Binder(), TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() @@ -217,10 +217,10 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter .getTransitionObserver() .onTransitionReady( - taskTransition /* transition */, + /* transition= */ taskTransition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() @@ -242,8 +242,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() @@ -265,8 +265,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() @@ -287,8 +287,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() @@ -316,8 +316,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) }, - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() @@ -344,8 +344,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( newTransition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue() @@ -552,8 +552,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) @@ -584,8 +584,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( transition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition) @@ -617,8 +617,8 @@ class DesktopTasksLimiterTest : ShellTestCase() { .onTransitionReady( mergedTransition, TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(), - StubTransaction() /* startTransaction */, - StubTransaction(), /* finishTransaction */ + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), ) desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index d491d445458d..3cc30cb491b3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -305,7 +305,7 @@ class DesktopTasksTransitionObserverTest { type: Int = TRANSIT_TO_BACK, withWallpaper: Boolean = false, ): TransitionInfo { - return TransitionInfo(type, 0 /* flags */).apply { + return TransitionInfo(type, /* flags= */ 0).apply { addChange( Change(mock(), mock()).apply { mode = type @@ -331,7 +331,7 @@ class DesktopTasksTransitionObserverTest { task: RunningTaskInfo?, type: Int = TRANSIT_OPEN, ): TransitionInfo { - return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply { + return TransitionInfo(TRANSIT_OPEN, /* flags= */ 0).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_OPEN @@ -344,7 +344,7 @@ class DesktopTasksTransitionObserverTest { } private fun createCloseTransition(task: RunningTaskInfo?): TransitionInfo { - return TransitionInfo(TRANSIT_CLOSE, 0 /* flags */).apply { + return TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_CLOSE @@ -357,7 +357,7 @@ class DesktopTasksTransitionObserverTest { } private fun createToBackTransition(task: RunningTaskInfo?): TransitionInfo { - return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply { + return TransitionInfo(TRANSIT_TO_BACK, /* flags= */ 0).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_TO_BACK diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 2216d5452ce5..341df0299a97 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -677,7 +677,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo): TransitionInfo { - return TransitionInfo(type, 0 /* flags */).apply { + return TransitionInfo(type, /* flags= */ 0).apply { addChange( // Home. TransitionInfo.Change(mock(), homeTaskLeash).apply { parent = null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index fd3adabfd44b..32096645aea7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -40,6 +40,7 @@ import org.mockito.Mockito.mock /** * Tests for [GroupedTaskInfo] + * Build & Run: atest WMShellUnitTests:GroupedTaskInfoTest */ @SmallTest @RunWith(AndroidTestingRunner::class) @@ -47,7 +48,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { - assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) + assertThat(singleTaskGroupInfo().isBaseType(TYPE_FULLSCREEN)).isTrue() } @Test @@ -66,7 +67,7 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSplitTasks_hasCorrectType() { - assertThat(splitTasksGroupInfo().type).isEqualTo(TYPE_SPLIT) + assertThat(splitTasksGroupInfo().isBaseType(TYPE_SPLIT)).isTrue() } @Test @@ -87,8 +88,8 @@ class GroupedTaskInfoTest : ShellTestCase() { @Test fun testFreeformTasks_hasCorrectType() { - assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).type) - .isEqualTo(TYPE_FREEFORM) + assertThat(freeformTasksGroupInfo(freeformTaskIds = arrayOf(1)).isBaseType(TYPE_FREEFORM)) + .isTrue() } @Test @@ -111,83 +112,155 @@ class GroupedTaskInfoTest : ShellTestCase() { } @Test + fun testMixedWithFullscreenBase_hasCorrectType() { + assertThat(mixedTaskGroupInfoWithFullscreenBase().isBaseType(TYPE_FULLSCREEN)).isTrue() + } + + @Test + fun testMixedWithSplitBase_hasCorrectType() { + assertThat(mixedTaskGroupInfoWithSplitBase().isBaseType(TYPE_SPLIT)).isTrue() + } + + @Test + fun testMixedWithFreeformBase_hasCorrectType() { + assertThat(mixedTaskGroupInfoWithFreeformBase().isBaseType(TYPE_FREEFORM)).isTrue() + } + + @Test + fun testMixed_disallowEmptyMixed() { + assertThrows(IllegalArgumentException::class.java) { + GroupedTaskInfo.forMixed(listOf()) + } + } + + @Test + fun testMixed_disallowNestedMixed() { + assertThrows(IllegalArgumentException::class.java) { + GroupedTaskInfo.forMixed(listOf( + GroupedTaskInfo.forMixed(listOf(singleTaskGroupInfo())))) + } + } + + @Test + fun testMixed_disallowNonMixedAccessors() { + val mixed = mixedTaskGroupInfoWithFullscreenBase() + assertThrows(IllegalStateException::class.java) { + mixed.taskInfo1 + } + assertThrows(IllegalStateException::class.java) { + mixed.taskInfo2 + } + assertThrows(IllegalStateException::class.java) { + mixed.splitBounds + } + assertThrows(IllegalStateException::class.java) { + mixed.minimizedTaskIds + } + } + + @Test fun testParcelling_singleTask() { - val recentTaskInfo = singleTaskGroupInfo() + val taskInfo = singleTaskGroupInfo() val parcel = Parcel.obtain() - recentTaskInfo.writeToParcel(parcel, 0) + taskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel: GroupedTaskInfo = + val taskInfoFromParcel: GroupedTaskInfo = GroupedTaskInfo.CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN) - assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) - assertThat(recentTaskInfoParcel.taskInfo2).isNull() + assertThat(taskInfoFromParcel.isBaseType(TYPE_FULLSCREEN)).isTrue() + assertThat(taskInfoFromParcel.taskInfo1.taskId).isEqualTo(1) + assertThat(taskInfoFromParcel.taskInfo2).isNull() } @Test fun testParcelling_splitTasks() { - val recentTaskInfo = splitTasksGroupInfo() + val taskInfo = splitTasksGroupInfo() val parcel = Parcel.obtain() - recentTaskInfo.writeToParcel(parcel, 0) + taskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel: GroupedTaskInfo = + val taskInfoFromParcel: GroupedTaskInfo = GroupedTaskInfo.CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT) - assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) - assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() - assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2) - assertThat(recentTaskInfoParcel.splitBounds).isNotNull() - assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50) + assertThat(taskInfoFromParcel.isBaseType(TYPE_SPLIT)).isTrue() + assertThat(taskInfoFromParcel.taskInfo1.taskId).isEqualTo(1) + assertThat(taskInfoFromParcel.taskInfo2).isNotNull() + assertThat(taskInfoFromParcel.taskInfo2!!.taskId).isEqualTo(2) + assertThat(taskInfoFromParcel.splitBounds).isNotNull() + assertThat(taskInfoFromParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50) } @Test fun testParcelling_freeformTasks() { - val recentTaskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3)) + val taskInfo = freeformTasksGroupInfo(freeformTaskIds = arrayOf(1, 2, 3)) val parcel = Parcel.obtain() - recentTaskInfo.writeToParcel(parcel, 0) + taskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel: GroupedTaskInfo = + val taskInfoFromParcel: GroupedTaskInfo = GroupedTaskInfo.CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) - assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3) + assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue() + assertThat(taskInfoFromParcel.taskInfoList).hasSize(3) // Only compare task ids val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( { it?.taskId }, "has taskId of" ) - assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator) - .containsExactly(1, 2, 3) + assertThat(taskInfoFromParcel.taskInfoList).comparingElementsUsing(taskIdComparator) + .containsExactly(1, 2, 3).inOrder() } @Test fun testParcelling_freeformTasks_minimizedTasks() { - val recentTaskInfo = freeformTasksGroupInfo( + val taskInfo = freeformTasksGroupInfo( freeformTaskIds = arrayOf(1, 2, 3), minimizedTaskIds = arrayOf(2)) val parcel = Parcel.obtain() - recentTaskInfo.writeToParcel(parcel, 0) + taskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel: GroupedTaskInfo = + val taskInfoFromParcel: GroupedTaskInfo = GroupedTaskInfo.CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) - assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) + assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue() + assertThat(taskInfoFromParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) } @Test - fun testGetTaskById_singleTasks() { + fun testParcelling_mixedTasks() { + val taskInfo = GroupedTaskInfo.forMixed(listOf( + freeformTasksGroupInfo(freeformTaskIds = arrayOf(4, 5, 6), + minimizedTaskIds = arrayOf(5)), + splitTasksGroupInfo(firstId = 2, secondId = 3), + singleTaskGroupInfo(id = 1))) + + val parcel = Parcel.obtain() + taskInfo.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + + // Read the object back from the parcel + val taskInfoFromParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) + assertThat(taskInfoFromParcel.isBaseType(TYPE_FREEFORM)).isTrue() + assertThat(taskInfoFromParcel.baseGroupedTask.minimizedTaskIds).isEqualTo( + arrayOf(5).toIntArray()) + for (i in 1..6) { + assertThat(taskInfoFromParcel.containsTask(i)).isTrue() + } + assertThat(taskInfoFromParcel.taskInfoList).hasSize(taskInfo.taskInfoList.size) + } + + @Test + fun testTaskProperties_singleTasks() { val task1 = createTaskInfo(id = 1234) val taskInfo = GroupedTaskInfo.forFullscreenTasks(task1) assertThat(taskInfo.getTaskById(1234)).isEqualTo(task1) assertThat(taskInfo.containsTask(1234)).isTrue() + assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1)) } @Test - fun testGetTaskById_multipleTasks() { + fun testTaskProperties_splitTasks() { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) @@ -198,6 +271,41 @@ class GroupedTaskInfoTest : ShellTestCase() { assertThat(taskInfo.getTaskById(2)).isEqualTo(task2) assertThat(taskInfo.containsTask(1)).isTrue() assertThat(taskInfo.containsTask(2)).isTrue() + assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1, task2)) + } + + @Test + fun testTaskProperties_freeformTasks() { + val task1 = createTaskInfo(id = 1) + val task2 = createTaskInfo(id = 2) + + val taskInfo = GroupedTaskInfo.forFreeformTasks(listOf(task1, task2), setOf()) + + assertThat(taskInfo.getTaskById(1)).isEqualTo(task1) + assertThat(taskInfo.getTaskById(2)).isEqualTo(task2) + assertThat(taskInfo.containsTask(1)).isTrue() + assertThat(taskInfo.containsTask(2)).isTrue() + assertThat(taskInfo.taskInfoList).isEqualTo(listOf(task1, task2)) + } + + @Test + fun testTaskProperties_mixedTasks() { + val task1 = createTaskInfo(id = 1) + val task2 = createTaskInfo(id = 2) + val task3 = createTaskInfo(id = 3) + val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) + + val splitTasks = GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) + val fullscreenTasks = GroupedTaskInfo.forFullscreenTasks(task3) + val mixedTasks = GroupedTaskInfo.forMixed(listOf(splitTasks, fullscreenTasks)) + + assertThat(mixedTasks.getTaskById(1)).isEqualTo(task1) + assertThat(mixedTasks.getTaskById(2)).isEqualTo(task2) + assertThat(mixedTasks.getTaskById(3)).isEqualTo(task3) + assertThat(mixedTasks.containsTask(1)).isTrue() + assertThat(mixedTasks.containsTask(2)).isTrue() + assertThat(mixedTasks.containsTask(3)).isTrue() + assertThat(mixedTasks.taskInfoList).isEqualTo(listOf(task1, task2, task3)) } private fun createTaskInfo(id: Int) = ActivityManager.RecentTaskInfo().apply { @@ -205,14 +313,14 @@ class GroupedTaskInfoTest : ShellTestCase() { token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } - private fun singleTaskGroupInfo(): GroupedTaskInfo { - val task = createTaskInfo(id = 1) + private fun singleTaskGroupInfo(id: Int = 1): GroupedTaskInfo { + val task = createTaskInfo(id) return GroupedTaskInfo.forFullscreenTasks(task) } - private fun splitTasksGroupInfo(): GroupedTaskInfo { - val task1 = createTaskInfo(id = 1) - val task2 = createTaskInfo(id = 2) + private fun splitTasksGroupInfo(firstId: Int = 1, secondId: Int = 2): GroupedTaskInfo { + val task1 = createTaskInfo(firstId) + val task2 = createTaskInfo(secondId) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) } @@ -225,4 +333,22 @@ class GroupedTaskInfoTest : ShellTestCase() { freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } + + private fun mixedTaskGroupInfoWithFullscreenBase(): GroupedTaskInfo { + return GroupedTaskInfo.forMixed(listOf( + singleTaskGroupInfo(id = 1), + singleTaskGroupInfo(id = 2))) + } + + private fun mixedTaskGroupInfoWithSplitBase(): GroupedTaskInfo { + return GroupedTaskInfo.forMixed(listOf( + splitTasksGroupInfo(firstId = 2, secondId = 3), + singleTaskGroupInfo(id = 1))) + } + + private fun mixedTaskGroupInfoWithFreeformBase(): GroupedTaskInfo { + return GroupedTaskInfo.forMixed(listOf( + freeformTasksGroupInfo(freeformTaskIds = arrayOf(2, 3, 4)), + singleTaskGroupInfo(id = 1))) + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 22b45e8c63af..7e5d6ce38c5a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -24,6 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; +import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM; +import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN; +import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; import static org.junit.Assert.assertEquals; @@ -346,9 +349,9 @@ public class RecentTasksControllerTest extends ShellTestCase { GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); + assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM)); + assertTrue(singleGroup1.isBaseType(TYPE_FULLSCREEN)); + assertTrue(singleGroup2.isBaseType(TYPE_FULLSCREEN)); // Check freeform group entries assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); @@ -385,9 +388,9 @@ public class RecentTasksControllerTest extends ShellTestCase { GroupedTaskInfo singleGroup = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType()); - assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType()); + assertTrue(splitGroup.isBaseType(TYPE_SPLIT)); + assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM)); + assertTrue(singleGroup.isBaseType(TYPE_FULLSCREEN)); // Check freeform group entries assertEquals(t3, freeformGroup.getTaskInfoList().get(0)); @@ -420,10 +423,10 @@ public class RecentTasksControllerTest extends ShellTestCase { // Expect no grouping of tasks assertEquals(4, recentTasks.size()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType()); + assertTrue(recentTasks.get(0).isBaseType(TYPE_FULLSCREEN)); + assertTrue(recentTasks.get(1).isBaseType(TYPE_FULLSCREEN)); + assertTrue(recentTasks.get(2).isBaseType(TYPE_FULLSCREEN)); + assertTrue(recentTasks.get(3).isBaseType(TYPE_FULLSCREEN)); assertEquals(t1, recentTasks.get(0).getTaskInfo1()); assertEquals(t2, recentTasks.get(1).getTaskInfo1()); @@ -457,9 +460,9 @@ public class RecentTasksControllerTest extends ShellTestCase { GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); - assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); + assertTrue(freeformGroup.isBaseType(TYPE_FREEFORM)); + assertTrue(singleGroup1.isBaseType(TYPE_FULLSCREEN)); + assertTrue(singleGroup2.isBaseType(TYPE_FULLSCREEN)); // Check freeform group entries assertEquals(3, freeformGroup.getTaskInfoList().size()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 7dac0859b7e9..6b02aeffd42a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; @@ -1176,7 +1175,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void webUriLink_webUriLinkUsedWhenWhenAvailable() { + public void sessionTransferUri_sessionTransferUriUsedWhenWhenAvailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, @@ -1188,7 +1187,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void webUriLink_webUriLinkUsedWhenSessionTransferUriUnavailable() { + public void webUri_webUriUsedWhenSessionTransferUriUnavailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */, @@ -1200,7 +1199,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB) - public void genericLink_genericLinkUsedWhenCapturedLinkAndWebUriUnavailable() { + public void genericLink_genericLinkUsedWhenCapturedLinkAndAssistContentUriUnavailable() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); final DesktopModeWindowDecoration decor = createWindowDecoration( taskInfo, null /* captured link */, null /* web uri */, @@ -1490,7 +1489,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { taskInfo.capturedLink = capturedLink; taskInfo.capturedLinkTimestamp = System.currentTimeMillis(); mAssistContent.setWebUri(webUri); - mAssistContent.getExtras().putObject(EXTRA_SESSION_TRANSFER_WEB_URI, sessionTransferUri); + mAssistContent.setSessionTransferUri(sessionTransferUri); final String genericLinkString = genericLink == null ? null : genericLink.toString(); doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any()); // Relayout to set captured link diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index dbb891455ddd..e693fcfd3918 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -162,10 +162,13 @@ const std::string& ApkAssets::GetDebugName() const { return assets_provider_->GetDebugName(); } -bool ApkAssets::IsUpToDate() const { +UpToDate ApkAssets::IsUpToDate() const { // Loaders are invalidated by the app, not the system, so assume they are up to date. - return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate()) - && assets_provider_->IsUpToDate()); + if (IsLoader()) { + return UpToDate::Always; + } + const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always; + return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); }); } } // namespace android diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index 2d3c06506a1f..11b12eb030a6 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -24,9 +24,8 @@ #include <ziparchive/zip_archive.h> namespace android { -namespace { -constexpr const char* kEmptyDebugString = "<empty>"; -} // namespace + +static constexpr std::string_view kEmptyDebugString = "<empty>"; std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode, bool* file_exists) const { @@ -86,11 +85,9 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const { } ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path, - package_property_t flags, time_t last_mod_time) - : zip_handle_(handle), - name_(std::move(path)), - flags_(flags), - last_mod_time_(last_mod_time) {} + package_property_t flags, ModDate last_mod_time) + : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) { +} std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, package_property_t flags, @@ -104,10 +101,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. - if (!isReadonlyFilesystem(path.c_str())) { - if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) { + if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) { + if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -116,7 +113,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, } return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime)); + new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date)); } std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, @@ -137,10 +134,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. if (!isReadonlyFilesystem(released_fd)) { - if (fstat(released_fd, &sb) < 0) { + if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -150,7 +147,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider( - handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime)); + handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date)); } std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, @@ -282,21 +279,16 @@ const std::string& ZipAssetsProvider::GetDebugName() const { return name_.GetDebugName(); } -bool ZipAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; - } - struct stat sb{}; - if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) { - // If fstat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; +UpToDate ZipAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get()))); } -DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time) - : dir_(std::move(path)), last_mod_time_(last_mod_time) {} +DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time) + : dir_(std::move(path)), last_mod_time_(last_mod_time) { +} std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) { struct stat sb; @@ -317,7 +309,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st const bool isReadonly = isReadonlyFilesystem(path.c_str()); return std::unique_ptr<DirectoryAssetsProvider>( - new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime)); + new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb))); } std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path, @@ -346,17 +338,11 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { return dir_; } -bool DirectoryAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; - } - struct stat sb; - if (stat(dir_.c_str(), &sb) < 0) { - // If stat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; +UpToDate DirectoryAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(dir_.c_str())); } MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary, @@ -369,8 +355,14 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create( std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) { - if (primary == nullptr || secondary == nullptr) { - return nullptr; + if (primary == nullptr && secondary == nullptr) { + return EmptyAssetsProvider::Create(); + } + if (!primary) { + return secondary; + } + if (!secondary) { + return primary; } return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary), std::move(secondary))); @@ -397,8 +389,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const { return debug_name_; } -bool MultiAssetsProvider::IsUpToDate() const { - return primary_->IsUpToDate() && secondary_->IsUpToDate(); +UpToDate MultiAssetsProvider::IsUpToDate() const { + return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); }); } EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) : @@ -438,12 +430,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const { if (path_.has_value()) { return *path_; } - const static std::string kEmpty = kEmptyDebugString; + constexpr static std::string kEmpty{kEmptyDebugString}; return kEmpty; } -bool EmptyAssetsProvider::IsUpToDate() const { - return true; +UpToDate EmptyAssetsProvider::IsUpToDate() const { + return UpToDate::Always; } } // namespace android diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 3ecd82b074a1..262e7df185b7 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -22,9 +22,10 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "android-base/utf8.h" -#include "androidfw/misc.h" +#include "androidfw/AssetManager.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" +#include "androidfw/misc.h" #include "utils/ByteOrder.h" #include "utils/Trace.h" @@ -268,11 +269,16 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head configurations_(configs), overlay_entries_(overlay_entries), string_pool_(std::move(string_pool)), - idmap_fd_( - android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)), overlay_apk_path_(overlay_apk_path), target_apk_path_(target_apk_path), - idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) { + idmap_last_mod_time_(kInvalidModDate) { + if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) || + !(target_apk_path_ == AssetManager::TARGET_APK_PATH || + isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) { + idmap_fd_.reset( + android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)); + idmap_last_mod_time_ = getFileModDate(idmap_fd_); + } } std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { @@ -381,8 +387,11 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path)); } -bool LoadedIdmap::IsUpToDate() const { - return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()); +UpToDate LoadedIdmap::IsUpToDate() const { + if (idmap_last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; + } + return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get())); } } // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index de9991a8be5e..a8eb062a2ece 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -152,12 +152,11 @@ static void fill9patchOffsets(Res_png_9patch* patch) { patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t)); } -void Res_value::copyFrom_dtoh(const Res_value& src) -{ - size = dtohs(src.size); - res0 = src.res0; - dataType = src.dataType; - data = dtohl(src.data); +void Res_value::copyFrom_dtoh_slow(const Res_value& src) { + size = dtohs(src.size); + res0 = src.res0; + dataType = src.dataType; + data = dtohl(src.data); } void Res_png_9patch::deviceToFile() @@ -2031,16 +2030,6 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const // -------------------------------------------------------------------- // -------------------------------------------------------------------- -void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) { - const size_t size = dtohl(o.size); - if (size >= sizeof(ResTable_config)) { - *this = o; - } else { - memcpy(this, &o, size); - memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); - } -} - /* static */ size_t unpackLanguageOrRegion(const char in[2], const char base, char out[4]) { if (in[0] & 0x80) { @@ -2105,34 +2094,33 @@ size_t ResTable_config::unpackRegion(char region[4]) const { return unpackLanguageOrRegion(this->country, '0', region); } - -void ResTable_config::copyFromDtoH(const ResTable_config& o) { - copyFromDeviceNoSwap(o); - size = sizeof(ResTable_config); - mcc = dtohs(mcc); - mnc = dtohs(mnc); - density = dtohs(density); - screenWidth = dtohs(screenWidth); - screenHeight = dtohs(screenHeight); - sdkVersion = dtohs(sdkVersion); - minorVersion = dtohs(minorVersion); - smallestScreenWidthDp = dtohs(smallestScreenWidthDp); - screenWidthDp = dtohs(screenWidthDp); - screenHeightDp = dtohs(screenHeightDp); -} - -void ResTable_config::swapHtoD() { - size = htodl(size); - mcc = htods(mcc); - mnc = htods(mnc); - density = htods(density); - screenWidth = htods(screenWidth); - screenHeight = htods(screenHeight); - sdkVersion = htods(sdkVersion); - minorVersion = htods(minorVersion); - smallestScreenWidthDp = htods(smallestScreenWidthDp); - screenWidthDp = htods(screenWidthDp); - screenHeightDp = htods(screenHeightDp); +void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) { + copyFromDeviceNoSwap(o); + size = sizeof(ResTable_config); + mcc = dtohs(mcc); + mnc = dtohs(mnc); + density = dtohs(density); + screenWidth = dtohs(screenWidth); + screenHeight = dtohs(screenHeight); + sdkVersion = dtohs(sdkVersion); + minorVersion = dtohs(minorVersion); + smallestScreenWidthDp = dtohs(smallestScreenWidthDp); + screenWidthDp = dtohs(screenWidthDp); + screenHeightDp = dtohs(screenHeightDp); +} + +void ResTable_config::swapHtoD_slow() { + size = htodl(size); + mcc = htods(mcc); + mnc = htods(mnc); + density = htods(density); + screenWidth = htods(screenWidth); + screenHeight = htods(screenHeight); + sdkVersion = htods(sdkVersion); + minorVersion = htods(minorVersion); + smallestScreenWidthDp = htods(smallestScreenWidthDp); + screenWidthDp = htods(screenWidthDp); + screenHeightDp = htods(screenHeightDp); } /* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) { @@ -2145,7 +2133,7 @@ void ResTable_config::swapHtoD() { // systems should happen very infrequently (if at all.) // The comparison code relies on memcmp low-level optimizations that make it // more efficient than strncmp. - const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; + static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index be55fe8b4bb6..86c459fb4647 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -32,13 +32,18 @@ namespace android { namespace util { void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) { - char buf[5]; - while (*src && len != 0) { - char16_t c = static_cast<char16_t>(dtohs(*src)); - utf16_to_utf8(&c, 1, buf, sizeof(buf)); - out->append(buf, strlen(buf)); - ++src; - --len; + static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + if constexpr (kDeviceEndiannessSame) { + *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)}); + } else { + char buf[5]; + while (*src && len != 0) { + char16_t c = static_cast<char16_t>(dtohs(*src)); + utf16_to_utf8(&c, 1, buf, sizeof(buf)); + out->append(buf, strlen(buf)); + ++src; + --len; + } } } @@ -63,8 +68,10 @@ std::string Utf16ToUtf8(StringPiece16 utf16) { } std::string utf8; - utf8.resize(utf8_length); - utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1); + utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) { + utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1); + return size; + }); return utf8; } diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 231808beb718..3f6f4661f2f7 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -116,7 +116,7 @@ class ApkAssets : public RefBase { return resources_asset_ != nullptr && resources_asset_->isAllocated(); } - bool IsUpToDate() const; + UpToDate IsUpToDate() const; // DANGER! // This is a destructive method that rips the assets provider out of ApkAssets object. diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index d33c325ff369..e3b3ae41f7f4 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROIDFW_ASSETSPROVIDER_H -#define ANDROIDFW_ASSETSPROVIDER_H +#pragma once #include <memory> #include <string> @@ -58,7 +57,7 @@ struct AssetsProvider { WARN_UNUSED virtual const std::string& GetDebugName() const = 0; // Returns whether the interface provides the most recent version of its files. - WARN_UNUSED virtual bool IsUpToDate() const = 0; + WARN_UNUSED virtual UpToDate IsUpToDate() const = 0; // Creates an Asset from a file on disk. static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); @@ -95,7 +94,7 @@ struct ZipAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const; ~ZipAssetsProvider() override = default; @@ -106,7 +105,7 @@ struct ZipAssetsProvider : public AssetsProvider { private: struct PathOrDebugName; ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags, - time_t last_mod_time); + ModDate last_mod_time); struct PathOrDebugName { static PathOrDebugName Path(std::string value) { @@ -135,7 +134,7 @@ struct ZipAssetsProvider : public AssetsProvider { std::unique_ptr<ZipArchive, ZipCloser> zip_handle_; PathOrDebugName name_; package_property_t flags_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a root directory. @@ -147,7 +146,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~DirectoryAssetsProvider() override = default; protected: @@ -156,23 +155,23 @@ struct DirectoryAssetsProvider : public AssetsProvider { bool* file_exists) const override; private: - explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time); + explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time); std::string dir_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a `primary` asset provider and falls back to supplying assets from the // `secondary` asset provider if the asset cannot be found in the `primary`. struct MultiAssetsProvider : public AssetsProvider { static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary, - std::unique_ptr<AssetsProvider>&& secondary); + std::unique_ptr<AssetsProvider>&& secondary = {}); bool ForEachFile(const std::string& root_path, base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~MultiAssetsProvider() override = default; protected: @@ -199,7 +198,7 @@ struct EmptyAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~EmptyAssetsProvider() override = default; protected: @@ -212,5 +211,3 @@ struct EmptyAssetsProvider : public AssetsProvider { }; } // namespace android - -#endif /* ANDROIDFW_ASSETSPROVIDER_H */ diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index ac75eb3bb98c..87f3c9df9a91 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef IDMAP_H_ -#define IDMAP_H_ +#pragma once #include <memory> #include <string> @@ -32,6 +31,31 @@ namespace android { +// An enum that tracks more states than just 'up to date' or 'not' for a resources container: +// there are several cases where we know for sure that the object can't change and won't get +// out of date. Reporting those states to the managed layer allows it to stop checking here +// completely, speeding up the cache lookups by dozens of milliseconds. +enum class UpToDate : int { False, True, Always }; + +// Combines two UpToDate values, and only accesses the second one if it matters to the result. +template <class Getter> +UpToDate combine(UpToDate first, Getter secondGetter) { + switch (first) { + case UpToDate::False: + return UpToDate::False; + case UpToDate::True: { + const auto second = secondGetter(); + return second == UpToDate::False ? UpToDate::False : UpToDate::True; + } + case UpToDate::Always: + return secondGetter(); + } +} + +inline UpToDate fromBool(bool value) { + return value ? UpToDate::True : UpToDate::False; +} + class LoadedIdmap; class IdmapResMap; struct Idmap_header; @@ -196,7 +220,7 @@ class LoadedIdmap { // Returns whether the idmap file on disk has not been modified since the construction of this // LoadedIdmap. - bool IsUpToDate() const; + UpToDate IsUpToDate() const; protected: // Exposed as protected so that tests can subclass and mock this class out. @@ -231,5 +255,3 @@ class LoadedIdmap { }; } // namespace android - -#endif // IDMAP_H_ diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index e330410ed1a0..819fe4b38c87 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -47,6 +47,8 @@ namespace android { +constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + constexpr const uint32_t kIdmapMagic = 0x504D4449u; constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au; @@ -408,7 +410,16 @@ struct Res_value typedef uint32_t data_type; data_type data; - void copyFrom_dtoh(const Res_value& src); + void copyFrom_dtoh(const Res_value& src) { + if constexpr (kDeviceEndiannessSame) { + *this = src; + } else { + copyFrom_dtoh_slow(src); + } + } + + private: + void copyFrom_dtoh_slow(const Res_value& src); }; /** @@ -1254,11 +1265,32 @@ struct ResTable_config // Varies in length from 3 to 8 chars. Zero-filled value. char localeNumberingSystem[8]; - void copyFromDeviceNoSwap(const ResTable_config& o); - - void copyFromDtoH(const ResTable_config& o); - - void swapHtoD(); + void copyFromDeviceNoSwap(const ResTable_config& o) { + const auto o_size = dtohl(o.size); + if (o_size >= sizeof(ResTable_config)) [[likely]] { + *this = o; + } else { + memcpy(this, &o, o_size); + memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size); + } + this->size = sizeof(*this); + } + + void copyFromDtoH(const ResTable_config& o) { + if constexpr (kDeviceEndiannessSame) { + copyFromDeviceNoSwap(o); + } else { + copyFromDtoH_slow(o); + } + } + + void swapHtoD() { + if constexpr (kDeviceEndiannessSame) { + ; // noop + } else { + swapHtoD_slow(); + } + } int compare(const ResTable_config& o) const; int compareLogical(const ResTable_config& o) const; @@ -1384,6 +1416,10 @@ struct ResTable_config bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const; String8 toString() const; + + private: + void copyFromDtoH_slow(const ResTable_config& o); + void swapHtoD_slow(); }; /** diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index c9ba8a01a5e9..d8ca64a174a2 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -15,6 +15,7 @@ */ #pragma once +#include <sys/stat.h> #include <time.h> // @@ -64,10 +65,15 @@ ModDate getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ ModDate getFileModDate(int fd); +// Extract the modification date from the stat structure. +ModDate getModDate(const struct ::stat& st); + // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); +bool isKnownWritablePath(const char* path); + } // namespace android // Whoever uses getFileModDate() will need this as well diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 32f3624a3aee..26eb320805c9 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -16,10 +16,10 @@ #define LOG_TAG "misc" -// -// Miscellaneous utility functions. -// -#include <androidfw/misc.h> +#include "androidfw/misc.h" + +#include <errno.h> +#include <sys/stat.h> #include "android-base/logging.h" @@ -28,9 +28,7 @@ #include <sys/vfs.h> #endif // __linux__ -#include <errno.h> -#include <sys/stat.h> - +#include <array> #include <cstdio> #include <cstring> #include <tuple> @@ -40,28 +38,26 @@ namespace android { /* * Get a file's type. */ -FileType getFileType(const char* fileName) -{ - struct stat sb; - - if (stat(fileName, &sb) < 0) { - if (errno == ENOENT || errno == ENOTDIR) - return kFileTypeNonexistent; - else { - PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; - return kFileTypeUnknown; - } - } else { - if (S_ISREG(sb.st_mode)) - return kFileTypeRegular; - else if (S_ISDIR(sb.st_mode)) - return kFileTypeDirectory; - else if (S_ISCHR(sb.st_mode)) - return kFileTypeCharDev; - else if (S_ISBLK(sb.st_mode)) - return kFileTypeBlockDev; - else if (S_ISFIFO(sb.st_mode)) - return kFileTypeFifo; +FileType getFileType(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return kFileTypeNonexistent; + else { + PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; + return kFileTypeUnknown; + } + } else { + if (S_ISREG(sb.st_mode)) + return kFileTypeRegular; + else if (S_ISDIR(sb.st_mode)) + return kFileTypeDirectory; + else if (S_ISCHR(sb.st_mode)) + return kFileTypeCharDev; + else if (S_ISBLK(sb.st_mode)) + return kFileTypeBlockDev; + else if (S_ISFIFO(sb.st_mode)) + return kFileTypeFifo; #if defined(S_ISLNK) else if (S_ISLNK(sb.st_mode)) return kFileTypeSymlink; @@ -75,7 +71,7 @@ FileType getFileType(const char* fileName) } } -static ModDate getModDate(const struct stat& st) { +ModDate getModDate(const struct stat& st) { #ifdef _WIN32 return st.st_mtime; #elif defined(__APPLE__) @@ -113,8 +109,14 @@ bool isReadonlyFilesystem(const char*) { bool isReadonlyFilesystem(int) { return false; } +bool isKnownWritablePath(const char*) { + return false; +} #else // __linux__ bool isReadonlyFilesystem(const char* path) { + if (isKnownWritablePath(path)) { + return false; + } struct statfs sfs; if (::statfs(path, &sfs)) { PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed"; @@ -131,6 +133,13 @@ bool isReadonlyFilesystem(int fd) { } return (sfs.f_flags & ST_RDONLY) != 0; } + +bool isKnownWritablePath(const char* path) { + // We know that all paths in /data/ are writable. + static constexpr char kRwPrefix[] = "/data/"; + return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0; +} + #endif // __linux__ } // namespace android diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index cb2e56f5f5e4..22b9e69500d9 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -218,10 +218,11 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { auto apk_assets = ApkAssets::LoadOverlay(temp_file.path); ASSERT_NE(nullptr, apk_assets); - ASSERT_TRUE(apk_assets->IsUpToDate()); + ASSERT_TRUE(apk_assets->IsOverlay()); + ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate()); unlink(temp_file.path); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); const auto sleep_duration = std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); @@ -230,7 +231,27 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { base::WriteStringToFile("hello", temp_file.path); std::this_thread::sleep_for(sleep_duration); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); +} + +TEST(IdmapTestUpToDate, Combine) { + ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] { + ADD_FAILURE(); // Shouldn't get called at all. + return UpToDate::False; + })); + + ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; })); + + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; })); + + ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; })); +} + +TEST(IdmapTestUpToDate, FromBool) { + ASSERT_EQ(UpToDate::False, fromBool(false)); + ASSERT_EQ(UpToDate::True, fromBool(true)); } } // namespace diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 36f62da651db..c9625c405faa 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -5866,19 +5866,31 @@ final public class MediaCodec { @NonNull MediaCodec codec, @NonNull MediaFormat format); /** - * Called when the metrics for this codec have been flushed due to the - * start of a new subsession. + * Called when the metrics for this codec have been flushed "mid-stream" + * due to the start of a new subsession during execution. * <p> - * This can happen when the codec is reconfigured after stop(), or - * mid-stream e.g. if the video size changes. When this happens, the - * metrics for the previous subsession are flushed, and - * {@link MediaCodec#getMetrics} will return the metrics for the - * new subsession. This happens just before the {@link Callback#onOutputFormatChanged} + * A new codec subsession normally starts when the codec is reconfigured + * after stop(), but it can also happen mid-stream e.g. if the video size + * changes. When this happens, the metrics for the previous subsession + * are flushed, and {@link MediaCodec#getMetrics} will return the metrics + * for the new subsession. + * <p> + * For subsessions that begin due to a reconfiguration, the metrics for + * the prior subsession can be retrieved via {@link MediaCodec#getMetrics} + * prior to calling {@link #configure}. + * <p> + * When a new subsession begins "mid-stream", the metrics for the prior + * subsession are flushed just before the {@link Callback#onOutputFormatChanged} * event, so this <b>optional</b> callback is provided to be able to * capture the final metrics for the previous subsession. * * @param codec The MediaCodec object. - * @param metrics The flushed metrics for this codec. + * @param metrics The flushed metrics for this codec. This is a + * {@link PersistableBundle} containing the set of + * attributes and values available for the media being + * handled by this instance of MediaCodec. The attributes + * are described in {@link MetricsConstants}. Additional + * vendor-specific fields may also be present. */ @FlaggedApi(FLAG_SUBSESSION_METRICS) public void onMetricsFlushed( diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 678150b9f3a1..4c5efc1cf24b 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -18,6 +18,8 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.media.MediaCodec.BufferInfo; import android.os.Build; @@ -257,6 +259,8 @@ final public class MediaMuxer { */ private OutputFormat() {} /** @hide */ + @SuppressLint("UnflaggedApi") + @TestApi public static final int MUXER_OUTPUT_FIRST = 0; /** MPEG4 media file format*/ public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST; @@ -269,6 +273,8 @@ final public class MediaMuxer { /** Ogg media file format*/ public static final int MUXER_OUTPUT_OGG = MUXER_OUTPUT_FIRST + 4; /** @hide */ + @SuppressLint("UnflaggedApi") + @TestApi public static final int MUXER_OUTPUT_LAST = MUXER_OUTPUT_OGG; }; diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 3d0c4069e782..213bc0673da6 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -27,6 +27,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -475,6 +476,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int loadSoundModel(@NonNull SoundModel soundModel) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -518,6 +520,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params, @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) { Objects.requireNonNull(soundModelId); @@ -544,6 +547,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int stopRecognition(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -568,6 +572,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int unloadSoundModel(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -587,6 +592,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public boolean isRecognitionActive(@NonNull UUID soundModelId) { if (soundModelId == null || mSoundTriggerSession == null) { return false; @@ -624,6 +630,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int getModelState(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index f629c8802aef..b30b779b57b5 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -320,6 +320,9 @@ LIBANDROID { ASystemFontIterator_open; # introduced=29 ASystemFontIterator_close; # introduced=29 ASystemFontIterator_next; # introduced=29 + ASystemHealth_getMaxCpuHeadroomTidsSize; # introduced=36 + ASystemHealth_getCpuHeadroomCalculationWindowRange; # introduced=36 + ASystemHealth_getGpuHeadroomCalculationWindowRange; # introduced=36 ASystemHealth_getCpuHeadroom; # introduced=36 ASystemHealth_getGpuHeadroom; # introduced=36 ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36 diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 68c1983825a2..1e6a7b7f2810 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -859,7 +859,7 @@ void APerformanceHintManager::layersFromNativeSurfaces(ANativeWindow** windows, std::vector<ANativeWindow*> windowVec(windows, windows + numWindows); for (auto&& window : windowVec) { Surface* surface = static_cast<Surface*>(window); - if (Surface::isValid(surface)) { + if (surface != nullptr) { const sp<IBinder>& handle = surface->getSurfaceControlHandle(); if (handle != nullptr) { out.push_back(handle); diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp index f3fa9f6836d5..5c07ac7bfccc 100644 --- a/native/android/system_health.cpp +++ b/native/android/system_health.cpp @@ -31,26 +31,28 @@ namespace hal = aidl::android::hardware::power; struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {}; struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {}; -const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; -const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; -const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; -const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; -const int CPU_HEADROOM_MAX_TID_COUNT = 5; - struct ASystemHealthManager { public: static ASystemHealthManager* getInstance(); - ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager); + + ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager, + IHintManager::HintManagerClientData&& clientData); ASystemHealthManager() = delete; ~ASystemHealthManager(); int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom); int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom); int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis); int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis); + int getMaxCpuHeadroomTidsSize(size_t* outSize); + int getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis); + int getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis); private: static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager); std::shared_ptr<IHintManager> mHintManager; + IHintManager::HintManagerClientData mClientData; }; ASystemHealthManager* ASystemHealthManager::getInstance() { @@ -60,10 +62,11 @@ ASystemHealthManager* ASystemHealthManager::getInstance() { return instance; } -ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager) - : mHintManager(std::move(hintManager)) {} +ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager, + IHintManager::HintManagerClientData&& clientData) + : mHintManager(std::move(hintManager)), mClientData(clientData) {} -ASystemHealthManager::~ASystemHealthManager() {} +ASystemHealthManager::~ASystemHealthManager() = default; ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) { if (!hintManager) { @@ -74,20 +77,37 @@ ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__); return nullptr; } - return new ASystemHealthManager(hintManager); -} - -ASystemHealthManager* ASystemHealth_acquireManager() { - return ASystemHealthManager::getInstance(); + IHintManager::HintManagerClientData clientData; + ndk::ScopedAStatus ret = hintManager->getClientData(&clientData); + if (!ret.isOk()) { + ALOGE("%s: PerformanceHint service is not initialized %s", __FUNCTION__, ret.getMessage()); + return nullptr; + } + return new ASystemHealthManager(hintManager, std::move(clientData)); } int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) { + if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP; std::optional<hal::CpuHeadroomResult> res; ::ndk::ScopedAStatus ret; CpuHeadroomParamsInternal internalParams; if (!params) { ret = mHintManager->getCpuHeadroom(internalParams, &res); } else { + LOG_ALWAYS_FATAL_IF((int)params->tids.size() > mClientData.maxCpuHeadroomThreads, + "%s: tids size should not exceed %d", __FUNCTION__, + mClientData.maxCpuHeadroomThreads); + LOG_ALWAYS_FATAL_IF(params->calculationWindowMillis < + mClientData.supportInfo.headroom + .cpuMinCalculationWindowMillis || + params->calculationWindowMillis > + mClientData.supportInfo.headroom + .cpuMaxCalculationWindowMillis, + "%s: calculationWindowMillis should be in range [%d, %d] but got %d", + __FUNCTION__, + mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis, + mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis, + params->calculationWindowMillis); ret = mHintManager->getCpuHeadroom(*params, &res); } if (!ret.isOk()) { @@ -106,12 +126,24 @@ int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float } int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; std::optional<hal::GpuHeadroomResult> res; ::ndk::ScopedAStatus ret; GpuHeadroomParamsInternal internalParams; if (!params) { ret = mHintManager->getGpuHeadroom(internalParams, &res); } else { + LOG_ALWAYS_FATAL_IF(params->calculationWindowMillis < + mClientData.supportInfo.headroom + .gpuMinCalculationWindowMillis || + params->calculationWindowMillis > + mClientData.supportInfo.headroom + .gpuMaxCalculationWindowMillis, + "%s: calculationWindowMillis should be in range [%d, %d] but got %d", + __FUNCTION__, + mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis, + mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis, + params->calculationWindowMillis); ret = mHintManager->getGpuHeadroom(*params, &res); } if (!ret.isOk()) { @@ -128,6 +160,7 @@ int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float } int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) { + if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP; int64_t minIntervalMillis = 0; ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis); if (!ret.isOk()) { @@ -142,6 +175,7 @@ int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinInterva } int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; int64_t minIntervalMillis = 0; ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis); if (!ret.isOk()) { @@ -155,6 +189,57 @@ int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinInterva return OK; } +int ASystemHealthManager::getMaxCpuHeadroomTidsSize(size_t* outSize) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + *outSize = mClientData.maxCpuHeadroomThreads; + return OK; +} + +int ASystemHealthManager::getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP; + *outMinMillis = mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis; + *outMaxMillis = mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis; + return OK; +} + +int ASystemHealthManager::getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP; + *outMinMillis = mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis; + *outMaxMillis = mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis; + return OK; +} + +int ASystemHealth_getMaxCpuHeadroomTidsSize(size_t* _Nonnull outSize) { + LOG_ALWAYS_FATAL_IF(outSize == nullptr, "%s: outSize should not be null", __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getMaxCpuHeadroomTidsSize(outSize); +} + +int ASystemHealth_getCpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + LOG_ALWAYS_FATAL_IF(outMinMillis == nullptr, "%s: outMinMillis should not be null", + __FUNCTION__); + LOG_ALWAYS_FATAL_IF(outMaxMillis == nullptr, "%s: outMaxMillis should not be null", + __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getCpuHeadroomCalculationWindowRange(outMinMillis, outMaxMillis); +} + +int ASystemHealth_getGpuHeadroomCalculationWindowRange(int32_t* _Nonnull outMinMillis, + int32_t* _Nonnull outMaxMillis) { + LOG_ALWAYS_FATAL_IF(outMinMillis == nullptr, "%s: outMinMillis should not be null", + __FUNCTION__); + LOG_ALWAYS_FATAL_IF(outMaxMillis == nullptr, "%s: outMaxMillis should not be null", + __FUNCTION__); + auto manager = ASystemHealthManager::getInstance(); + if (manager == nullptr) return ENOTSUP; + return manager->getGpuHeadroomCalculationWindowRange(outMinMillis, outMaxMillis); +} + int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params, float* _Nonnull outHeadroom) { LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__); @@ -189,19 +274,15 @@ int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinInterv void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params, int windowMillis) { - LOG_ALWAYS_FATAL_IF(windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN || - windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX, - "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__, - windowMillis); + LOG_ALWAYS_FATAL_IF(windowMillis <= 0, "%s: windowMillis should be positive but got %d", + __FUNCTION__, windowMillis); params->calculationWindowMillis = windowMillis; } void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params, int windowMillis) { - LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN || - windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX, - "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__, - windowMillis); + LOG_ALWAYS_FATAL_IF(windowMillis <= 0, "%s: windowMillis should be positive but got %d", + __FUNCTION__, windowMillis); params->calculationWindowMillis = windowMillis; } @@ -214,13 +295,11 @@ int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull p } void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids, - int tidsSize) { + size_t tidsSize) { LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__); - LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5", - __FUNCTION__); params->tids.resize(tidsSize); params->tids.clear(); - for (int i = 0; i < tidsSize; ++i) { + for (int i = 0; i < (int)tidsSize; ++i) { LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d", tids[i]); params->tids[i] = tids[i]; @@ -269,10 +348,10 @@ AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() { return new AGpuHeadroomParams(); } -void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params) { +void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nullable params) { delete params; } -void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) { +void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nullable params) { delete params; } diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index f68fa1a89540..0fa92eab4c0a 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -161,6 +161,9 @@ public: clientDataIn, ::aidl::android::os::IHintManager::HintManagerClientData* _aidl_return), (override)); + MOCK_METHOD(ScopedAStatus, getClientData, + (::aidl::android::os::IHintManager::HintManagerClientData * _aidl_return), + (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (override)); }; @@ -602,6 +605,15 @@ TEST_F(PerformanceHintTest, TestASessionCreationConfig) { ASSERT_NE(config, nullptr); } +TEST_F(PerformanceHintTest, TestSessionCreationWithNullLayers) { + EXPECT_CALL(*mMockIHintManager, createHintSessionWithConfig(_, _, _, _, _)).Times(1); + auto&& config = configFromCreator( + {.tids = mTids, .nativeWindows = {nullptr}, .surfaceControls = {nullptr}}); + APerformanceHintManager* manager = createManager(); + auto&& session = createSessionUsingConfig(manager, config); + ASSERT_TRUE(session); +} + TEST_F(PerformanceHintTest, TestSupportObject) { // Disable GPU and Power Efficiency support to test partial enabling mClientData.supportInfo.sessionModes &= ~(1 << (int)hal::SessionMode::AUTO_GPU); diff --git a/packages/CredentialManager/tests/robotests/Android.bp b/packages/CredentialManager/tests/robotests/Android.bp index 27afaaa49fdd..01f403d3719d 100644 --- a/packages/CredentialManager/tests/robotests/Android.bp +++ b/packages/CredentialManager/tests/robotests/Android.bp @@ -53,7 +53,6 @@ android_robolectric_test { "android.test.mock.stubs.system", "truth", ], - upstream: true, java_resource_dirs: ["config"], instrumentation_for: "CredentialManagerRobo", } diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp index 589a3d6cc103..db3c36355dde 100644 --- a/packages/CredentialManager/wear/robotests/Android.bp +++ b/packages/CredentialManager/wear/robotests/Android.bp @@ -24,6 +24,5 @@ android_robolectric_test { "framework_graphics_flags_java_lib", ], java_resource_dirs: ["config"], - upstream: true, strict_mode: false, } diff --git a/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm b/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm new file mode 100644 index 000000000000..b384a2418ff2 --- /dev/null +++ b/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm @@ -0,0 +1,357 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Romanian keyboard layout. +# + +type OVERLAY + +map key 86 PLUS + +### ROW 1 + +key GRAVE { + label: '\u201e' + base: '\u201e' + shift: '\u201d' + ralt: '`' + ralt+shift: '~' +} + +key 1 { + label: '1' + base: '1' + shift: '!' + ralt: '\u0303' +} + +key 2 { + label: '2' + base: '2' + shift: '@' + ralt: '\u030C' +} + +key 3 { + label: '3' + base: '3' + shift: '#' + ralt: '\u0302' +} + +key 4 { + label: '4' + base: '4' + shift: '$' + ralt: '\u0306' +} + +key 5 { + label: '5' + base: '5' + shift: '%' + ralt: '\u030A' +} + +key 6 { + label: '6' + base: '6' + shift: '^' + ralt: '\u0328' +} + +key 7 { + label: '7' + base: '7' + shift: '&' + ralt: '\u0300' +} + +key 8 { + label: '8' + base: '8' + shift: '*' + ralt: '\u0307' +} + +key 9 { + label: '9' + base: '9' + shift: '(' + ralt: '\u0301' +} + +key 0 { + label: '0' + base: '0' + shift: ')' + ralt: '\u030B' +} + +key MINUS { + label: '-' + base: '-' + shift: '_' + ralt: '\u0308' + ralt+shift: '\u2013' +} + +key EQUALS { + label: '=' + base: '=' + shift: '+' + ralt: '\u0327' + ralt+shift: '\u00b1' +} + +### ROW 2 + +key Q { + label: 'Q' + base, capslock+shift: 'q' + shift, capslock: 'Q' +} + +key W { + label: 'W' + base, capslock+shift: 'w' + shift, capslock: 'W' +} + +key E { + label: 'E' + base, capslock+shift: 'e' + shift, capslock: 'E' + ralt: '\u20ac' +} + +key R { + label: 'R' + base, capslock+shift: 'r' + shift, capslock: 'R' +} + +key T { + label: 'T' + base, capslock+shift: 't' + shift, capslock: 'T' +} + +key Y { + label: 'Y' + base, capslock+shift: 'y' + shift, capslock: 'Y' +} + +key U { + label: 'U' + base, capslock+shift: 'u' + shift, capslock: 'U' +} + +key I { + label: 'I' + base, capslock+shift: 'i' + shift, capslock: 'I' +} + +key O { + label: 'O' + base, capslock+shift: 'o' + shift, capslock: 'O' +} + +key P { + label: 'P' + base, capslock+shift: 'p' + shift, capslock: 'P' + ralt: '\u00a7' +} + +key LEFT_BRACKET { + label: '\u0102' + base, capslock+shift: '\u0103' + shift, capslock: '\u0102' + ralt: '[' + ralt+shift: '{' +} + +key RIGHT_BRACKET { + label: '\u00ce' + base, capslock+shift: '\u00ee' + shift, capslock: '\u00ce' + ralt: ']' + ralt+shift: '}' +} + +### ROW 3 + +key A { + label: 'A' + base, capslock+shift: 'a' + shift, capslock: 'A' +} + +key S { + label: 'S' + base, capslock+shift: 's' + shift, capslock: 'S' + ralt: '\u00df' +} + +key D { + label: 'D' + base, capslock+shift: 'd' + shift, capslock: 'D' + ralt: '\u0111' + ralt+shift, ralt+capslock: '\u0110' + ralt+shift+capslock: '\u0111' +} + +key F { + label: 'F' + base, capslock+shift: 'f' + shift, capslock: 'F' +} + +key G { + label: 'G' + base, capslock+shift: 'g' + shift, capslock: 'G' +} + +key H { + label: 'H' + base, capslock+shift: 'h' + shift, capslock: 'H' +} + +key J { + label: 'J' + base, capslock+shift: 'j' + shift, capslock: 'J' +} + +key K { + label: 'K' + base, capslock+shift: 'k' + shift, capslock: 'K' +} + +key L { + label: 'L' + base, capslock+shift: 'l' + shift, capslock: 'L' + ralt: '\u0142' + ralt+shift, ralt+capslock: '\u0141' + ralt+shift+capslock: '\u0142' +} + +key SEMICOLON { + label: '\u0218' + base, capslock+shift: '\u0219' + shift, capslock: '\u0218' + ralt: ';' + ralt+shift: ':' +} + +key APOSTROPHE { + label: '\u021a' + base, capslock+shift: '\u021b' + shift, capslock: '\u021a' + ralt: '\'' + ralt+shift: '\u0022' +} + +key BACKSLASH { + label: '\u00c2' + base, capslock+shift: '\u00e2' + shift, capslock: '\u00c2' + ralt: '\\' + ralt+shift: '|' +} + +### ROW 4 + +key PLUS { + label: '\\' + base: '\\' + shift: '|' +} + +key Z { + label: 'Z' + base, capslock+shift: 'z' + shift, capslock: 'Z' +} + +key X { + label: 'X' + base, capslock+shift: 'x' + shift, capslock: 'X' +} + +key C { + label: 'C' + base, capslock+shift: 'c' + shift, capslock: 'C' + ralt: '\u00a9' +} + +key V { + label: 'V' + base, capslock+shift: 'v' + shift, capslock: 'V' +} + +key B { + label: 'B' + base, capslock+shift: 'b' + shift, capslock: 'B' +} + +key N { + label: 'N' + base, capslock+shift: 'n' + shift, capslock: 'N' +} + +key M { + label: 'M' + base, capslock+shift: 'm' + shift, capslock: 'M' +} + +key COMMA { + label: ',' + base: ',' + shift: ';' + ralt: '<' + ralt+shift: '\u00ab' +} + +key PERIOD { + label: '.' + base: '.' + shift: ':' + ralt: '>' + ralt+shift: '\u00bb' +} + +key SLASH { + label: '/' + base: '/' + shift: '?' +} diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml index 5a911256d9be..bd7cdc481524 100644 --- a/packages/InputDevices/res/values/strings.xml +++ b/packages/InputDevices/res/values/strings.xml @@ -164,4 +164,7 @@ <!-- Montenegrin (Cyrillic) keyboard layout label. [CHAR LIMIT=35] --> <string name="keyboard_layout_montenegrin_cyrillic">Montenegrin (Cyrillic)</string> + + <!-- Romanian keyboard layout label. [CHAR LIMIT=35] --> + <string name="keyboard_layout_romanian">Romanian</string> </resources> diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml index 93094890418d..9ce9a87a1f9f 100644 --- a/packages/InputDevices/res/xml/keyboard_layouts.xml +++ b/packages/InputDevices/res/xml/keyboard_layouts.xml @@ -360,4 +360,11 @@ android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic" android:keyboardLocale="cnr-Cyrl-ME" android:keyboardLayoutType="extended" /> + + <keyboard-layout + android:name="keyboard_layout_romanian" + android:label="@string/keyboard_layout_romanian" + android:keyboardLayout="@raw/keyboard_layout_romanian" + android:keyboardLocale="ro-Latn-RO" + android:keyboardLayoutType="qwerty" /> </keyboard-layouts> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java index b20117d78230..c99d37bb6ce6 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java @@ -19,6 +19,7 @@ package com.android.packageinstaller; import static android.Manifest.permission; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import android.app.Activity; import android.app.DialogFragment; @@ -53,6 +54,8 @@ public class UnarchiveActivity extends Activity { @Override public void onCreate(Bundle icicle) { + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + super.onCreate(null); int callingUid = getLaunchedFromUid(); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java index 42dd382b98bc..fbb0fa4d6a57 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java @@ -21,10 +21,14 @@ import android.app.Dialog; import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; +import android.widget.Button; public class UnarchiveFragment extends DialogFragment implements DialogInterface.OnClickListener { + private Dialog mDialog; + private Button mRestoreButton; + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String appTitle = getArguments().getString(UnarchiveActivity.APP_TITLE); @@ -40,7 +44,32 @@ public class UnarchiveFragment extends DialogFragment implements dialogBuilder.setPositiveButton(R.string.restore, this); dialogBuilder.setNegativeButton(android.R.string.cancel, this); - return dialogBuilder.create(); + mDialog = dialogBuilder.create(); + return mDialog; + } + + @Override + public void onStart() { + super.onStart(); + if (mDialog != null) { + mRestoreButton = ((AlertDialog) mDialog).getButton(DialogInterface.BUTTON_POSITIVE); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mRestoreButton != null) { + mRestoreButton.setEnabled(false); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mRestoreButton != null) { + mRestoreButton.setEnabled(true); + } } @Override diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp index 2e3b42de5b9d..6044eaba5f89 100644 --- a/packages/SettingsLib/DataStore/tests/Android.bp +++ b/packages/SettingsLib/DataStore/tests/Android.bp @@ -25,6 +25,5 @@ android_robolectric_test { java_resource_dirs: ["config"], instrumentation_for: "SettingsLibDataStoreShell", coverage_libs: ["SettingsLibDataStore"], - upstream: true, strict_mode: false, } diff --git a/packages/SettingsLib/Ipc/Android.bp b/packages/SettingsLib/Ipc/Android.bp index 2c7209a48bbd..bc5a9364279d 100644 --- a/packages/SettingsLib/Ipc/Android.bp +++ b/packages/SettingsLib/Ipc/Android.bp @@ -25,7 +25,7 @@ android_library { name: "SettingsLibIpc-testutils", srcs: ["testutils/**/*.kt"], static_libs: [ - "Robolectric_all-target_upstream", + "Robolectric_all-target", "SettingsLibIpc", "androidx.test.core", "flag-junit", diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp index 3e109f13ae7b..1661dfb2a86b 100644 --- a/packages/SettingsLib/SettingsTheme/Android.bp +++ b/packages/SettingsLib/SettingsTheme/Android.bp @@ -16,7 +16,6 @@ android_library { ], resource_dirs: ["res"], static_libs: [ - "aconfig_settingslib_exported_flags_java_lib", "androidx.preference_preference", "com.google.android.material_material", ], @@ -24,12 +23,12 @@ android_library { min_sdk_version: "21", apex_available: [ "//apex_available:platform", - "com.android.adservices", "com.android.cellbroadcast", "com.android.devicelock", "com.android.extservices", + "com.android.permission", + "com.android.adservices", "com.android.healthfitness", "com.android.mediaprovider", - "com.android.permission", ], } diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt index 8223eff1e024..74f5441f6760 100644 --- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsThemeHelper.kt @@ -18,7 +18,6 @@ package com.android.settingslib.widget import android.content.Context import android.os.Build -import com.android.settingslib.flags.Flags object SettingsThemeHelper { private const val IS_EXPRESSIVE_DESIGN_ENABLED = "is_expressive_design_enabled" @@ -50,8 +49,7 @@ object SettingsThemeHelper { expressiveThemeState = if ( (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) && - (getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false) || - Flags.isExpressiveDesignEnabled()) + getPropBoolean(context, IS_EXPRESSIVE_DESIGN_ENABLED, false) ) { ExpressiveThemeState.ENABLED } else { diff --git a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp index f6477e2f052a..dd6743b8595c 100644 --- a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp +++ b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp @@ -68,7 +68,6 @@ android_robolectric_test { "android.test.mock.stubs.system", "truth", ], - upstream: true, java_resource_dirs: ["config"], instrumentation_for: "SpaRoboApp", diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt new file mode 100644 index 000000000000..5b7e2a86135a --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.common + +import android.content.Context +import android.content.res.Resources +import android.icu.text.DecimalFormat +import android.icu.text.MeasureFormat +import android.icu.text.NumberFormat +import android.icu.text.UnicodeSet +import android.icu.text.UnicodeSetSpanner +import android.icu.util.Measure +import android.text.format.Formatter +import android.text.format.Formatter.RoundedBytesResult +import java.math.BigDecimal + +class BytesFormatter(resources: Resources) { + + enum class UseCase(val flag: Int) { + FileSize(Formatter.FLAG_SI_UNITS), + DataUsage(Formatter.FLAG_IEC_UNITS), + } + + data class Result(val number: String, val units: String) + + constructor(context: Context) : this(context.resources) + + private val locale = resources.configuration.locales[0] + + fun format(bytes: Long, useCase: UseCase): String { + val rounded = RoundedBytesResult.roundBytes(bytes, useCase.flag) + val numberFormatter = getNumberFormatter(rounded.fractionDigits) + return numberFormatter.formatRoundedBytesResult(rounded) + } + + fun formatWithUnits(bytes: Long, useCase: UseCase): Result { + val rounded = RoundedBytesResult.roundBytes(bytes, useCase.flag) + val numberFormatter = getNumberFormatter(rounded.fractionDigits) + val formattedString = numberFormatter.formatRoundedBytesResult(rounded) + val formattedNumber = numberFormatter.format(rounded.value) + return Result( + number = formattedNumber, + units = formattedString.removeFirst(formattedNumber), + ) + } + + private fun NumberFormat.formatRoundedBytesResult(rounded: RoundedBytesResult): String { + val measureFormatter = + MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.SHORT, this) + return measureFormatter.format(Measure(rounded.value, rounded.units)) + } + + private fun getNumberFormatter(fractionDigits: Int) = + NumberFormat.getInstance(locale).apply { + minimumFractionDigits = fractionDigits + maximumFractionDigits = fractionDigits + isGroupingUsed = false + if (this is DecimalFormat) { + setRoundingMode(BigDecimal.ROUND_HALF_UP) + } + } + + private companion object { + fun String.removeFirst(removed: String): String = + SPACES_AND_CONTROLS.trim(replaceFirst(removed, "")).toString() + + val SPACES_AND_CONTROLS = UnicodeSetSpanner(UnicodeSet("[[:Zs:][:Cf:]]").freeze()) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt new file mode 100644 index 000000000000..7220848eebff --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.common + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class BytesFormatterTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val formatter = BytesFormatter(context) + + @Test + fun `Zero bytes`() { + // Given a byte value of 0, the formatted output should be "0 byte" for both FileSize + // and DataUsage UseCases. This verifies special handling of zero values. + + val fileSizeResult = formatter.format(0, BytesFormatter.UseCase.FileSize) + assertThat(fileSizeResult).isEqualTo("0 byte") + + val dataUsageResult = formatter.format(0, BytesFormatter.UseCase.DataUsage) + assertThat(dataUsageResult).isEqualTo("0 byte") + } + + @Test + fun `Positive bytes`() { + // Given a positive byte value (e.g., 1000), the formatted output should be correctly + // displayed with appropriate units (e.g., '1.00 kB') for both UseCases. + + val fileSizeResult = formatter.format(1000, BytesFormatter.UseCase.FileSize) + assertThat(fileSizeResult).isEqualTo("1.00 kB") + + val dataUsageResult = formatter.format(1024, BytesFormatter.UseCase.DataUsage) + assertThat(dataUsageResult).isEqualTo("1.00 kB") + } + + @Test + fun `Large bytes`() { + // Given a very large byte value (e.g., Long.MAX_VALUE), the formatted output should be + // correctly displayed with the largest unit (e.g., 'PB') for both UseCases. + + val fileSizeResult = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.FileSize) + assertThat(fileSizeResult).isEqualTo("9223 PB") + + val dataUsageResult = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.DataUsage) + assertThat(dataUsageResult).isEqualTo("8192 PB") + } + + @Test + fun `Bytes requiring rounding`() { + // Given byte values that require rounding (e.g., 1512), the formatted output should be + // rounded to the appropriate number of decimal places (e.g., '1.51 kB'). + + val fileSizeResult = formatter.format(1512, BytesFormatter.UseCase.FileSize) + assertThat(fileSizeResult).isEqualTo("1.51 kB") + + val dataUsageResult = formatter.format(1512, BytesFormatter.UseCase.DataUsage) + assertThat(dataUsageResult).isEqualTo("1.48 kB") + } + + @Test + fun `FileSize UseCase`() { + // When the UseCase is FileSize, the correct units (byte, KB, kB, GB, TB, PB) should + // be used. + val values = + listOf( + 1L, + 1024L, + 1024L * 1024L, + 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L * 1024L * 1024L, + ) + val expectedUnits = listOf("byte", "kB", "MB", "GB", "TB", "PB", "PB") + + values.zip(expectedUnits).forEach { (value, expectedUnit) -> + val result = formatter.format(value, BytesFormatter.UseCase.FileSize) + assertThat(result).contains(expectedUnit) + } + } + + @Test + fun `DataUsage UseCase`() { + // When the UseCase is DataUsage, the correct units (byte, kB, MB, GB, TB, PB) should + // be used. + val values = + listOf( + 1L, + 1024L, + 1024L * 1024L, + 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L * 1024L, + 1024L * 1024L * 1024L * 1024L * 1024L * 1024L, + ) + val expectedUnits = listOf("byte", "kB", "MB", "GB", "TB", "PB", "PB") + + values.zip(expectedUnits).forEach { (value, expectedUnit) -> + val result = formatter.format(value, BytesFormatter.UseCase.DataUsage) + assertThat(result).contains(expectedUnit) + } + } + + @Test + fun `Fraction digits`() { + // The number of fraction digits in the output should be correctly determined based on + // the rounded byte value. + + assertThat(formatter.format(1500, BytesFormatter.UseCase.FileSize)).isEqualTo("1.50 kB") + assertThat(formatter.format(1050, BytesFormatter.UseCase.FileSize)).isEqualTo("1.05 kB") + assertThat(formatter.format(999, BytesFormatter.UseCase.FileSize)).isEqualTo("1.00 kB") + } + + @Test + fun `Rounding mode`() { + // The rounding mode used for formatting should be ROUND_HALF_UP. + + val result = formatter.format(1006, BytesFormatter.UseCase.FileSize) + + assertThat(result).isEqualTo("1.01 kB") // Ensure rounding mode is effective + } + + @Test + fun `Grouping separator`() { + // Grouping separators should not be used in the formatted output. + + val result = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.FileSize) + + assertThat(result).isEqualTo("9223 PB") + } + + @Test + fun `Format with units`() { + // Verify that the `formatWithUnits` method correctly formats the given bytes with the + // specified units. + + val resultByte = formatter.formatWithUnits(0, BytesFormatter.UseCase.FileSize) + assertThat(resultByte).isEqualTo(BytesFormatter.Result("0", "byte")) + + val resultKb = formatter.formatWithUnits(1000, BytesFormatter.UseCase.FileSize) + assertThat(resultKb).isEqualTo(BytesFormatter.Result("1.00", "kB")) + + val resultMb = formatter.formatWithUnits(479_999_999, BytesFormatter.UseCase.FileSize) + assertThat(resultMb).isEqualTo(BytesFormatter.Result("480", "MB")) + + val resultGb = formatter.formatWithUnits(20_100_000_000, BytesFormatter.UseCase.FileSize) + assertThat(resultGb).isEqualTo(BytesFormatter.Result("20.10", "GB")) + + val resultTb = + formatter.formatWithUnits(300_100_000_000_000, BytesFormatter.UseCase.FileSize) + assertThat(resultTb).isEqualTo(BytesFormatter.Result("300", "TB")) + + val resultPb = + formatter.formatWithUnits(1000_000_000_000_000, BytesFormatter.UseCase.FileSize) + assertThat(resultPb).isEqualTo(BytesFormatter.Result("1.00", "PB")) + } +} diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 71cf8f6ee53b..bbe08f254283 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -6,7 +6,6 @@ flag { namespace: "systemui" description: "Enable new status bar system icons" bug: "314812750" - is_exported: true } flag { @@ -14,7 +13,6 @@ flag { namespace: "bluetooth" description: "Displays the auto on toggle in the bluetooth QS tile dialog" bug: "316985153" - is_exported: true } flag { @@ -22,7 +20,6 @@ flag { namespace: "pixel_cross_device_control" description: "Gates the legacy le audio sharing UI." bug: "322295262" - is_exported: true } flag { @@ -30,7 +27,6 @@ flag { namespace: "pixel_cross_device_control" description: "Gates whether to enable LE audio sharing" bug: "323125723" - is_exported: true } flag { @@ -38,7 +34,6 @@ flag { namespace: "pixel_cross_device_control" description: "Gates whether to enable LE audio private broadcast sharing via QR code" bug: "323125723" - is_exported: true } flag { @@ -46,7 +41,6 @@ flag { namespace: "dck_framework" description: "Hide exclusively managed Bluetooth devices in BT settings menu." bug: "324475542" - is_exported: true } flag { @@ -54,7 +48,6 @@ flag { namespace: "bluetooth" description: "Enable setting preferred transport for Le Audio device" bug: "330581926" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -65,7 +58,6 @@ flag { namespace: "pixel_cross_device_control" description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header." bug: "328556903" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -76,7 +68,6 @@ flag { namespace: "cross_device_experiences" description: "Use bluetooth profile connection policy to determine spatial audio attributes" bug: "341005211" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -87,7 +78,6 @@ flag { namespace: "cross_device_experiences" description: "Gates whether to show separate volume bars during audio sharing" bug: "336716411" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -127,7 +117,6 @@ flag { namespace: "accessibility" description: "Changes the return value of HearingAidProfile.accessProfileEnabled() to true" bug: "356530795" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -138,7 +127,6 @@ flag { namespace: "cross_device_experiences" description: "Gates whether to enable fix for hysteresis mode" bug: "355222285" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -149,7 +137,6 @@ flag { namespace: "cross_device_experiences" description: "Gates whether to enable fix for member device active state sync on lea profile" bug: "364201289" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -160,7 +147,6 @@ flag { namespace: "cross_device_experiences" description: "Gates whether to enable audio sharing qs dialog improvement" bug: "360759048" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -171,7 +157,6 @@ flag { namespace: "cross_device_experiences" description: "Gates whether to enable audio sharing developer option" bug: "368401233" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -182,7 +167,6 @@ flag { namespace: "accessibility" description: "Enable the ambient volume control in device details and hearing devices dialog." bug: "357878944" - is_exported: true } flag { @@ -190,7 +174,6 @@ flag { namespace: "android_settings" description: "Enable the user consent prompt before writing sensitive preferences via service" bug: "378552675" - is_exported: true } flag { @@ -198,7 +181,6 @@ flag { namespace: "accessibility" description: "Enable the input routing control in device details and hearing devices dialog." bug: "349255906" - is_exported: true } flag { @@ -206,7 +188,6 @@ flag { namespace: "accessibility" description: "Enable the connection status report for a set of hearing device." bug: "357882387" - is_exported: true } flag { @@ -214,7 +195,6 @@ flag { namespace: "cross_device_experiences" description: "Do not show problem connecting message when Android Auto disconnect A2DP" bug: "381981752" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -225,7 +205,6 @@ flag { namespace: "cross_device_experiences" description: "Do not auto pick audio sharing fallback device in UI" bug: "383469911" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } @@ -236,16 +215,7 @@ flag { namespace: "cross_device_experiences" description: "UI changes for temporary bond devices in audio sharing." bug: "362859132" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } } - -flag { - name: "is_expressive_design_enabled" - namespace: "android_settings" - description: "enable expressive design in Settings" - bug: "386013400" - is_exported: true -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 145b62cd12b5..68e9fe703090 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -73,6 +73,10 @@ public class BluetoothUtils { private static final Set<Integer> SA_PROFILES = ImmutableSet.of( BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID); + private static final List<Integer> BLUETOOTH_DEVICE_CLASS_HEADSET = + List.of( + BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES, + BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET); private static final String TEMP_BOND_TYPE = "TEMP_BOND_TYPE"; private static final String TEMP_BOND_DEVICE_METADATA_VALUE = "le_audio_sharing"; @@ -390,6 +394,19 @@ public class BluetoothUtils { return false; } + /** Checks whether the bluetooth device is a headset. */ + public static boolean isHeadset(@NonNull BluetoothDevice bluetoothDevice) { + String deviceType = + BluetoothUtils.getStringMetaData( + bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); + if (!TextUtils.isEmpty(deviceType)) { + return BluetoothDevice.DEVICE_TYPE_HEADSET.equals(deviceType) + || BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.equals(deviceType); + } + BluetoothClass btClass = bluetoothDevice.getBluetoothClass(); + return btClass != null && BLUETOOTH_DEVICE_CLASS_HEADSET.contains(btClass.getDeviceClass()); + } + /** Create an Icon pointing to a drawable. */ public static IconCompat createIconWithDrawable(Drawable drawable) { Bitmap bitmap; diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index 81358ca168d0..117ca85c2761 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -65,7 +65,6 @@ android_robolectric_test { test_options: { timeout: 36000, }, - upstream: true, strict_mode: false, } @@ -100,10 +99,10 @@ java_library { plugins: [ "auto_value_plugin_1.9", "auto_value_builder_plugin_1.9", - "Robolectric_processor_upstream", + "Robolectric_processor", ], libs: [ - "Robolectric_all-target_upstream", + "Robolectric_all-target", "mockito-robolectric-prebuilt", "truth", ], diff --git a/packages/SettingsLib/tests/robotests/fragment/Android.bp b/packages/SettingsLib/tests/robotests/fragment/Android.bp index 3e67156af0c4..0214874979f3 100644 --- a/packages/SettingsLib/tests/robotests/fragment/Android.bp +++ b/packages/SettingsLib/tests/robotests/fragment/Android.bp @@ -28,13 +28,13 @@ java_library { //"-J-verbose", ], libs: [ - "Robolectric_all-target_upstream", + "Robolectric_all-target", "androidx.fragment_fragment", ], plugins: [ "auto_value_plugin_1.9", "auto_value_builder_plugin_1.9", - "Robolectric_processor_upstream", + "Robolectric_processor", ], } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index d49447f05011..cafe19ff9a9b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -80,7 +80,9 @@ public class BluetoothUtilsTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private CachedBluetoothDevice mCachedBluetoothDevice; - @Mock private BluetoothDevice mBluetoothDevice; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private BluetoothDevice mBluetoothDevice; + @Mock private AudioManager mAudioManager; @Mock private PackageManager mPackageManager; @Mock private LeAudioProfile mA2dpProfile; @@ -399,6 +401,38 @@ public class BluetoothUtilsTest { } @Test + public void isHeadset_metadataMatched_returnTrue() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)) + .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes()); + + assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isTrue(); + } + + @Test + public void isHeadset_metadataNotMatched_returnFalse() { + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)) + .thenReturn(BluetoothDevice.DEVICE_TYPE_CARKIT.getBytes()); + + assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isFalse(); + } + + @Test + public void isHeadset_btClassMatched_returnTrue() { + when(mBluetoothDevice.getBluetoothClass().getDeviceClass()) + .thenReturn(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); + + assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isTrue(); + } + + @Test + public void isHeadset_btClassNotMatched_returnFalse() { + when(mBluetoothDevice.getBluetoothClass().getDeviceClass()) + .thenReturn(BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER); + + assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isFalse(); + } + + @Test public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() { when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 5ddf005d9468..dafcc729b8f1 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -322,9 +322,6 @@ <!-- Whether vibrate icon is shown in the status bar by default. --> <integer name="def_statusBarVibrateIconEnabled">0</integer> - <!-- Whether predictive back animation is enabled by default. --> - <bool name="def_enable_back_animation">false</bool> - <!-- Whether wifi is always requested by default. --> <bool name="def_enable_wifi_always_requested">false</bool> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 5b4ee8bdb339..1f56f10cca7d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -109,6 +109,7 @@ public class SystemSettings { Settings.System.LOCALE_PREFERENCES, Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, Settings.System.MOUSE_SCROLLING_ACCELERATION, + Settings.System.MOUSE_SCROLLING_SPEED, Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED, Settings.System.TOUCHPAD_POINTER_SPEED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 0432eeacec4d..4d98a11bdfe7 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -227,6 +227,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_SCROLLING_ACCELERATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_POINTER_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.MOUSE_SCROLLING_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index a2cc008843a4..ef0bc3b100e0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -193,6 +193,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { "power_button_instantly_locks"; private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY = "pin_enhanced_privacy"; + private static final int NUM_LOCK_SETTINGS = 5; // Error messages for logging metrics. private static final String ERROR_COULD_NOT_READ_FROM_CURSOR = @@ -208,6 +209,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String ERROR_SKIPPED_DUE_TO_LARGE_SCREEN = "skipped_due_to_large_screen"; private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation"; + private static final String ERROR_IO_EXCEPTION = "io_exception"; + private static final String ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG = + "failed_to_restore_softap_config"; + private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES = + "failed_to_convert_network_policies"; + private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION = + "unknown_backup_serialization_version"; // Name of the temporary file we use during full backup/restore. This is @@ -794,29 +802,44 @@ public class SettingsBackupAgent extends BackupAgentHelper { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); + int backedUpSettingsCount = 0; try { out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO_ENABLED); out.writeUTF(ownerInfoEnabled ? "1" : "0"); + backedUpSettingsCount++; if (ownerInfo != null) { out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO); out.writeUTF(ownerInfo != null ? ownerInfo : ""); + backedUpSettingsCount++; } if (lockPatternUtils.isVisiblePatternEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED); out.writeUTF(visiblePatternEnabled ? "1" : "0"); + backedUpSettingsCount++; } if (lockPatternUtils.isPowerButtonInstantlyLocksEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS); out.writeUTF(powerButtonInstantlyLocks ? "1" : "0"); + backedUpSettingsCount++; } if (lockPatternUtils.isPinEnhancedPrivacyEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY); out.writeUTF(lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) ? "1" : "0"); + backedUpSettingsCount++; } // End marker out.writeUTF(""); out.flush(); + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_LOCK_SETTINGS, backedUpSettingsCount); + } } catch (IOException ioe) { + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_LOCK_SETTINGS, + NUM_LOCK_SETTINGS - backedUpSettingsCount, + ERROR_IO_EXCEPTION); + } } return baos.toByteArray(); } @@ -1162,6 +1185,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, nBytes); DataInputStream in = new DataInputStream(bais); + int restoredLockSettingsCount = 0; try { String key; // Read until empty string marker @@ -1187,9 +1211,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { lockPatternUtils.setPinEnhancedPrivacyEnabled("1".equals(value), userId); break; } + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestored(KEY_LOCK_SETTINGS, /* count= */ 1); + restoredLockSettingsCount++; + } + } in.close(); } catch (IOException ioe) { + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_LOCK_SETTINGS, + NUM_LOCK_SETTINGS - restoredLockSettingsCount, + ERROR_IO_EXCEPTION); + } } } @@ -1309,12 +1344,31 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiManager.restoreSupplicantBackupData(supplicant_bytes, ipconfig_bytes); } - private byte[] getSoftAPConfiguration() { - return mWifiManager.retrieveSoftApBackupData(); + @VisibleForTesting + byte[] getSoftAPConfiguration() { + byte[] data = mWifiManager.retrieveSoftApBackupData(); + if (areAgentMetricsEnabled) { + // We're unable to determine how many settings this includes, so we'll just log 1. + numberOfSettingsPerKey.put(KEY_SOFTAP_CONFIG, 1); + } + return data; } - private void restoreSoftApConfiguration(byte[] data) { - SoftApConfiguration configInCloud = mWifiManager.restoreSoftApBackupData(data); + @VisibleForTesting + void restoreSoftApConfiguration(byte[] data) { + SoftApConfiguration configInCloud; + if (areAgentMetricsEnabled) { + try { + configInCloud = mWifiManager.restoreSoftApBackupData(data); + mBackupRestoreEventLogger.logItemsRestored(KEY_SOFTAP_CONFIG, /* count= */ 1); + } catch (Exception e) { + configInCloud = null; + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_SOFTAP_CONFIG, /* count= */ 1, ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG); + } + } else { + configInCloud = mWifiManager.restoreSoftApBackupData(data); + } if (configInCloud != null) { if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration "); // Depending on device hardware, we may need to notify the user of a setting change @@ -1384,6 +1438,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { out.writeInt(NETWORK_POLICIES_BACKUP_VERSION); out.writeInt(policies.length); + int numberOfPoliciesBackedUp = 0; for (NetworkPolicy policy : policies) { // We purposefully only backup policies that the user has // defined; any inferred policies might include @@ -1393,13 +1448,23 @@ public class SettingsBackupAgent extends BackupAgentHelper { out.writeByte(BackupUtils.NOT_NULL); out.writeInt(marshaledPolicy.length); out.write(marshaledPolicy); + if (areAgentMetricsEnabled) { + numberOfPoliciesBackedUp++; + } } else { out.writeByte(BackupUtils.NULL); } } + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp); + } } catch (IOException ioe) { Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage()); baos.reset(); + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_NETWORK_POLICIES, + policies.length, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); } } return baos.toByteArray(); @@ -1433,6 +1498,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { int version = in.readInt(); if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION); throw new BackupUtils.BadVersionException( "Unknown Backup Serialization Version"); } @@ -1449,10 +1518,15 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // Only set the policies if there was no error in the restore operation networkPolicyManager.setNetworkPolicies(policies); + mBackupRestoreEventLogger.logItemsRestored(KEY_NETWORK_POLICIES, policies.length); } catch (NullPointerException | IOException | BackupUtils.BadVersionException | DateTimeException e) { // NPE can be thrown when trying to instantiate a NetworkPolicy Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage()); + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); } } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ed193515b382..cb656bdd5d54 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -6122,17 +6122,7 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion == 220) { - final SettingsState globalSettings = getGlobalSettingsLocked(); - final Setting enableBackAnimation = - globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION); - if (enableBackAnimation.isNull()) { - final boolean defEnableBackAnimation = - getContext() - .getResources() - .getBoolean(R.bool.def_enable_back_animation); - initGlobalSettingsDefaultValLocked( - Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation); - } + // Version 221: Removed currentVersion = 221; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c88a7fd834d6..cbdb36fff98c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -564,7 +564,6 @@ public class SettingsBackupTest { Settings.Global.WATCHDOG_TIMEOUT_MILLIS, Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, - Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device Settings.Global.Wearable.COMBINED_LOCATION_ENABLE, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index 18c43a704bcc..95dd0db40c0e 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -16,6 +16,8 @@ package com.android.providers.settings; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; @@ -28,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupDataInput; @@ -42,6 +45,8 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; +import android.net.wifi.SoftApConfiguration; +import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; @@ -126,6 +131,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; + @Mock private static WifiManager mWifiManager; private TestFriendlySettingsBackupAgent mAgentUnderTest; private Context mContext; @@ -754,6 +760,80 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); } + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void getSoftAPConfiguration_flagIsEnabled_numberOfSettingsInKeyAreRecorded() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null); + + mAgentUnderTest.getSoftAPConfiguration(); + + assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void getSoftAPConfiguration_flagIsNotEnabled_numberOfSettingsInKeyAreNotRecorded() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null); + + mAgentUnderTest.getSoftAPConfiguration(); + + assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 0); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSoftApConfiguration_flagIsEnabled_restoreIsSuccessful_successMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSoftApConfiguration_flagIsEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenThrow(new RuntimeException()); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSoftApConfiguration_flagIsNotEnabled_metricsAreNotLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + assertNull(getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest)); + } + private byte[] generateBackupData(Map<String, String> keyValueData) { int totalBytes = 0; for (String key : keyValueData.keySet()) { @@ -890,6 +970,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { this.numberOfSettingsPerKey.put(key, numberOfSettings); } } + + int getNumberOfSettingsPerKey(String key) { + if (numberOfSettingsPerKey == null || !numberOfSettingsPerKey.containsKey(key)) { + return 0; + } + return numberOfSettingsPerKey.get(key); + } } /** The TestSettingsHelper tracks which values have been backed up and/or restored. */ @@ -944,6 +1031,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { public ContentResolver getContentResolver() { return mContentResolver; } + + @Override + public Object getSystemService(String name) { + if (name.equals(Context.WIFI_SERVICE)) { + return mWifiManager; + } + return super.getSystemService(name); + } } /** ContentProvider which returns a set of known test values. */ diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index b88ae3751d22..227fff59d327 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -85,6 +85,9 @@ filegroup { filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ + "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt", + "tests/src/**/systemui/power/PowerNotificationWarningsTest.java", + "tests/src/**/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt", "tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java", "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt", "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt", @@ -841,7 +844,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - upstream: true, instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], @@ -879,7 +881,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - upstream: true, instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], @@ -916,6 +917,7 @@ android_ravenwood_test { "android.test.mock.impl", ], auto_gen_config: true, + team: "trendy_team_ravenwood", plugins: [ "dagger2-compiler", ], diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 795b39576391..c6cc9a975191 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -119,4 +119,5 @@ xuqiu@google.com yeinj@google.com yuandizhou@google.com yurilin@google.com +yuzhechen@google.com zakcohen@google.com diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig index ad4a02764176..ee918c275b7b 100644 --- a/packages/SystemUI/aconfig/predictive_back.aconfig +++ b/packages/SystemUI/aconfig/predictive_back.aconfig @@ -7,17 +7,3 @@ flag { description: "Enable Shade Animations" bug: "327732946" } - -flag { - name: "predictive_back_animate_bouncer" - namespace: "systemui" - description: "Enable Predictive Back Animation in Bouncer" - bug: "327733487" -} - -flag { - name: "predictive_back_animate_dialogs" - namespace: "systemui" - description: "Enable Predictive Back Animation for SysUI dialogs" - bug: "327721544" -} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 715d22328f2b..7d5fd903c01b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -133,14 +133,6 @@ flag { } flag { - name: "notifications_footer_view_refactor" - namespace: "systemui" - description: "Enables the refactored version of the footer view in the notification shade " - "(containing the \"Clear all\" button). Should not bring any behavior changes" - bug: "293167744" -} - -flag { name: "notifications_icon_container_refactor" namespace: "systemui" description: "Enables the refactored version of the notification icon container in StatusBar, " diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt deleted file mode 100644 index 1c9dabbb0e07..000000000000 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.android.systemui.animation - -interface AnimationFeatureFlags { - val isPredictiveBackQsDialogAnim: Boolean - get() = false -} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt index 907c39d842ce..c88c4ebb1a8d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt @@ -59,13 +59,8 @@ constructor( private val mainExecutor: Executor, private val callback: Callback, private val interactionJankMonitor: InteractionJankMonitor, - private val featureFlags: AnimationFeatureFlags, private val transitionAnimator: TransitionAnimator = - TransitionAnimator( - mainExecutor, - TIMINGS, - INTERPOLATORS, - ), + TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS), private val isForTesting: Boolean = false, ) { private companion object { @@ -219,7 +214,7 @@ constructor( dialog: Dialog, view: View, cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false + animateBackgroundBoundsChange: Boolean = false, ) { val controller = Controller.fromView(view, cuj) if (controller == null) { @@ -245,7 +240,7 @@ constructor( fun show( dialog: Dialog, controller: Controller, - animateBackgroundBoundsChange: Boolean = false + animateBackgroundBoundsChange: Boolean = false, ) { if (Looper.myLooper() != Looper.getMainLooper()) { throw IllegalStateException( @@ -263,15 +258,14 @@ constructor( val controller = animatedParent?.dialogContentWithBackground?.let { Controller.fromView(it, controller.cuj) - } - ?: controller + } ?: controller // Make sure we don't run the launch animation from the same source twice at the same time. if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) { Log.e( TAG, "Not running dialog launch animation from source as it is already expanded into a" + - " dialog" + " dialog", ) dialog.show() return @@ -288,7 +282,6 @@ constructor( animateBackgroundBoundsChange = animateBackgroundBoundsChange, parentAnimatedDialog = animatedParent, forceDisableSynchronization = isForTesting, - featureFlags = featureFlags, ) openedDialogs.add(animatedDialog) @@ -305,7 +298,7 @@ constructor( dialog: Dialog, animateFrom: Dialog, cuj: DialogCuj? = null, - animateBackgroundBoundsChange: Boolean = false + animateBackgroundBoundsChange: Boolean = false, ) { val view = openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground @@ -313,7 +306,7 @@ constructor( Log.w( TAG, "Showing dialog $dialog normally as the dialog it is shown from was not shown " + - "using DialogTransitionAnimator" + "using DialogTransitionAnimator", ) dialog.show() return @@ -323,7 +316,7 @@ constructor( dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange, - cuj = cuj + cuj = cuj, ) } @@ -346,8 +339,7 @@ constructor( val animatedDialog = openedDialogs.firstOrNull { it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl - } - ?: return null + } ?: return null return createActivityTransitionController(animatedDialog, cujType) } @@ -373,7 +365,7 @@ constructor( private fun createActivityTransitionController( animatedDialog: AnimatedDialog, - cujType: Int? = null + cujType: Int? = null, ): ActivityTransitionAnimator.Controller? { // At this point, we know that the intent of the caller is to dismiss the dialog to show // an app, so we disable the exit animation into the source because we will never want to @@ -440,7 +432,7 @@ constructor( } private fun disableDialogDismiss() { - dialog.setDismissOverride { /* Do nothing */} + dialog.setDismissOverride { /* Do nothing */ } } private fun enableDialogDismiss() { @@ -530,7 +522,6 @@ private class AnimatedDialog( * Whether synchronization should be disabled, which can be useful if we are running in a test. */ private val forceDisableSynchronization: Boolean, - private val featureFlags: AnimationFeatureFlags, ) { /** * The DecorView of this dialog window. @@ -643,8 +634,7 @@ private class AnimatedDialog( originalDialogBackgroundColor = GhostedViewTransitionAnimatorController.findGradientDrawable(background) ?.color - ?.defaultColor - ?: Color.BLACK + ?.defaultColor ?: Color.BLACK // Make the background view invisible until we start the animation. We use the transition // visibility like GhostView does so that we don't mess up with the accessibility tree (see @@ -700,7 +690,7 @@ private class AnimatedDialog( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { dialogContentWithBackground.removeOnLayoutChangeListener(this) @@ -717,9 +707,7 @@ private class AnimatedDialog( // the dialog. dialog.setDismissOverride(this::onDialogDismissed) - if (featureFlags.isPredictiveBackQsDialogAnim) { - dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) - } + dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground) // Show the dialog. dialog.show() @@ -815,7 +803,7 @@ private class AnimatedDialog( if (hasInstrumentedJank) { interactionJankMonitor.end(controller.cuj!!.cujType) } - } + }, ) } @@ -888,14 +876,14 @@ private class AnimatedDialog( onAnimationFinished(true /* instantDismiss */) onDialogDismissed(this@AnimatedDialog) } - } + }, ) } private fun startAnimation( isLaunching: Boolean, onLaunchAnimationStart: () -> Unit = {}, - onLaunchAnimationEnd: () -> Unit = {} + onLaunchAnimationEnd: () -> Unit = {}, ) { // Create 2 controllers to animate both the dialog and the source. val startController = @@ -969,7 +957,7 @@ private class AnimatedDialog( override fun onTransitionAnimationProgress( state: TransitionAnimator.State, progress: Float, - linearProgress: Float + linearProgress: Float, ) { startController.onTransitionAnimationProgress(state, progress, linearProgress) @@ -1026,7 +1014,7 @@ private class AnimatedDialog( oldLeft: Int, oldTop: Int, oldRight: Int, - oldBottom: Int + oldBottom: Int, ) { // Don't animate if bounds didn't actually change. if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) { diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index e02e8b483543..5f1f588bb2b5 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed @@ -52,7 +53,6 @@ import androidx.compose.ui.node.currentValueOf import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.util.fastSumBy import com.android.compose.modifiers.thenIf import kotlin.math.sign import kotlinx.coroutines.CompletableDeferred @@ -81,7 +81,13 @@ interface NestedDraggable { * in the direction given by [sign], with the given number of [pointersDown] when the touch slop * was detected. */ - fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller + fun onDragStarted( + position: Offset, + sign: Float, + pointersDown: Int, + // TODO(b/382665591): Make this non-nullable. + pointerType: PointerType?, + ): Controller /** * Whether this draggable should consume any scroll amount with the given [sign] coming from a @@ -184,8 +190,8 @@ private class NestedDraggableNode( */ private var lastFirstDown: Offset? = null - /** The number of pointers down. */ - private var pointersDownCount = 0 + /** The pointers currently down, in order of which they were done and mapping to their type. */ + private val pointersDown = linkedMapOf<PointerId, PointerType>() init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) @@ -256,7 +262,9 @@ private class NestedDraggableNode( check(down.position == lastFirstDown) { "Position from detectDrags() is not the same as position in trackDownPosition()" } - check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" } + check(pointersDown.size == 1 && pointersDown.keys.first() == down.id) { + "pointersDown should only contain $down but it contains $pointersDown" + } var overSlop = 0f val onTouchSlopReached = { change: PointerInputChange, over: Float -> @@ -295,8 +303,9 @@ private class NestedDraggableNode( } } - check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" } - val controller = draggable.onDragStarted(down.position, sign, pointersDownCount) + check(pointersDown.size > 0) { "pointersDown is empty" } + val controller = + draggable.onDragStarted(down.position, sign, pointersDown.size, drag.type) if (overSlop != 0f) { onDrag(controller, drag, overSlop, velocityTracker) } @@ -450,20 +459,24 @@ private class NestedDraggableNode( private suspend fun PointerInputScope.trackDownPosition() { awaitEachGesture { - val down = awaitFirstDown(requireUnconsumed = false) - lastFirstDown = down.position - pointersDownCount = 1 + try { + val down = awaitFirstDown(requireUnconsumed = false) + lastFirstDown = down.position + pointersDown[down.id] = down.type - do { - pointersDownCount += - awaitPointerEvent().changes.fastSumBy { change -> + do { + awaitPointerEvent().changes.forEach { change -> when { - change.changedToDownIgnoreConsumed() -> 1 - change.changedToUpIgnoreConsumed() -> -1 - else -> 0 + change.changedToDownIgnoreConsumed() -> { + pointersDown[change.id] = change.type + } + change.changedToUpIgnoreConsumed() -> pointersDown.remove(change.id) } } - } while (pointersDownCount > 0) + } while (pointersDown.size > 0) + } finally { + pointersDown.clear() + } } } @@ -491,12 +504,13 @@ private class NestedDraggableNode( if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) { val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" } - // TODO(b/382665591): Replace this by check(pointersDownCount > 0). - val pointersDown = pointersDownCount.coerceAtLeast(1) + // TODO(b/382665591): Ensure that there is at least one pointer down. + val pointersDownCount = pointersDown.size.coerceAtLeast(1) + val pointerType = pointersDown.entries.firstOrNull()?.value nestedScrollController = NestedScrollController( overscrollEffect, - draggable.onDragStarted(startedPosition, sign, pointersDown), + draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType), ) } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index 9c49090916e3..7f70e97411f4 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -33,10 +33,12 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performMouseInput import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeDown import androidx.compose.ui.test.swipeLeft @@ -653,6 +655,61 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(flingIsDone).isTrue() } + @Test + fun pointerType() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Touch) + } + + @Test + fun pointerType_mouse() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performMouseInput { + moveTo(center) + press() + moveBy(touchSlop.toOffset()) + release() + } + + assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Mouse) + } + + @Test + fun pointersDown_clearedWhenDisabled() { + val draggable = TestDraggable() + var enabled by mutableStateOf(true) + rule.setContent { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation, enabled = enabled)) + } + + rule.onRoot().performTouchInput { down(center) } + + enabled = false + rule.waitForIdle() + + rule.onRoot().performTouchInput { up() } + + enabled = true + rule.waitForIdle() + + rule.onRoot().performTouchInput { down(center) } + } + private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { @@ -688,6 +745,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw var onDragStartedPosition = Offset.Zero var onDragStartedSign = 0f var onDragStartedPointersDown = 0 + var onDragStartedPointerType: PointerType? = null var onDragDelta = 0f override fun shouldStartDrag(change: PointerInputChange): Boolean = shouldStartDrag @@ -696,11 +754,13 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw position: Offset, sign: Float, pointersDown: Int, + pointerType: PointerType?, ): NestedDraggable.Controller { onDragStartedCalled = true onDragStartedPosition = position onDragStartedSign = sign onDragStartedPointersDown = pointersDown + onDragStartedPointerType = pointerType onDragDelta = 0f onDragStarted.invoke(position, sign) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index c704a3e96467..de428a7d3548 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.effect.ContentOverscrollEffect /** @@ -238,6 +239,18 @@ interface BaseContentScope : ElementStateScope { fun Modifier.noResizeDuringTransitions(): Modifier /** + * Temporarily disable this content swipe actions when any scrollable below this modifier has + * consumed any amount of scroll delta, until the scroll gesture is finished. + * + * This can for instance be used to ensure that a scrollable list is overscrolled once it + * reached its bounds instead of directly starting a scene transition from the same scroll + * gesture. + */ + fun Modifier.disableSwipesWhenScrolling( + bounds: NestedScrollableBound = NestedScrollableBound.Any + ): Modifier + + /** * A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore * enabling sharedElement transitions between them. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 158256d14d1a..8153586efbca 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -232,6 +232,8 @@ fun MutableSceneTransitionLayoutState( canShowOverlay: (OverlayKey) -> Boolean = { true }, canHideOverlay: (OverlayKey) -> Boolean = { true }, canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, + onTransitionStart: (TransitionState.Transition) -> Unit = {}, + onTransitionEnd: (TransitionState.Transition) -> Unit = {}, ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, @@ -241,6 +243,8 @@ fun MutableSceneTransitionLayoutState( canShowOverlay, canHideOverlay, canReplaceOverlay, + onTransitionStart, + onTransitionEnd, ) } @@ -252,7 +256,11 @@ internal class MutableSceneTransitionLayoutStateImpl( internal val canChangeScene: (SceneKey) -> Boolean = { true }, internal val canShowOverlay: (OverlayKey) -> Boolean = { true }, internal val canHideOverlay: (OverlayKey) -> Boolean = { true }, - internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> true }, + internal val canReplaceOverlay: (from: OverlayKey, to: OverlayKey) -> Boolean = { _, _ -> + true + }, + private val onTransitionStart: (TransitionState.Transition) -> Unit = {}, + private val onTransitionEnd: (TransitionState.Transition) -> Unit = {}, ) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() @@ -367,9 +375,11 @@ internal class MutableSceneTransitionLayoutStateImpl( startTransitionInternal(transition, chain) // Run the transition until it is finished. + onTransitionStart(transition) transition.runInternal() } finally { finishTransition(transition) + onTransitionEnd(transition) } } @@ -384,14 +394,10 @@ internal class MutableSceneTransitionLayoutStateImpl( val toContent = transition.toContent // Update the transition specs. - transition.transformationSpec = - transitions - .transitionSpec(fromContent, toContent, key = transition.key) - .transformationSpec(transition) - transition.previewTransformationSpec = - transitions - .transitionSpec(fromContent, toContent, key = transition.key) - .previewTransformationSpec(transition) + val spec = transitions.transitionSpec(fromContent, toContent, key = transition.key) + transition._cuj = spec.cuj + transition.transformationSpec = spec.transformationSpec(transition) + transition.previewTransformationSpec = spec.previewTransformationSpec(transition) } private fun startTransitionInternal(transition: TransitionState.Transition, chain: Boolean) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 756d71c1b5cf..ff8efc28aa21 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -29,6 +29,7 @@ import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.TransformationMatcher import com.android.compose.animation.scene.transformation.TransformationWithRange +import com.android.internal.jank.Cuj.CujType /** The transitions configuration of a [SceneTransitionLayout]. */ class SceneTransitions @@ -111,7 +112,15 @@ internal constructor( } private fun defaultTransition(from: ContentKey, to: ContentKey) = - TransitionSpecImpl(key = null, from, to, null, null, TransformationSpec.EmptyProvider) + TransitionSpecImpl( + key = null, + from, + to, + cuj = null, + previewTransformationSpec = null, + reversePreviewTransformationSpec = null, + TransformationSpec.EmptyProvider, + ) companion object { internal val DefaultSwipeSpec = @@ -147,6 +156,9 @@ internal interface TransitionSpec { */ val to: ContentKey? + /** The CUJ covered by this transition. */ + @CujType val cuj: Int? + /** * Return a reversed version of this [TransitionSpec] for a transition going from [to] to * [from]. @@ -213,6 +225,7 @@ internal class TransitionSpecImpl( override val key: TransitionKey?, override val from: ContentKey?, override val to: ContentKey?, + override val cuj: Int?, private val previewTransformationSpec: ((TransitionState.Transition) -> TransformationSpecImpl)? = null, @@ -226,6 +239,7 @@ internal class TransitionSpecImpl( key = key, from = to, to = from, + cuj = cuj, previewTransformationSpec = reversePreviewTransformationSpec, reversePreviewTransformationSpec = previewTransformationSpec, transformationSpec = { transition -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index 607e4fadc256..ba92f9bea07d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -315,16 +315,10 @@ internal class SwipeAnimation<T : ContentKey>( val skipAnimation = hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress) - val targetOffset = - if (targetContent == fromContent) { - 0f - } else { - val distance = distance() - check(distance != DistanceUnspecified) { - "distance is equal to $DistanceUnspecified" - } - distance - } + val distance = distance() + check(distance != DistanceUnspecified) { "distance is equal to $DistanceUnspecified" } + + val targetOffset = if (targetContent == fromContent) 0f else distance // If the effective current content changed, it should be reflected right now in the // current state, even before the settle animation is ongoing. That way all the @@ -343,7 +337,16 @@ internal class SwipeAnimation<T : ContentKey>( } val animatable = - Animatable(initialOffset, OffsetVisibilityThreshold).also { offsetAnimation = it } + Animatable(initialOffset, OffsetVisibilityThreshold).also { + offsetAnimation = it + + // We should animate when the progress value is between [0, 1]. + if (distance > 0) { + it.updateBounds(0f, distance) + } else { + it.updateBounds(distance, 0f) + } + } check(isAnimatingOffset()) @@ -370,42 +373,26 @@ internal class SwipeAnimation<T : ContentKey>( val velocityConsumed = CompletableDeferred<Float>() offsetAnimationRunnable.complete { - try { + val result = animatable.animateTo( targetValue = targetOffset, animationSpec = swipeSpec, initialVelocity = initialVelocity, - ) { - // Immediately stop this transition if we are bouncing on a content that - // does not bounce. - if (!contentTransition.isWithinProgressRange(progress)) { - // We are no longer able to consume the velocity, the rest can be - // consumed by another component in the hierarchy. - velocityConsumed.complete(initialVelocity - velocity) - throw SnapException() - } - } - } catch (_: SnapException) { - /* Ignore. */ - } finally { - if (!velocityConsumed.isCompleted) { - // The animation consumed the whole available velocity - velocityConsumed.complete(initialVelocity) - } + ) - // Wait for overscroll to finish so that the transition is removed from the STLState - // only after the overscroll is done, to avoid dropping frame right when the user - // lifts their finger and overscroll is animated to 0. - overscrollCompletable?.await() - } + // We are no longer able to consume the velocity, the rest can be consumed by another + // component in the hierarchy. + velocityConsumed.complete(initialVelocity - result.endState.velocity) + + // Wait for overscroll to finish so that the transition is removed from the STLState + // only after the overscroll is done, to avoid dropping frame right when the user + // lifts their finger and overscroll is animated to 0. + overscrollCompletable?.await() } return velocityConsumed.await() } - /** An exception thrown during the animation to stop it immediately. */ - private class SnapException : Exception() - private fun canChangeContent(targetContent: ContentKey): Boolean { return when (val transition = contentTransition) { is TransitionState.Transition.ChangeScene -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index c5b3df222855..3f6bce724b1b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -54,7 +54,7 @@ private fun DraggableHandlerImpl.contentForSwipes(): Content { /** Whether swipe should be enabled in the given [orientation]. */ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { - if (userActions.isEmpty()) { + if (userActions.isEmpty() || !areSwipesAllowed()) { return false } @@ -69,6 +69,10 @@ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { * @return The best matching [UserActionResult], or `null` if no match is found. */ internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? { + if (!areSwipesAllowed()) { + return null + } + var bestPoints = Int.MIN_VALUE var bestMatch: UserActionResult? = null userActions.forEach { (actionSwipe, actionResult) -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index fda6fab6229a..998054ef6c9e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.Transformation +import com.android.internal.jank.Cuj.CujType /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -64,6 +65,7 @@ interface SceneTransitionsBuilder { fun to( to: ContentKey, key: TransitionKey? = null, + @CujType cuj: Int? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, @@ -90,6 +92,7 @@ interface SceneTransitionsBuilder { from: ContentKey, to: ContentKey? = null, key: TransitionKey? = null, + @CujType cuj: Int? = null, preview: (TransitionBuilder.() -> Unit)? = null, reversePreview: (TransitionBuilder.() -> Unit)? = null, builder: TransitionBuilder.() -> Unit = {}, @@ -146,6 +149,9 @@ interface TransitionBuilder : BaseTransitionBuilder { */ var swipeSpec: SpringSpec<Float>? + /** The CUJ associated to this transitions. */ + @CujType var cuj: Int? + /** * Define a timestamp-based range for the transformations inside [builder]. * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index a1649964ec13..7ca521513714 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -37,6 +37,7 @@ import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationMatcher import com.android.compose.animation.scene.transformation.TransformationRange import com.android.compose.animation.scene.transformation.Translate +import com.android.internal.jank.Cuj.CujType internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { val impl = SceneTransitionsBuilderImpl().apply(builder) @@ -52,28 +53,47 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { override fun to( to: ContentKey, key: TransitionKey?, + @CujType cuj: Int?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, builder: TransitionBuilder.() -> Unit, ) { - transition(from = null, to = to, key = key, preview, reversePreview, builder) + transition( + from = null, + to = to, + key = key, + cuj = cuj, + preview = preview, + reversePreview = reversePreview, + builder = builder, + ) } override fun from( from: ContentKey, to: ContentKey?, key: TransitionKey?, + @CujType cuj: Int?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, builder: TransitionBuilder.() -> Unit, ) { - transition(from = from, to = to, key = key, preview, reversePreview, builder) + transition( + from = from, + to = to, + key = key, + cuj = cuj, + preview = preview, + reversePreview = reversePreview, + builder = builder, + ) } private fun transition( from: ContentKey?, to: ContentKey?, key: TransitionKey?, + @CujType cuj: Int?, preview: (TransitionBuilder.() -> Unit)?, reversePreview: (TransitionBuilder.() -> Unit)?, builder: TransitionBuilder.() -> Unit, @@ -93,9 +113,10 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { val spec = TransitionSpecImpl( - key, - from, - to, + key = key, + from = from, + to = to, + cuj = cuj, previewTransformationSpec = preview?.let { { t -> transformationSpec(t, it) } }, reversePreviewTransformationSpec = reversePreview?.let { { t -> transformationSpec(t, it) } }, @@ -190,6 +211,7 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow) override var swipeSpec: SpringSpec<Float>? = null override var distance: UserActionDistance? = null + override var cuj: Int? = null private val durationMillis: Int by lazy { val spec = spec if (spec !is DurationBasedAnimationSpec) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 4c15f7a4534f..59b4a09385f5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -56,7 +56,10 @@ import com.android.compose.animation.scene.effect.GestureEffect import com.android.compose.animation.scene.effect.VisualEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions +import com.android.compose.gesture.NestedScrollControlState +import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.effect.OffsetOverscrollEffect +import com.android.compose.gesture.nestedScrollController import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.ContainerState import com.android.compose.ui.graphics.container @@ -70,7 +73,8 @@ internal sealed class Content( actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, ) { - internal val scope = ContentScopeImpl(layoutImpl, content = this) + private val nestedScrollControlState = NestedScrollControlState() + internal val scope = ContentScopeImpl(layoutImpl, content = this, nestedScrollControlState) val containerState = ContainerState() var content by mutableStateOf(content) @@ -101,11 +105,14 @@ internal sealed class Content( scope.content() } } + + fun areSwipesAllowed(): Boolean = nestedScrollControlState.isOuterScrollAllowed } internal class ContentScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val content: Content, + private val nestedScrollControlState: NestedScrollControlState, ) : ContentScope, ElementStateScope by layoutImpl.elementStateScope { override val contentKey: ContentKey get() = content.key @@ -176,6 +183,10 @@ internal class ContentScopeImpl( return noResizeDuringTransitions(layoutState = layoutImpl.state) } + override fun Modifier.disableSwipesWhenScrolling(bounds: NestedScrollableBound): Modifier { + return nestedScrollController(nestedScrollControlState, bounds) + } + @Composable override fun NestedSceneTransitionLayout( state: SceneTransitionLayoutState, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt index e7ca51114b93..712af56ee1bc 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt @@ -32,6 +32,7 @@ import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransformationSpec import com.android.compose.animation.scene.TransformationSpecImpl import com.android.compose.animation.scene.TransitionKey +import com.android.internal.jank.Cuj.CujType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -237,6 +238,11 @@ sealed interface TransitionState { /** Whether user input is currently driving the transition. */ abstract val isUserInputOngoing: Boolean + /** The CUJ covered by this transition. */ + @CujType + val cuj: Int? + get() = _cuj + /** * The progress of the preview transition. This is usually in the `[0; 1]` range, but it can * also be less than `0` or greater than `1` when using transitions with a spring @@ -251,13 +257,15 @@ sealed interface TransitionState { internal open val isInPreviewStage: Boolean = false /** - * The current [TransformationSpecImpl] associated to this transition. + * The current [TransformationSpecImpl] and other values associated to this transition from + * the spec. * * Important: These will be set exactly once, when this transition is * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty internal var previewTransformationSpec: TransformationSpecImpl? = null + internal var _cuj: Int? = null /** * An animatable that animates from 1f to 0f. This will be used to nicely animate the sudden diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt new file mode 100644 index 000000000000..06a9735d97e2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ContentTest { + @get:Rule val rule = createComposeRule() + + @Test + fun disableSwipesWhenScrolling() { + lateinit var layoutImpl: SceneTransitionLayoutImpl + rule.setContent { + SceneTransitionLayoutForTesting( + remember { MutableSceneTransitionLayoutState(SceneA) }, + onLayoutImpl = { layoutImpl = it }, + ) { + scene(SceneA) { + Box( + Modifier.fillMaxSize() + .disableSwipesWhenScrolling() + .scrollable(rememberScrollableState { it }, Orientation.Vertical) + ) + } + } + } + + val content = layoutImpl.content(SceneA) + assertThat(content.areSwipesAllowed()).isTrue() + rule.onRoot().performTouchInput { + down(topLeft) + moveBy(bottomLeft) + } + + assertThat(content.areSwipesAllowed()).isFalse() + rule.onRoot().performTouchInput { up() } + assertThat(content.areSwipesAllowed()).isTrue() + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 5074cd5211ce..f3be5e43c294 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -491,4 +491,66 @@ class SceneTransitionLayoutStateTest { assertThat(state.transitionState).isIdle() assertThat(state.transitionState).hasCurrentScene(SceneC) } + + @Test + fun trackTransitionCujs() = runTest { + val started = mutableSetOf<TransitionState.Transition>() + val finished = mutableSetOf<TransitionState.Transition>() + val cujWhenStarting = mutableMapOf<TransitionState.Transition, Int?>() + val state = + MutableSceneTransitionLayoutState( + SceneA, + transitions { + // A <=> B. + from(SceneA, to = SceneB, cuj = 1) + + // A <=> C. + from(SceneA, to = SceneC, cuj = 2) + from(SceneC, to = SceneA, cuj = 3) + }, + onTransitionStart = { transition -> + started.add(transition) + cujWhenStarting[transition] = transition.cuj + }, + onTransitionEnd = { finished.add(it) }, + ) + + val aToB = transition(SceneA, SceneB) + val bToA = transition(SceneB, SceneA) + val aToC = transition(SceneA, SceneC) + val cToA = transition(SceneC, SceneA) + + val animationScope = this + state.startTransitionImmediately(animationScope, aToB) + assertThat(started).containsExactly(aToB) + assertThat(finished).isEmpty() + + state.startTransitionImmediately(animationScope, bToA) + assertThat(started).containsExactly(aToB, bToA) + assertThat(finished).isEmpty() + + aToB.finish() + runCurrent() + assertThat(finished).containsExactly(aToB) + + state.startTransitionImmediately(animationScope, aToC) + assertThat(started).containsExactly(aToB, bToA, aToC) + assertThat(finished).containsExactly(aToB) + + state.startTransitionImmediately(animationScope, cToA) + assertThat(started).containsExactly(aToB, bToA, aToC, cToA) + assertThat(finished).containsExactly(aToB) + + bToA.finish() + aToC.finish() + cToA.finish() + runCurrent() + assertThat(started).containsExactly(aToB, bToA, aToC, cToA) + assertThat(finished).containsExactly(aToB, bToA, aToC, cToA) + + assertThat(cujWhenStarting[aToB]).isEqualTo(1) + assertThat(cujWhenStarting[bToA]).isEqualTo(1) + assertThat(cujWhenStarting[aToC]).isEqualTo(2) + assertThat(cujWhenStarting[cToA]).isEqualTo(3) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 7c8c6e5f6c12..e580e3c40690 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size @@ -33,6 +34,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assertHeightIsEqualTo @@ -43,6 +45,9 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onChild import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeDown import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset @@ -469,4 +474,41 @@ class SceneTransitionLayoutTest { assertThat(layoutImpl.overlaysOrNullForTest()).isNull() } + + @Test + fun transitionProgressBoundedBetween0And1() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) } + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Spacer(Modifier.fillMaxSize()) + } + scene(SceneB) { Spacer(Modifier.fillMaxSize()) } + } + } + assertThat(state.transitionState).isIdle() + + rule.mainClock.autoAdvance = false + + // Swipe the verticalSwipeDistance. + rule.onRoot().performTouchInput { + swipeDown(endY = bottom + touchSlop, durationMillis = 50) + } + + rule.mainClock.advanceTimeBy(16) + val transition = assertThat(state.transitionState).isSceneTransition() + assertThat(transition).isNotNull() + assertThat(transition).hasProgress(1f, tolerance = 0.01f) + + rule.mainClock.advanceTimeBy(16) + // Fling animation, we are overscrolling now. Progress should always be between [0, 1]. + assertThat(transition).hasProgress(1f) + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 0f8ca947479b..2b0825f39243 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -30,7 +30,6 @@ import android.util.AttributeSet import android.util.Log import android.util.MathUtils import android.util.TypedValue -import android.view.View.MeasureSpec.AT_MOST import android.view.View.MeasureSpec.EXACTLY import android.view.animation.Interpolator import android.widget.TextView @@ -77,7 +76,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe var maxSingleDigitWidth = -1 var digitTranslateAnimator: DigitTranslateAnimator? = null var aodFontSizePx: Float = -1F - var isVertical: Boolean = false // Store the font size when there's no height constraint as a reference when adjusting font size private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE @@ -148,16 +146,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure()") - if (isVertical) { - // use at_most to avoid apply measuredWidth from last measuring to measuredHeight - // cause we use max to setMeasuredDimension - super.onMeasure( - MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST), - MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST), - ) - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - } + super.onMeasure(widthMeasureSpec, heightMeasureSpec) val layout = this.layout if (layout != null) { @@ -213,18 +202,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe ) } - if (isVertical) { - expectedWidth = expectedHeight.also { expectedHeight = expectedWidth } - } setMeasuredDimension(expectedWidth, expectedHeight) } override fun onDraw(canvas: Canvas) { - if (isVertical) { - canvas.save() - canvas.translate(0F, measuredHeight.toFloat()) - canvas.rotate(-90F) - } logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText } val translation = getLocalTranslation() canvas.translate(translation.x.toFloat(), translation.y.toFloat()) @@ -238,9 +219,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat()) } canvas.translate(-translation.x.toFloat(), -translation.y.toFloat()) - if (isVertical) { - canvas.restore() - } } override fun invalidate() { @@ -353,18 +331,20 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point { - val viewWidth = if (isVertical) measuredHeight else measuredWidth when (horizontalAlignment) { HorizontalAlignment.LEFT -> { inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left } HorizontalAlignment.RIGHT -> { inPoint.x = - viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt() + measuredWidth - + interpolatedTextBounds.right - + lockScreenPaint.strokeWidth.toInt() } HorizontalAlignment.CENTER -> { inPoint.x = - (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left + (measuredWidth - interpolatedTextBounds.width()) / 2 - + interpolatedTextBounds.left } } return inPoint @@ -373,7 +353,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe // translation of reference point of text // used for translation when calling textInterpolator private fun getLocalTranslation(): Point { - val viewHeight = if (isVertical) measuredWidth else measuredHeight val interpolatedTextBounds = updateInterpolatedTextBounds() val localTranslation = Point(0, 0) val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure @@ -381,7 +360,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe when (verticalAlignment) { VerticalAlignment.CENTER -> { localTranslation.y = - ((viewHeight - interpolatedTextBounds.height()) / 2 - + ((measuredHeight - interpolatedTextBounds.height()) / 2 - interpolatedTextBounds.top - correctedBaseline) } @@ -392,7 +371,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } VerticalAlignment.BOTTOM -> { localTranslation.y = - viewHeight - + measuredHeight - interpolatedTextBounds.bottom - lockScreenPaint.strokeWidth.toInt() - correctedBaseline diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 2c1dacdfae73..4d2a6d9bd57a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -232,7 +232,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() { val pinViewController = constructPinViewController(mockKeyguardPinView) - `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) @@ -249,7 +248,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() { val pinViewController = constructPinViewController(mockKeyguardPinView) - `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) @@ -275,7 +273,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun onUserInput_autoConfirmation_attemptsUnlock() { val pinViewController = constructPinViewController(mockKeyguardPinView) - whenever(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) whenever(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) whenever(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) whenever(passwordTextView.text).thenReturn("000000") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt index e659ef274980..698fac107a1d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Context.INPUT_SERVICE +import android.content.Intent import android.hardware.input.InputGestureData +import android.hardware.input.InputManager import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.fakeInputManager import android.platform.test.annotations.EnableFlags @@ -27,9 +29,12 @@ import androidx.test.filters.SmallTest import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyboard.shortcut.customInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData +import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper import com.android.systemui.kosmos.testScope import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.userTracker @@ -48,18 +53,41 @@ import org.mockito.kotlin.whenever @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER) class CustomInputGesturesRepositoryTest : SysuiTestCase() { - private val mockUserContext: Context = mock() + private val primaryUserContext: Context = mock() + private val secondaryUserContext: Context = mock() + private var activeUserContext: Context = primaryUserContext + private val kosmos = testKosmos().also { - it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext }) + it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext }) } private val inputManager = kosmos.fakeInputManager.inputManager + private val broadcastDispatcher = kosmos.broadcastDispatcher + private val inputManagerForSecondaryUser: InputManager = mock() private val testScope = kosmos.testScope + private val testHelper = kosmos.shortcutHelperTestHelper private val customInputGesturesRepository = kosmos.customInputGesturesRepository @Before - fun setup(){ - whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) + fun setup() { + activeUserContext = primaryUserContext + whenever(primaryUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager) + whenever(secondaryUserContext.getSystemService(INPUT_SERVICE)) + .thenReturn(inputManagerForSecondaryUser) + } + + @Test + fun customInputGestures_emitsNewUsersInputGesturesWhenUserIsSwitch() { + testScope.runTest { + setCustomInputGesturesForPrimaryUser(allAppsInputGestureData) + setCustomInputGesturesForSecondaryUser(goHomeInputGestureData) + + val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures) + assertThat(inputGestures).containsExactly(allAppsInputGestureData) + + switchToSecondaryUser() + assertThat(inputGestures).containsExactly(goHomeInputGestureData) + } } @Test @@ -115,4 +143,24 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() { } } + private fun setCustomInputGesturesForPrimaryUser(vararg inputGesture: InputGestureData) { + whenever( + inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) + ).thenReturn(inputGesture.toList()) + } + + private fun setCustomInputGesturesForSecondaryUser(vararg inputGesture: InputGestureData) { + whenever( + inputManagerForSecondaryUser.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY) + ).thenReturn(inputGesture.toList()) + } + + private fun switchToSecondaryUser() { + activeUserContext = secondaryUserContext + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_USER_SWITCHED) + ) + } + }
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt index b29a5f4e456f..9e8713be3f5e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository import com.android.systemui.kosmos.testScope import com.android.systemui.shade.data.repository.FlingInfo import com.android.systemui.shade.data.repository.fakeShadeRepository @@ -47,7 +48,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.reset import org.mockito.Mockito.spy -import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -55,9 +55,8 @@ import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject class FromLockscreenTransitionInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository( - testScope = testScope, - )) + this.fakeKeyguardTransitionRepository = + spy(FakeKeyguardTransitionRepository(testScope = testScope)) } private val testScope = kosmos.testScope @@ -181,6 +180,12 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { underTest.start() assertThatRepository(transitionRepository).noTransitionsStarted() + transitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) + keyguardRepository.setKeyguardDismissible(true) runCurrent() shadeRepository.setCurrentFling( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index a36e0eac086e..9bae7bd72f7d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.navigationbar import android.app.ActivityManager +import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -61,6 +62,7 @@ class TaskbarDelegateTest : SysuiTestCase() { @Mock lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock lateinit var mStatusBarStateController: StatusBarStateController @Mock lateinit var mDisplayTracker: DisplayTracker + @Mock lateinit var mHandler: Handler @Before fun setup() { @@ -69,6 +71,11 @@ class TaskbarDelegateTest : SysuiTestCase() { `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController) `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState) `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState) + `when`(mHandler.post(any())).thenAnswer { + (it.arguments[0] as Runnable).run() + true + } + mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance() mTaskbarDelegate = TaskbarDelegate( @@ -76,6 +83,7 @@ class TaskbarDelegateTest : SysuiTestCase() { mLightBarControllerFactory, mStatusBarKeyguardViewManager, mStatusBarStateController, + mHandler, ) mTaskbarDelegate.setDependencies( mCommandQueue, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java index 2e9d6e85d0aa..49cbb5a924f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java @@ -53,7 +53,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import org.junit.Test; import org.junit.runner.RunWith; @@ -365,7 +364,6 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void updateExpansion_partiallyExpanded_fullscreenFalse() { // WHEN QS are only partially expanded mQsController.setExpanded(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index b5043ce700f1..fe44c3e31e66 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -39,7 +39,7 @@ class ShadeRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { - underTest = ShadeRepositoryImpl() + underTest = ShadeRepositoryImpl(testScope) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt index 83fb14aaf792..6b2c4b260806 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -9,9 +9,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -25,7 +24,7 @@ class ConditionExtensionsTest : SysuiTestCase() { @Before fun setUp() { - testScope = TestScope(StandardTestDispatcher()) + testScope = TestScope(UnconfinedTestDispatcher()) } @Test @@ -34,11 +33,9 @@ class ConditionExtensionsTest : SysuiTestCase() { val flow = flowOf(true) val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) - runCurrent() assertThat(condition.isConditionSet).isFalse() condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() } @@ -49,11 +46,9 @@ class ConditionExtensionsTest : SysuiTestCase() { val flow = flowOf(false) val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) - runCurrent() assertThat(condition.isConditionSet).isFalse() condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() } @@ -65,7 +60,6 @@ class ConditionExtensionsTest : SysuiTestCase() { val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) condition.start() - runCurrent() assertThat(condition.isConditionSet).isFalse() assertThat(condition.isConditionMet).isFalse() } @@ -78,11 +72,10 @@ class ConditionExtensionsTest : SysuiTestCase() { flow.toCondition( scope = this, strategy = Condition.START_EAGERLY, - initialValue = true + initialValue = true, ) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() } @@ -95,11 +88,10 @@ class ConditionExtensionsTest : SysuiTestCase() { flow.toCondition( scope = this, strategy = Condition.START_EAGERLY, - initialValue = false + initialValue = false, ) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() } @@ -111,16 +103,13 @@ class ConditionExtensionsTest : SysuiTestCase() { val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() flow.value = true - runCurrent() assertThat(condition.isConditionMet).isTrue() flow.value = false - runCurrent() assertThat(condition.isConditionMet).isFalse() condition.stop() @@ -131,15 +120,12 @@ class ConditionExtensionsTest : SysuiTestCase() { testScope.runTest { val flow = MutableSharedFlow<Boolean>() val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(0) condition.start() - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(1) condition.stop() - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(0) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index a62d9d5ce62f..0061c4142e8b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -132,7 +132,7 @@ class CallChipViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chip) repo.setOngoingCallState( - inCallModel(startTimeMs = 1000, notificationIcon = mock<StatusBarIconView>()) + inCallModel(startTimeMs = 1000, notificationIcon = createStatusBarIconViewOrNull()) ) assertThat((latest as OngoingActivityChipModel.Shown).icon) @@ -147,11 +147,12 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) - val notifIcon = mock<StatusBarIconView>() + val notifIcon = createStatusBarIconViewOrNull() repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon)) assertThat((latest as OngoingActivityChipModel.Shown).icon) @@ -165,6 +166,24 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun chip_positiveStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel( + startTimeMs = 1000, + notificationIcon = createStatusBarIconViewOrNull(), + notificationKey = "notifKey", + ) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey")) + } + + @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -192,7 +211,7 @@ class CallChipViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chip) repo.setOngoingCallState( - inCallModel(startTimeMs = 0, notificationIcon = mock<StatusBarIconView>()) + inCallModel(startTimeMs = 0, notificationIcon = createStatusBarIconViewOrNull()) ) assertThat((latest as OngoingActivityChipModel.Shown).icon) @@ -207,11 +226,12 @@ class CallChipViewModelTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun chip_zeroStartTime_notifIconFlagOn_iconIsNotifIcon() = + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chip_zeroStartTime_notifIconFlagOn_cdFlagOff_iconIsNotifIcon() = testScope.runTest { val latest by collectLastValue(underTest.chip) - val notifIcon = mock<StatusBarIconView>() + val notifIcon = createStatusBarIconViewOrNull() repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon)) assertThat((latest as OngoingActivityChipModel.Shown).icon) @@ -224,8 +244,27 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun chip_zeroStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel( + startTimeMs = 0, + notificationIcon = createStatusBarIconViewOrNull(), + notificationKey = "notifKey", + ) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey")) + } + + @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun chip_notifIconFlagOn_butNullNotifIcon_iconIsPhone() = + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -242,6 +281,24 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + repo.setOngoingCallState( + inCallModel( + startTimeMs = 1000, + notificationIcon = null, + notificationKey = "notifKey", + ) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey")) + } + + @Test fun chip_positiveStartTime_colorsAreThemed() = testScope.runTest { val latest by collectLastValue(underTest.chip) @@ -330,4 +387,13 @@ class CallChipViewModelTest : SysuiTestCase() { verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null) } + + companion object { + fun createStatusBarIconViewOrNull(): StatusBarIconView? = + if (StatusBarConnectedDisplays.isEnabled) { + null + } else { + mock<StatusBarIconView>() + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt index 0d033a4098ec..fe15eac46e2d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.notification.domain.interactor +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -148,7 +149,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { } @Test - fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() = + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_missingStatusBarIconChipView_cdFlagDisabled_inConstructor_emitsNull() = kosmos.runTest { val underTest = factory.create( @@ -167,6 +169,25 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_missingStatusBarIconChipView_cdFlagEnabled_inConstructor_emitsNotNull() = + kosmos.runTest { + val underTest = + factory.create( + activeNotificationModel( + key = "notif1", + statusBarChipIcon = null, + promotedContent = PROMOTED_CONTENT, + ), + 32L, + ) + + val latest by collectLastValue(underTest.notificationChip) + + assertThat(latest).isNotNull() + } + + @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun notificationChip_cdEnabled_missingStatusBarIconChipView_inConstructor_emitsNotNull() = kosmos.runTest { val underTest = @@ -186,7 +207,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { } @Test - fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() = + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_cdFlagDisabled_missingStatusBarIconChipView_inSet_emitsNull() = kosmos.runTest { val startingNotif = activeNotificationModel( @@ -211,6 +233,31 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_cdFlagEnabled_missingStatusBarIconChipView_inSet_emitsNotNull() = + kosmos.runTest { + val startingNotif = + activeNotificationModel( + key = "notif1", + statusBarChipIcon = mock(), + promotedContent = PROMOTED_CONTENT, + ) + val underTest = factory.create(startingNotif, 123L) + val latest by collectLastValue(underTest.notificationChip) + assertThat(latest).isNotNull() + + underTest.setNotification( + activeNotificationModel( + key = "notif1", + statusBarChipIcon = null, + promotedContent = PROMOTED_CONTENT, + ) + ) + + assertThat(latest).isNotNull() + } + + @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() = kosmos.runTest { val startingNotif = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt index f703d785ceac..ee4a52d35d68 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -83,7 +84,8 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() { @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) - fun notificationChips_notifMissingStatusBarChipIconView_empty() = + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() = kosmos.runTest { val latest by collectLastValue(underTest.notificationChips) @@ -101,6 +103,25 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() = + kosmos.runTest { + val latest by collectLastValue(underTest.notificationChips) + + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = null, + promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + ) + ) + ) + + assertThat(latest).isNotEmpty() + } + + @Test @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun notificationChips_onePromotedNotif_statusBarIconViewMatches() = kosmos.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 17076b4d7505..e561e3ea27d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -23,7 +23,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.kosmos.collectLastValue @@ -31,6 +30,7 @@ import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModelTest.Companion.createStatusBarIconViewOrNull import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel @@ -48,7 +48,6 @@ import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.runner.RunWith import org.mockito.kotlin.mock @@ -84,8 +83,8 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) - fun chips_notifMissingStatusBarChipIconView_empty() = + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, StatusBarConnectedDisplays.FLAG_NAME) + fun chips_notifMissingStatusBarChipIconView_cdFlagDisabled_empty() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -104,11 +103,31 @@ class NotifChipsViewModelTest : SysuiTestCase() { @Test @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chips_notifMissingStatusBarChipIconView_cdFlagEnabled_notEmpty() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = null, + promotedContent = PromotedNotificationContentModel.Builder("notif").build(), + ) + ) + ) + + assertThat(latest).isNotEmpty() + } + + @Test + @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY) fun chips_onePromotedNotif_statusBarIconViewMatches() = kosmos.runTest { val latest by collectLastValue(underTest.chips) - val icon = mock<StatusBarIconView>() + val icon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -121,8 +140,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat(latest).hasSize(1) val chip = latest!![0] - assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java) - assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon)) + assertIsNotifChip(chip, icon, "notif") } @Test @@ -168,7 +186,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -187,8 +205,8 @@ class NotifChipsViewModelTest : SysuiTestCase() { kosmos.runTest { val latest by collectLastValue(underTest.chips) - val firstIcon = mock<StatusBarIconView>() - val secondIcon = mock<StatusBarIconView>() + val firstIcon = createStatusBarIconViewOrNull() + val secondIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -203,15 +221,15 @@ class NotifChipsViewModelTest : SysuiTestCase() { ), activeNotificationModel( key = "notif3", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = null, ), ) ) assertThat(latest).hasSize(2) - assertIsNotifChip(latest!![0], firstIcon) - assertIsNotifChip(latest!![1], secondIcon) + assertIsNotifChip(latest!![0], firstIcon, "notif1") + assertIsNotifChip(latest!![1], secondIcon, "notif2") } @Test @@ -269,7 +287,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -293,7 +311,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -323,7 +341,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -353,7 +371,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -382,7 +400,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -411,7 +429,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -439,7 +457,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -467,7 +485,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -499,7 +517,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = promotedContentBuilder.build(), ) ) @@ -531,7 +549,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { listOf( activeNotificationModel( key = "clickTest", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = PromotedNotificationContentModel.Builder("clickTest").build(), ) @@ -552,9 +570,21 @@ class NotifChipsViewModelTest : SysuiTestCase() { } companion object { - fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { - assertThat((latest as OngoingActivityChipModel.Shown).icon) - .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) + fun assertIsNotifChip( + latest: OngoingActivityChipModel?, + expectedIcon: StatusBarIconView?, + notificationKey: String, + ) { + val shown = latest as OngoingActivityChipModel.Shown + if (StatusBarConnectedDisplays.isEnabled) { + assertThat(shown.icon) + .isEqualTo( + OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notificationKey) + ) + } else { + assertThat(latest.icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon!!)) + } } fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 4fb42e94adb2..42358cce59a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository @@ -169,29 +170,35 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Test fun primaryChip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() = testScope.runTest { + val notificationKey = "call" screenRecordState.value = ScreenRecordModel.DoingNothing // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = notificationKey) + ) val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest) + assertIsCallChip(latest, notificationKey) } @Test fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = testScope.runTest { // Start with just the lowest priority chip shown - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + val callNotificationKey = "call" + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) // And everything else hidden mediaProjectionState.value = MediaProjectionState.NotProjecting screenRecordState.value = ScreenRecordModel.DoingNothing val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest) + assertIsCallChip(latest, callNotificationKey) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -218,7 +225,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + val callNotificationKey = "call" + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) val latest by collectLastValue(underTest.primaryChip) @@ -235,7 +245,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN the lower priority call is used - assertIsCallChip(latest) + assertIsCallChip(latest, callNotificationKey) } /** Regression test for b/347726238. */ @@ -364,13 +374,27 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all) } - fun assertIsCallChip(latest: OngoingActivityChipModel?) { - assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) + fun assertIsCallChip(latest: OngoingActivityChipModel?, notificationKey: String) { + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java) + if (StatusBarConnectedDisplays.isEnabled) { + assertNotificationIcon(latest, notificationKey) + return + } val icon = (((latest as OngoingActivityChipModel.Shown).icon) as OngoingActivityChipModel.ChipIcon.SingleColorIcon) .impl as Icon.Resource assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone) } + + private fun assertNotificationIcon( + latest: OngoingActivityChipModel?, + notificationKey: String, + ) { + val shown = latest as OngoingActivityChipModel.Shown + val notificationIcon = + shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon + assertThat(notificationIcon.notificationKey).isEqualTo(notificationKey) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 0050ebee64d6..0f42f29e76ee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -34,7 +34,7 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository -import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModelTest.Companion.createStatusBarIconViewOrNull import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor @@ -186,13 +186,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun chips_screenRecordShowAndCallShow_primaryIsScreenRecordSecondaryIsCall() = testScope.runTest { + val callNotificationKey = "call" screenRecordState.value = ScreenRecordModel.Recording - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) val latest by collectLastValue(underTest.chips) assertIsScreenRecordChip(latest!!.primary) - assertIsCallChip(latest!!.secondary) + assertIsCallChip(latest!!.secondary, callNotificationKey) } @Test @@ -240,15 +243,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun chips_shareToAppShowAndCallShow_primaryIsShareToAppSecondaryIsCall() = testScope.runTest { + val callNotificationKey = "call" screenRecordState.value = ScreenRecordModel.DoingNothing mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) val latest by collectLastValue(underTest.chips) assertIsShareToAppChip(latest!!.primary) - assertIsCallChip(latest!!.secondary) + assertIsCallChip(latest!!.secondary, callNotificationKey) } @Test @@ -258,25 +264,31 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + val callNotificationKey = "call" + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) val latest by collectLastValue(underTest.primaryChip) - assertIsCallChip(latest) + assertIsCallChip(latest, callNotificationKey) } @Test fun chips_onlyCallShown_primaryIsCallSecondaryIsHidden() = testScope.runTest { + val callNotificationKey = "call" screenRecordState.value = ScreenRecordModel.DoingNothing // MediaProjection covers both share-to-app and cast-to-other-device mediaProjectionState.value = MediaProjectionState.NotProjecting - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) val latest by collectLastValue(underTest.chips) - assertIsCallChip(latest!!.primary) + assertIsCallChip(latest!!.primary, callNotificationKey) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @@ -285,7 +297,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.chips) - val icon = mock<StatusBarIconView>() + val icon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -296,7 +308,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) ) - assertIsNotifChip(latest!!.primary, icon) + assertIsNotifChip(latest!!.primary, icon, "notif") assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @@ -305,8 +317,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.chips) - val firstIcon = mock<StatusBarIconView>() - val secondIcon = mock<StatusBarIconView>() + val firstIcon = createStatusBarIconViewOrNull() + val secondIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -324,8 +336,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) ) - assertIsNotifChip(latest!!.primary, firstIcon) - assertIsNotifChip(latest!!.secondary, secondIcon) + assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif") + assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif") } @Test @@ -333,9 +345,9 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.chips) - val firstIcon = mock<StatusBarIconView>() - val secondIcon = mock<StatusBarIconView>() - val thirdIcon = mock<StatusBarIconView>() + val firstIcon = createStatusBarIconViewOrNull() + val secondIcon = createStatusBarIconViewOrNull() + val thirdIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -359,8 +371,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ) ) - assertIsNotifChip(latest!!.primary, firstIcon) - assertIsNotifChip(latest!!.secondary, secondIcon) + assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif") + assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif") } @Test @@ -368,8 +380,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.chips) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - val firstIcon = mock<StatusBarIconView>() + val callNotificationKey = "call" + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) + + val firstIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -380,43 +396,47 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { ), activeNotificationModel( key = "secondNotif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = PromotedNotificationContentModel.Builder("secondNotif").build(), ), ) ) - assertIsCallChip(latest!!.primary) - assertIsNotifChip(latest!!.secondary, firstIcon) + assertIsCallChip(latest!!.primary, callNotificationKey) + assertIsNotifChip(latest!!.secondary, firstIcon, "firstNotif") } @Test fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() = testScope.runTest { + val callNotificationKey = "call" val latest by collectLastValue(underTest.chips) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) screenRecordState.value = ScreenRecordModel.Recording setNotifs( listOf( activeNotificationModel( key = "notif", - statusBarChipIcon = mock<StatusBarIconView>(), + statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = PromotedNotificationContentModel.Builder("notif").build(), ) ) ) assertIsScreenRecordChip(latest!!.primary) - assertIsCallChip(latest!!.secondary) + assertIsCallChip(latest!!.secondary, callNotificationKey) } @Test fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = testScope.runTest { + val callNotificationKey = "call" // Start with just the lowest priority chip shown - val notifIcon = mock<StatusBarIconView>() + val notifIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -433,13 +453,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsNotifChip(latest, notifIcon) + assertIsNotifChip(latest, notifIcon, "notif") // WHEN the higher priority call chip is added - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) // THEN the higher priority call chip is used - assertIsCallChip(latest) + assertIsCallChip(latest, callNotificationKey) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -462,12 +484,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { @Test fun primaryChip_highestPriorityChipRemoved_showsNextPriorityChip() = testScope.runTest { + val callNotificationKey = "call" // WHEN all chips are active screenRecordState.value = ScreenRecordModel.Recording mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - val notifIcon = mock<StatusBarIconView>() + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) + val notifIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -493,20 +518,21 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN the lower priority call is used - assertIsCallChip(latest) + assertIsCallChip(latest, callNotificationKey) // WHEN the higher priority call is removed callRepo.setOngoingCallState(OngoingCallModel.NoCall) // THEN the lower priority notif is used - assertIsNotifChip(latest, notifIcon) + assertIsNotifChip(latest, notifIcon, "notif") } @Test fun chips_movesChipsAroundAccordingToPriority() = testScope.runTest { + val callNotificationKey = "call" // Start with just the lowest priority chip shown - val notifIcon = mock<StatusBarIconView>() + val notifIcon = createStatusBarIconViewOrNull() setNotifs( listOf( activeNotificationModel( @@ -523,16 +549,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chips) - assertIsNotifChip(latest!!.primary, notifIcon) + assertIsNotifChip(latest!!.primary, notifIcon, "notif") assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) // WHEN the higher priority call chip is added - callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + callRepo.setOngoingCallState( + inCallModel(startTimeMs = 34, notificationKey = callNotificationKey) + ) // THEN the higher priority call chip is used as primary and notif is demoted to // secondary - assertIsCallChip(latest!!.primary) - assertIsNotifChip(latest!!.secondary, notifIcon) + assertIsCallChip(latest!!.primary, callNotificationKey) + assertIsNotifChip(latest!!.secondary, notifIcon, "notif") // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -545,7 +573,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // THEN the higher priority media projection chip is used as primary and call is demoted // to secondary (and notif is dropped altogether) assertIsShareToAppChip(latest!!.primary) - assertIsCallChip(latest!!.secondary) + assertIsCallChip(latest!!.secondary, callNotificationKey) // WHEN the higher priority screen record chip is added screenRecordState.value = ScreenRecordModel.Recording @@ -559,13 +587,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { // THEN media projection and notif remain assertIsShareToAppChip(latest!!.primary) - assertIsNotifChip(latest!!.secondary, notifIcon) + assertIsNotifChip(latest!!.secondary, notifIcon, "notif") // WHEN media projection is dropped mediaProjectionState.value = MediaProjectionState.NotProjecting // THEN notif is promoted to primary - assertIsNotifChip(latest!!.primary, notifIcon) + assertIsNotifChip(latest!!.primary, notifIcon, "notif") assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt index a70d24efada7..912633c874ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -28,11 +28,11 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -59,10 +59,9 @@ class RenderStageManagerTest : SysuiTestCase() { fun setUp() { renderStageManager = RenderStageManager() renderStageManager.attach(shadeListBuilder) - - val captor = argumentCaptor<ShadeListBuilder.OnRenderListListener>() - verify(shadeListBuilder).setOnRenderListListener(captor.capture()) - onRenderListListener = captor.lastValue + onRenderListListener = withArgCaptor { + verify(shadeListBuilder).setOnRenderListListener(capture()) + } } private fun setUpRenderer() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt index 34f46088ad79..3d5d1eddf581 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -48,7 +47,6 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) @SmallTest -@EnableFlags(FooterViewRefactor.FLAG_NAME) class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index 615f4b01df9b..daa1db2d49fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.footer.ui.view; -import static com.android.systemui.log.LogAssertKt.assertLogsWtf; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -34,7 +32,6 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.view.LayoutInflater; import android.view.View; @@ -44,7 +41,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import org.junit.Before; @@ -62,8 +58,7 @@ public class FooterViewTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getFlags() { - return FlagsParameterization.progressionOf(FooterViewRefactor.FLAG_NAME, - NotifRedesignFooter.FLAG_NAME); + return FlagsParameterization.allCombinationsOf(NotifRedesignFooter.FLAG_NAME); } public FooterViewTest(FlagsParameterization flags) { @@ -106,24 +101,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void setHistoryShown() { - mView.showHistory(true); - assertTrue(mView.isHistoryShown()); - assertTrue(((TextView) mView.findViewById(R.id.manage_text)) - .getText().toString().contains("History")); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void setHistoryNotShown() { - mView.showHistory(false); - assertFalse(mView.isHistoryShown()); - assertTrue(((TextView) mView.findViewById(R.id.manage_text)) - .getText().toString().contains("Manage")); - } - - @Test public void testPerformVisibilityAnimation() { mView.setVisible(false /* visible */, false /* animate */); assertFalse(mView.isVisible()); @@ -140,7 +117,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(NotifRedesignFooter.FLAG_NAME) public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() { int resId = R.string.manage_notifications_history_text; @@ -160,16 +136,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetManageOrHistoryButtonText_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.manage_notifications_history_text; - assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(NotifRedesignFooter.FLAG_NAME) public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() { int resId = R.string.manage_notifications_history_text; @@ -189,16 +155,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.accessibility_clear_all; - assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { int resId = R.string.clear_all_notifications_text; mView.setClearAllButtonText(resId); @@ -217,16 +173,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetClearAllButtonText_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.clear_all_notifications_text; - assertLogsWtf(() -> mView.setClearAllButtonText(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { int resId = R.string.accessibility_clear_all; mView.setClearAllButtonDescription(resId); @@ -245,16 +191,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetClearAllButtonDescription_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.accessibility_clear_all; - assertLogsWtf(() -> mView.setClearAllButtonDescription(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageString_resourceOnlyFetchedOnce() { int resId = R.string.unlock_to_see_notif_text; mView.setMessageString(resId); @@ -273,16 +209,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetMessageString_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.unlock_to_see_notif_text; - assertLogsWtf(() -> mView.setMessageString(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageIcon_resourceOnlyFetchedOnce() { int resId = R.drawable.ic_friction_lock_closed; mView.setMessageIcon(resId); @@ -298,15 +224,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetMessageIcon_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.drawable.ic_friction_lock_closed; - assertLogsWtf(() -> mView.setMessageIcon(resId)); - verify(mSpyContext, never()).getDrawable(anyInt()); - } - - @Test public void testSetFooterLabelVisible() { mView.setFooterLabelVisible(true); assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 1adfc2b72214..06b1c432955a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRe import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter import com.android.systemui.testKosmos import com.android.systemui.util.ui.isAnimating @@ -57,7 +56,6 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) @SmallTest -@EnableFlags(FooterViewRefactor.FLAG_NAME) class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index c6cffa9da13b..20cd6c7517e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -25,14 +25,10 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -45,7 +41,6 @@ import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.view.MotionEvent; -import android.view.View; import android.view.ViewTreeObserver; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -57,15 +52,12 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.ExpandHelper; import com.android.systemui.SysuiTestCase; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.DisableSceneContainer; import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.ui.controller.KeyguardMediaController; import com.android.systemui.plugins.ActivityStarter; @@ -78,23 +70,18 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; -import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -106,11 +93,8 @@ import com.android.systemui.statusbar.notification.stack.ui.viewbinder.Notificat import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor; @@ -145,16 +129,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private Provider<IStatusBarService> mStatusBarService; @Mock private NotificationRoundnessManager mNotificationRoundnessManager; @Mock private TunerService mTunerService; - @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private DynamicPrivacyController mDynamicPrivacyController; @Mock private ConfigurationController mConfigurationController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock private ZenModeController mZenModeController; @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private PowerInteractor mPowerInteractor; - @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private WallpaperInteractor mWallpaperInteractor; @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; @Mock private MetricsLogger mMetricsLogger; @@ -164,12 +145,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder; @Mock private NotificationSwipeHelper mNotificationSwipeHelper; @Mock private GroupExpansionManager mGroupExpansionManager; - @Mock private SectionHeaderController mSilentHeaderController; @Mock private NotifPipeline mNotifPipeline; @Mock private NotifCollection mNotifCollection; @Mock private UiEventLogger mUiEventLogger; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; @Mock private Provider<WindowRootView> mWindowRootView; @@ -193,9 +172,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; - private final SeenNotificationsInteractor mSeenNotificationsInteractor = - mKosmos.getSeenNotificationsInteractor(); - private NotificationStackScrollLayoutController mController; private NotificationTestHelper mNotificationTestHelper; @@ -279,114 +255,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true); - initController(/* viewIsAttached= */ true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ true); - - setupShowEmptyShadeViewState(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* notifVisibleInShade= */ true); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - - setupShowEmptyShadeViewState(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* notifVisibleInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - verify(mSysuiStatusBarStateController).addCallback( - mStateListenerArgumentCaptor.capture(), anyInt()); - StatusBarStateController.StateListener stateListener = - mStateListenerArgumentCaptor.getValue(); - stateListener.onStateChanged(SHADE); - mController.getView().removeAllViews(); - - mController.setQsFullScreen(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - - mController.setQsFullScreen(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - - // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we - // hide the empty view. - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* areNotificationsHiddenInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - - // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we - // can show the empty view. - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* areNotificationsHiddenInShade= */ false); - } - - @Test public void testOnUserChange_verifyNotSensitive() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); initController(/* viewIsAttached= */ true); @@ -788,31 +656,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateFooter_remoteInput() { - ArgumentCaptor<RemoteInputController.Callback> callbackCaptor = - ArgumentCaptor.forClass(RemoteInputController.Callback.class); - doNothing().when(mRemoteInputManager).addControllerCallback(callbackCaptor.capture()); - when(mRemoteInputManager.isRemoteInputActive()).thenReturn(false); - initController(/* viewIsAttached= */ true); - verify(mNotificationStackScrollLayout).setIsRemoteInputActive(false); - RemoteInputController.Callback callback = callbackCaptor.getValue(); - callback.onRemoteInputActive(true); - verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() { - initController(/* viewIsAttached= */ true); - mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true); - verify(mNotificationStackScrollLayout).updateFooter(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean()); - } - - @Test public void testAttach_updatesViewStatusBarState() { // GIVEN: Controller is attached initController(/* viewIsAttached= */ true); @@ -844,98 +687,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() { - // GIVEN: Controller is attached, active notifications is empty, - // and mNotificationStackScrollLayout.onKeyguard() is true - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - - // THEN: mNotificationStackScrollLayout should not be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is not empty, - // and mNotificationStackScrollLayout.onKeyguard() is true - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); - mController.getNotifStackController().setNotifStats( - new NotifStats( - /* numActiveNotifs = */ 1, - /* hasNonClearableAlertingNotifs = */ false, - /* hasClearableAlertingNotifs = */ false, - /* hasNonClearableSilentNotifs = */ false, - /* hasClearableSilentNotifs = */ false) - ); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is not empty, - // and mNotificationStackScrollLayout.onKeyguard() is false - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); - mController.getNotifStackController().setNotifStats( - new NotifStats( - /* numActiveNotifs = */ 1, - /* hasNonClearableAlertingNotifs = */ false, - /* hasClearableAlertingNotifs = */ false, - /* hasNonClearableSilentNotifs = */ false, - /* hasClearableSilentNotifs = */ false) - ); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is empty, - // and mNotificationStackScrollLayout.onKeyguard() is false - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() { - initController(/* viewIsAttached= */ true); - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.GONE, - /* to= */ KeyguardState.AOD)); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() { - initController(/* viewIsAttached= */ true); - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.OCCLUDED, - /* to= */ KeyguardState.AOD)); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); - } - - @Test @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) public void sensitiveNotificationProtectionControllerListenerNotRegistered() { initController(/* viewIsAttached= */ true); @@ -996,24 +747,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { return argThat(new LogMatcher(category, type)); } - private void setupShowEmptyShadeViewState(boolean toShow) { - if (toShow) { - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.LOCKSCREEN, - /* to= */ KeyguardState.GONE)); - mController.setQsFullScreen(false); - mController.getView().removeAllViews(); - } else { - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.GONE, - /* to= */ KeyguardState.AOD)); - mController.setQsFullScreen(true); - mController.getView().addContainerView(mock(ExpandableNotificationRow.class)); - } - } - private void initController(boolean viewIsAttached) { when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached); ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); @@ -1033,16 +766,12 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mStatusBarService, mNotificationRoundnessManager, mTunerService, - mDeviceProvisionedController, mDynamicPrivacyController, mConfigurationController, mSysuiStatusBarStateController, mKeyguardMediaController, mKeyguardBypassController, mPowerInteractor, - mPrimaryBouncerInteractor, - mKeyguardTransitionRepo, - mZenModeController, mNotificationLockscreenUserManager, mMetricsLogger, mColorUpdateLogger, @@ -1051,14 +780,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { new FalsingManagerFake(), mNotificationSwipeHelperBuilder, mGroupExpansionManager, - mSilentHeaderController, mNotifPipeline, mNotifCollection, mLockscreenShadeTransitionController, mUiEventLogger, - mRemoteInputManager, mVisibilityLocationProviderDelegator, - mSeenNotificationsInteractor, mViewBinder, mShadeController, mWindowRootView, @@ -1076,7 +802,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } static class LogMatcher implements ArgumentMatcher<LogMaker> { - private int mCategory, mType; + private final int mCategory, mType; LogMatcher(int category, int type) { mCategory = category; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index dcac2941b48b..39cff63f363e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -2,12 +2,10 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.content.pm.PackageManager -import android.platform.test.annotations.DisableFlags import android.platform.test.flag.junit.FlagsParameterization import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager @@ -740,20 +738,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat((footerView.viewState as FooterViewState).hideContent).isTrue() } - @DisableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) - @Test - fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() { - ambientState.isClearAllInProgress = true - ambientState.isShadeExpanded = true - ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack - hostView.removeAllViews() // remove all rows - hostView.addView(footerView) - - stackScrollAlgorithm.resetViewStates(ambientState, 0) - - assertThat((footerView.viewState as FooterViewState).hideContent).isTrue() - } - @Test fun getGapForLocation_onLockscreen_returnsSmallGap() { val gap = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index e592e4b319e3..1b4f9a79557d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -41,7 +40,6 @@ import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRo import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository @@ -63,7 +61,6 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -@EnableFlags(FooterViewRefactor.FLAG_NAME) class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index d174484219ff..2e12336f6e93 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -40,7 +40,6 @@ import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.TestableLooper; @@ -610,7 +609,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER) public void testPredictiveBackCallback_registration() { /* verify that a predictive back callback is registered when the bouncer becomes visible */ mBouncerExpansionCallback.onVisibilityChanged(true); @@ -625,7 +623,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER) public void testPredictiveBackCallback_invocationHidesBouncer() { mBouncerExpansionCallback.onVisibilityChanged(true); /* capture the predictive back callback during registration */ @@ -643,7 +640,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER) public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())) .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin); @@ -663,7 +659,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER) public void testPredictiveBackCallback_forwardsBackDispatches() { mBouncerExpansionCallback.onVisibilityChanged(true); /* capture the predictive back callback during registration */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 0652a835cb7c..650fa7ce46de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -31,7 +31,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; @@ -41,7 +40,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.animation.back.BackAnimationSpec; @@ -137,7 +135,6 @@ public class SystemUIDialogTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS) public void usePredictiveBackAnimFlag() { final SystemUIDialog dialog = new SystemUIDialog(mContext); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index 3d6882c3fdaf..c6bae197ad76 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -18,9 +18,8 @@ package com.android.systemui.statusbar.policy.domain.interactor import android.app.AutomaticZenRule import android.app.Flags -import android.app.NotificationManager.INTERRUPTION_FILTER_NONE -import android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY import android.app.NotificationManager.Policy +import android.media.AudioManager import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION @@ -34,6 +33,7 @@ import androidx.test.filters.SmallTest import com.android.internal.R import com.android.settingslib.notification.data.repository.updateNotificationPolicy import com.android.settingslib.notification.modes.TestModeBuilder +import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope @@ -402,115 +402,124 @@ class ZenModeInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI) - fun activeModesBlockingEverything_hasModesWithFilterNone() = + fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() = testScope.runTest { - val blockingEverything by collectLastValue(underTest.activeModesBlockingEverything) + val blockingMedia by + collectLastValue( + underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC)) + ) zenModeRepository.addModes( listOf( TestModeBuilder() - .setName("Filter=None, Not active") - .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .setName("Blocks media, Not active") + .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) .setActive(false) .build(), TestModeBuilder() - .setName("Filter=Priority, Active") - .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setName("Allows media, Active") + .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Filter=None, Active") - .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .setName("Blocks media, Active") + .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Filter=None, Active Too") - .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .setName("Blocks media, Active Too") + .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) .setActive(true) .build(), ) ) runCurrent() - assertThat(blockingEverything!!.mainMode!!.name).isEqualTo("Filter=None, Active") - assertThat(blockingEverything!!.modeNames) - .containsExactly("Filter=None, Active", "Filter=None, Active Too") + assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active") + assertThat(blockingMedia!!.modeNames) + .containsExactly("Blocks media, Active", "Blocks media, Active Too") .inOrder() } @Test @EnableFlags(Flags.FLAG_MODES_UI) - fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() = + fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() = testScope.runTest { - val blockingMedia by collectLastValue(underTest.activeModesBlockingMedia) + val blockingAlarms by + collectLastValue( + underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM)) + ) zenModeRepository.addModes( listOf( TestModeBuilder() - .setName("Blocks media, Not active") - .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) + .setName("Blocks alarms, Not active") + .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) .setActive(false) .build(), TestModeBuilder() - .setName("Allows media, Active") - .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build()) + .setName("Allows alarms, Active") + .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Blocks media, Active") - .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) + .setName("Blocks alarms, Active") + .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Blocks media, Active Too") - .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build()) + .setName("Blocks alarms, Active Too") + .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) .setActive(true) .build(), ) ) runCurrent() - assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active") - assertThat(blockingMedia!!.modeNames) - .containsExactly("Blocks media, Active", "Blocks media, Active Too") + assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active") + assertThat(blockingAlarms!!.modeNames) + .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too") .inOrder() } @Test @EnableFlags(Flags.FLAG_MODES_UI) - fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() = + fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() = testScope.runTest { - val blockingAlarms by collectLastValue(underTest.activeModesBlockingAlarms) + val blockingSystem by + collectLastValue( + underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM)) + ) zenModeRepository.addModes( listOf( TestModeBuilder() - .setName("Blocks alarms, Not active") - .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) + .setName("Blocks system, Not active") + .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build()) .setActive(false) .build(), TestModeBuilder() - .setName("Allows alarms, Active") - .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build()) + .setName("Allows system, Active") + .setZenPolicy(ZenPolicy.Builder().allowSystem(true).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Blocks alarms, Active") - .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) + .setName("Blocks system, Active") + .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build()) .setActive(true) .build(), TestModeBuilder() - .setName("Blocks alarms, Active Too") - .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build()) + .setName("Blocks system, Active Too") + .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build()) .setActive(true) .build(), ) ) runCurrent() - assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active") - assertThat(blockingAlarms!!.modeNames) - .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too") + assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active") + assertThat(blockingSystem!!.modeNames) + .containsExactly("Blocks system, Active", "Blocks system, Active Too") .inOrder() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt deleted file mode 100644 index 2ad1124d72d4..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.touchpad.tutorial.ui - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toTutorialActionState -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class StateTransitionsTest : SysuiTestCase() { - - companion object { - private const val START_MARKER = "startMarker" - private const val END_MARKER = "endMarker" - private const val SUCCESS_ANIMATION = 0 - } - - // needed to simulate caching last state as it's used to create new state - private var lastState: TutorialActionState = NotStarted - - private fun GestureState.toTutorialActionState(): TutorialActionState { - val newState = - this.toGestureUiState( - progressStartMarker = START_MARKER, - progressEndMarker = END_MARKER, - successAnimation = SUCCESS_ANIMATION, - ) - .toTutorialActionState(lastState) - lastState = newState - return lastState - } - - @Test - fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() { - val happyPath = - listOf( - GestureState.NotStarted, - GestureState.InProgress(0f), - GestureState.InProgress(0.5f), - GestureState.InProgress(1f), - GestureState.Finished, - ) - - val resultingStates = mutableListOf<TutorialActionState>() - happyPath.forEach { resultingStates.add(it.toTutorialActionState()) } - - assertThat(resultingStates) - .containsExactly( - NotStarted, - InProgress(0f, START_MARKER, END_MARKER), - InProgress(0.5f, START_MARKER, END_MARKER), - InProgress(1f, START_MARKER, END_MARKER), - Finished(SUCCESS_ANIMATION), - ) - .inOrder() - } - - @Test - fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() { - val errorPath = - listOf( - GestureState.NotStarted, - GestureState.InProgress(0f), - GestureState.Error, - GestureState.InProgress(0.5f), - GestureState.InProgress(1f), - GestureState.Finished, - ) - - val resultingStates = mutableListOf<TutorialActionState>() - errorPath.forEach { resultingStates.add(it.toTutorialActionState()) } - - assertThat(resultingStates) - .containsExactly( - NotStarted, - InProgress(0f, START_MARKER, END_MARKER), - Error, - InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)), - InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)), - Finished(SUCCESS_ANIMATION), - ) - .inOrder() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt index 4aec88e8497b..d752046f4791 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources @@ -71,8 +71,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "gesture to L", - progressEndMarker = "end progress L", + startMarker = "gesture to L", + endMarker = "end progress L", ), ) } @@ -85,8 +85,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "gesture to R", - progressEndMarker = "end progress R", + startMarker = "gesture to R", + endMarker = "end progress R", ), ) } @@ -114,7 +114,7 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { kosmos.runTest { fun performBackGesture() = ThreeFingerGesture.swipeLeft().forEach { viewModel.handleEvent(it) } - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performBackGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -134,15 +134,21 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertProgressWhileMovingFingers(deltaX: Float, expected: GestureUiState) { + private fun Kosmos.assertProgressWhileMovingFingers( + deltaX: Float, + expected: TutorialActionState, + ) { assertStateAfterEvents( events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expected = expected, ) } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt index 65a995dcd043..7862fd32ca04 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity @@ -86,8 +86,8 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "drag with gesture", - progressEndMarker = "release playback realtime", + startMarker = "drag with gesture", + endMarker = "release playback realtime", ), ) } @@ -108,7 +108,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performHomeGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -121,7 +121,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performHomeGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -147,8 +147,11 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt index 1bc60b67095e..6180fa98b1cd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity @@ -89,8 +89,8 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "drag with gesture", - progressEndMarker = "onPause", + startMarker = "drag with gesture", + endMarker = "onPause", ), ) } @@ -111,7 +111,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performRecentAppsGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -124,7 +124,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performRecentAppsGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -150,8 +150,11 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt new file mode 100644 index 000000000000..c113dd9e1eff --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.touchpad.tutorial.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted +import com.android.systemui.kosmos.collectValues +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TouchpadTutorialScreenViewModelTest : SysuiTestCase() { + + companion object { + private const val START_MARKER = "startMarker" + private const val END_MARKER = "endMarker" + private const val SUCCESS_ANIMATION = 0 + } + + private val kosmos = testKosmos() + private val animationProperties = + TutorialAnimationProperties( + progressStartMarker = START_MARKER, + progressEndMarker = END_MARKER, + successAnimation = SUCCESS_ANIMATION, + ) + + @Before + fun before() { + kosmos.useUnconfinedTestDispatcher() + } + + @Test + fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() = + kosmos.runTest { + val happyPath: Flow<Pair<GestureState, TutorialAnimationProperties>> = + listOf( + GestureState.NotStarted, + GestureState.InProgress(0f), + GestureState.InProgress(0.5f), + GestureState.InProgress(1f), + GestureState.Finished, + ) + .map { it to animationProperties } + .asFlow() + + val resultingStates by collectValues(happyPath.mapToTutorialState()) + + assertThat(resultingStates) + .containsExactly( + NotStarted, + InProgress(0f, START_MARKER, END_MARKER), + InProgress(0.5f, START_MARKER, END_MARKER), + InProgress(1f, START_MARKER, END_MARKER), + Finished(SUCCESS_ANIMATION), + ) + .inOrder() + } + + @Test + fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() = + kosmos.runTest { + val errorPath: Flow<Pair<GestureState, TutorialAnimationProperties>> = + listOf( + GestureState.NotStarted, + GestureState.InProgress(0f), + GestureState.Error, + GestureState.InProgress(0.5f), + GestureState.InProgress(1f), + GestureState.Finished, + ) + .map { it to animationProperties } + .asFlow() + + val resultingStates by collectValues(errorPath.mapToTutorialState()) + + assertThat(resultingStates) + .containsExactly( + NotStarted, + InProgress(0f, START_MARKER, END_MARKER), + Error, + InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)), + InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)), + Finished(SUCCESS_ANIMATION), + ) + .inOrder() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt index d3071f87f744..51cac6976362 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt @@ -23,66 +23,40 @@ import android.platform.test.annotations.EnableFlags import android.service.notification.ZenPolicy import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.logging.uiEventLogger import com.android.settingslib.notification.modes.TestModeBuilder import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository -import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos -import com.android.systemui.volume.domain.interactor.audioVolumeInteractor -import com.android.systemui.volume.shared.volumePanelLogger import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class AudioStreamSliderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() - private val testScope = kosmos.testScope private val zenModeRepository = kosmos.fakeZenModeRepository - private lateinit var mediaStream: AudioStreamSliderViewModel - private lateinit var alarmsStream: AudioStreamSliderViewModel - private lateinit var notificationStream: AudioStreamSliderViewModel - private lateinit var otherStream: AudioStreamSliderViewModel - - @Before - fun setUp() { - mediaStream = audioStreamSliderViewModel(AudioManager.STREAM_MUSIC) - alarmsStream = audioStreamSliderViewModel(AudioManager.STREAM_ALARM) - notificationStream = audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION) - otherStream = audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL) - } - - private fun audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel { - return AudioStreamSliderViewModel( + private fun Kosmos.audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel { + return audioStreamSliderViewModelFactory.create( AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)), - testScope.backgroundScope, - context, - kosmos.audioVolumeInteractor, - kosmos.zenModeInteractor, - kosmos.uiEventLogger, - kosmos.volumePanelLogger, - kosmos.sliderHapticsViewModelFactory, + applicationCoroutineScope, ) } @Test @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) fun slider_media_hasDisabledByModesText() = - testScope.runTest { - val mediaSlider by collectLastValue(mediaStream.slider) + kosmos.runTest { + val mediaSlider by + collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_MUSIC).slider) zenModeRepository.addMode( TestModeBuilder() @@ -112,8 +86,9 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) fun slider_alarms_hasDisabledByModesText() = - testScope.runTest { - val alarmsSlider by collectLastValue(alarmsStream.slider) + kosmos.runTest { + val alarmsSlider by + collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_ALARM).slider) zenModeRepository.addMode( TestModeBuilder() @@ -141,9 +116,10 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) - fun slider_other_hasDisabledByModesText() = - testScope.runTest { - val otherSlider by collectLastValue(otherStream.slider) + fun slider_other_hasDisabledText() = + kosmos.runTest { + val otherSlider by + collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL).slider) zenModeRepository.addMode( TestModeBuilder() @@ -154,20 +130,17 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() { ) runCurrent() - assertThat(otherSlider!!.disabledMessage) - .isEqualTo("Unavailable because Everything blocked is on") - - zenModeRepository.clearModes() - runCurrent() - assertThat(otherSlider!!.disabledMessage).isEqualTo("Unavailable") } @Test @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) fun slider_notification_hasSpecialDisabledText() = - testScope.runTest { - val notificationSlider by collectLastValue(notificationStream.slider) + kosmos.runTest { + val notificationSlider by + collectLastValue( + audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION).slider + ) runCurrent() assertThat(notificationSlider!!.disabledMessage) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cd37c22c8bc3..a01ff3d5258f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3798,7 +3798,7 @@ <!-- Title at the top of the keyboard shortcut helper UI when in customize mode. The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> - <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string> + <string name="shortcut_helper_customize_mode_title">Customize shortcuts</string> <!-- Title at the top of the keyboard shortcut helper remove shortcut dialog. The helper is a component that shows the user which keyboard shortcuts they can use. Also allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 7d220b505aa0..6e23a0783c9d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -21,11 +21,9 @@ import android.util.Log; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; -import android.window.TaskSnapshot; import android.window.WindowAnimationState; import com.android.internal.os.IResultReceiver; -import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.wm.shell.recents.IRecentsAnimationController; public class RecentsAnimationControllerCompat { @@ -40,18 +38,6 @@ public class RecentsAnimationControllerCompat { mAnimationController = animationController; } - public ThumbnailData screenshotTask(int taskId) { - try { - final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); - if (snapshot != null) { - return ThumbnailData.fromSnapshot(snapshot); - } - } catch (RemoteException e) { - Log.e(TAG, "Failed to screenshot task", e); - } - return new ThumbnailData(); - } - public void setInputConsumerEnabled(boolean enabled) { try { mAnimationController.setInputConsumerEnabled(enabled); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index d3c02e6f6449..b159a70066ce 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -29,7 +29,6 @@ import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor; import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -93,10 +92,8 @@ public class KeyguardPinViewController mPasswordEntry.setUserActivityListener(this::onUserInput); mView.onDevicePostureChanged(mPostureController.getDevicePosture()); mPostureController.addCallback(mPostureCallback); - if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) { - mPasswordEntry.setUsePinShapes(true); - updateAutoConfirmationState(); - } + mPasswordEntry.setUsePinShapes(true); + updateAutoConfirmationState(); } protected void onUserInput() { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index e02e3fbc339b..10f060c13a59 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -22,10 +22,10 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAK import android.annotation.MainThread; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Trace; import android.util.Log; import android.view.Display; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.util.Preconditions; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.DozeScope; @@ -314,7 +314,7 @@ public class DozeMachine { mState = newState; mDozeLog.traceState(newState); - Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal()); + TrackTracer.instantForGroup("keyguard", "doze_machine_state", newState.ordinal()); updatePulseReason(newState, oldState, pulseReason); performTransitionOnComponents(oldState, newState); diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt index 29785959de18..9596a540b63b 100644 --- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt @@ -20,10 +20,12 @@ import android.content.Context import android.hardware.input.InputManager import android.hardware.input.KeyGestureEvent import androidx.datastore.core.DataStore +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStoreFile @@ -68,7 +70,7 @@ interface ContextualEducationRepository { suspend fun updateGestureEduModel( gestureType: GestureType, - transform: (GestureEduModel) -> GestureEduModel + transform: (GestureEduModel) -> GestureEduModel, ) suspend fun updateEduDeviceConnectionTime( @@ -149,6 +151,8 @@ constructor( String.format(DATASTORE_DIR, userId) ) }, + corruptionHandler = + ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }), scope = newDsScope, ) dataStoreScope = newDsScope @@ -159,7 +163,7 @@ constructor( private fun getGestureEduModel( gestureType: GestureType, - preferences: Preferences + preferences: Preferences, ): GestureEduModel { return GestureEduModel( signalCount = preferences[getSignalCountKey(gestureType)] ?: 0, @@ -183,7 +187,7 @@ constructor( override suspend fun updateGestureEduModel( gestureType: GestureType, - transform: (GestureEduModel) -> GestureEduModel + transform: (GestureEduModel) -> GestureEduModel, ) { datastore.filterNotNull().first().edit { preferences -> val currentModel = getGestureEduModel(gestureType, preferences) @@ -193,17 +197,17 @@ constructor( setInstant( preferences, updatedModel.lastShortcutTriggeredTime, - getLastShortcutTriggeredTimeKey(gestureType) + getLastShortcutTriggeredTimeKey(gestureType), ) setInstant( preferences, updatedModel.usageSessionStartTime, - getUsageSessionStartTimeKey(gestureType) + getUsageSessionStartTimeKey(gestureType), ) setInstant( preferences, updatedModel.lastEducationTime, - getLastEducationTimeKey(gestureType) + getLastEducationTimeKey(gestureType), ) } } @@ -220,12 +224,12 @@ constructor( setInstant( preferences, updatedModel.keyboardFirstConnectionTime, - getKeyboardFirstConnectionTimeKey() + getKeyboardFirstConnectionTimeKey(), ) setInstant( preferences, updatedModel.touchpadFirstConnectionTime, - getTouchpadFirstConnectionTimeKey() + getTouchpadFirstConnectionTimeKey(), ) } } @@ -235,7 +239,7 @@ constructor( keyboardFirstConnectionTime = preferences[getKeyboardFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) }, touchpadFirstConnectionTime = - preferences[getTouchpadFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) } + preferences[getTouchpadFirstConnectionTimeKey()]?.let { Instant.ofEpochSecond(it) }, ) } @@ -263,7 +267,7 @@ constructor( private fun setInstant( preferences: MutablePreferences, instant: Instant?, - key: Preferences.Key<Long> + key: Preferences.Key<Long>, ) { if (instant != null) { // Use epochSecond because an instant is defined as a signed long (64bit number) of diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 63ac783ad42b..129a6bb72996 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -35,7 +35,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression import com.android.systemui.statusbar.notification.shared.NotificationMinimalism @@ -57,7 +56,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token NotificationMinimalism.token dependsOn NotificationThrottleHun.token - ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token ModesEmptyShadeFix.token dependsOn modesUi // SceneContainer dependencies diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c039e0188064..2c33c0b4403b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -76,21 +76,10 @@ object Flags { val LOCKSCREEN_CUSTOM_CLOCKS = resourceBooleanFlag(R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks") - /** - * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository - * will occur in stages. This is one stage of many to come. - */ - // TODO(b/255607168): Tracking Bug - @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1") - /** Flag to control the revamp of keyguard biometrics progress animation */ // TODO(b/244313043): Tracking bug @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") - // flag for controlling auto pin confirmation and material u shapes in bouncer - @JvmField - val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation") - /** Enables code to show contextual loyalty cards in wallet entrypoints */ // TODO(b/294110497): Tracking Bug @JvmField @@ -100,10 +89,6 @@ object Flags { // TODO(b/242908637): Tracking Bug @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview") - /** Inflate and bind views upon emitting a blueprint value . */ - // TODO(b/297365780): Tracking Bug - @JvmField val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard") - /** Enables UI updates for AI wallpapers in the wallpaper picker. */ // TODO(b/267722622): Tracking Bug @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp") diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt index 19a19d551613..c702ba9f401e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt @@ -25,6 +25,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.CommandQueue @@ -41,6 +42,7 @@ constructor( private val stateRepository: ShortcutHelperStateRepository, private val activityStarter: ActivityStarter, @Background private val backgroundScope: CoroutineScope, + private val customInputGesturesRepository: CustomInputGesturesRepository ) : CoreStartable { override fun start() { registerBroadcastReceiver( @@ -55,6 +57,10 @@ constructor( action = Intent.ACTION_CLOSE_SYSTEM_DIALOGS, onReceive = { stateRepository.hide() }, ) + registerBroadcastReceiver( + action = Intent.ACTION_USER_SWITCHED, + onReceive = { customInputGesturesRepository.refreshCustomInputGestures() }, + ) commandQueue.addCallback( object : CommandQueue.Callbacks { override fun dismissKeyboardShortcutsMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt index 36cd40052041..e5c638cbdfba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt @@ -25,6 +25,7 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RES import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS import android.hardware.input.InputSettings import android.util.Log +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER @@ -37,6 +38,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject import kotlin.coroutines.CoroutineContext +@SysUISingleton class CustomInputGesturesRepository @Inject constructor(private val userTracker: UserTracker, @@ -56,7 +58,7 @@ constructor(private val userTracker: UserTracker, val customInputGestures = _customInputGesture.onStart { refreshCustomInputGestures() } - private fun refreshCustomInputGestures() { + fun refreshCustomInputGestures() { setCustomInputGestures(inputGestures = retrieveCustomInputGestures()) } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt index d7be5e622276..e255bdea6100 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt @@ -27,14 +27,19 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking @@ -66,6 +71,11 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to MultiTasking, KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to MultiTasking, KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking, + KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to MultiTasking, + KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to MultiTasking, + KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to MultiTasking, + KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to MultiTasking, + KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to MultiTasking, // App Category KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories, @@ -102,15 +112,23 @@ class InputGestureMaps @Inject constructor(private val context: Context) { R.string.shortcutHelper_category_split_screen, KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to R.string.shortcutHelper_category_split_screen, + KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to + R.string.shortcutHelper_category_split_screen, + KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to + R.string.shortcutHelper_category_split_screen, + KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to + R.string.shortcutHelper_category_split_screen, + KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to + R.string.shortcutHelper_category_split_screen, + KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to R.string.shortcutHelper_category_split_screen, // App Category - KEY_GESTURE_TYPE_LAUNCH_APPLICATION to - R.string.keyboard_shortcut_group_applications, + KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications, ) /** - * App Category shortcut labels are mapped dynamically based on intent - * see [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData] + * App Category shortcut labels are mapped dynamically based on intent see + * [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData] */ val gestureToInternalKeyboardShortcutInfoLabelResIdMap = mapOf( @@ -136,6 +154,16 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to R.string.system_multitasking_lhs, KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to R.string.system_multitasking_rhs, KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to R.string.system_multitasking_full_screen, + KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to + R.string.system_desktop_mode_snap_left_window, + KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to + R.string.system_desktop_mode_snap_right_window, + KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to + R.string.system_desktop_mode_minimize_window, + KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to + R.string.system_desktop_mode_toggle_maximize_window, + KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to + R.string.system_multitasking_move_to_next_display, ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index d40fe468b0a5..591383999182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -538,27 +538,30 @@ public class KeyguardService extends Service { @Override // Binder interface public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, boolean + powerButtonLaunchGestureTriggered) { trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason - + " cameraGestureTriggered=" + cameraGestureTriggered); + + " powerButtonLaunchTriggered=" + powerButtonLaunchGestureTriggered); checkPermission(); mKeyguardViewMediator.onFinishedGoingToSleep( WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason), - cameraGestureTriggered); - mPowerInteractor.onFinishedGoingToSleep(cameraGestureTriggered); + powerButtonLaunchGestureTriggered); + mPowerInteractor.onFinishedGoingToSleep(powerButtonLaunchGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.FINISHED_GOING_TO_SLEEP); } @Override // Binder interface public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, + boolean powerButtonLaunchGestureTriggered) { trace("onStartedWakingUp pmWakeReason=" + pmWakeReason - + " cameraGestureTriggered=" + cameraGestureTriggered); + + " powerButtonLaunchGestureTriggered=" + powerButtonLaunchGestureTriggered); Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp"); checkPermission(); - mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); - mPowerInteractor.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, + powerButtonLaunchGestureTriggered); + mPowerInteractor.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason); Trace.endSection(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 63ac5094c400..e65cd9873491 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -109,6 +109,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; @@ -3983,7 +3984,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void setPendingLock(boolean hasPendingLock) { mPendingLock = hasPendingLock; - Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0); + TrackTracer.instantForGroup("keyguard", "pendingLock", mPendingLock ? 1 : 0); } private boolean isViewRootReady() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java index 633628f1167e..c3182003227f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java @@ -16,8 +16,7 @@ package com.android.systemui.keyguard; -import android.os.Trace; - +import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.power.domain.interactor.PowerInteractor; @@ -80,7 +79,7 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme private void setScreenState(int screenState) { mScreenState = screenState; - Trace.traceCounter(Trace.TRACE_TAG_APP, "screenState", screenState); + TrackTracer.instantForGroup("screen", "screenState", screenState); } public interface Observer { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index c0ffda6640b2..c261cfefb2b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -24,11 +24,11 @@ import android.graphics.Point; import android.os.Bundle; import android.os.PowerManager; import android.os.RemoteException; -import android.os.Trace; import android.util.DisplayMetrics; import androidx.annotation.Nullable; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; @@ -197,7 +197,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private void setWakefulness(@Wakefulness int wakefulness) { mWakefulness = wakefulness; - Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness); + TrackTracer.instantForGroup("screen", "wakefulness", wakefulness); } private void updateLastWakeOriginLocation() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index af37eea102ca..1a2238cfbc9e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -234,8 +234,6 @@ public abstract class MediaOutputBaseAdapter extends (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams(); params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable() : mController.getItemMarginEndDefault(); - mTitleIcon.setBackgroundTintList( - ColorStateList.valueOf(mController.getColorItemContent())); } void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java index 1dbb31708fe1..15afd22a27d8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java @@ -37,9 +37,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.AudioManager; @@ -535,17 +532,9 @@ public class MediaSwitchingController // Use default Bluetooth device icon to handle getIcon() is null case. drawable = mContext.getDrawable(com.android.internal.R.drawable.ic_bt_headphones_a2dp); } - if (!(drawable instanceof BitmapDrawable)) { - setColorFilter(drawable, isActiveItem(device)); - } return BluetoothUtils.createIconWithDrawable(drawable); } - void setColorFilter(Drawable drawable, boolean isActive) { - drawable.setColorFilter(new PorterDuffColorFilter(mColorItemContent, - PorterDuff.Mode.SRC_IN)); - } - boolean isActiveItem(MediaDevice device) { boolean isConnected = mLocalMediaManager.getCurrentConnectedDevice().getId().equals( device.getId()); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index e9b7534f55e6..3f14b55e46a1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -44,6 +44,7 @@ import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.inputmethodservice.InputMethodService.ImeWindowVisibility; +import android.os.Handler; import android.os.RemoteException; import android.os.Trace; import android.util.Log; @@ -61,6 +62,7 @@ import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.view.AppearanceRegion; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; @@ -182,15 +184,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final StatusBarStateController mStatusBarStateController; private DisplayTracker mDisplayTracker; + private final Handler mBgHandler; @Inject public TaskbarDelegate(Context context, LightBarTransitionsController.Factory lightBarTransitionsControllerFactory, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + @Background Handler bgHandler) { mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory; mContext = context; + mBgHandler = bgHandler; mDisplayManager = mContext.getSystemService(DisplayManager.class); mPipListener = (bounds) -> { mEdgeBackGestureHandler.setPipStashExclusionBounds(bounds); @@ -245,7 +250,9 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, new LightBarTransitionsController.DarkIntensityApplier() { @Override public void applyDarkIntensity(float darkIntensity) { - mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + mBgHandler.post(() -> { + mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt index e5ff2529e335..f295c0ccb3de 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt @@ -19,6 +19,7 @@ import android.content.Context import android.hardware.display.DisplayManager import android.os.Bundle import android.os.UserHandle +import android.view.View import androidx.annotation.StyleRes import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger @@ -119,6 +120,12 @@ class ScreenRecordPermissionDialogDelegate( super<BaseMediaProjectionPermissionDialogDelegate>.onCreate(dialog, savedInstanceState) setDialogTitle(R.string.screenrecord_permission_dialog_title) dialog.setTitle(R.string.screenrecord_title) + setStartButtonOnClickListener { v: View? -> + val screenRecordViewBinder: ScreenRecordPermissionViewBinder? = + viewBinder as ScreenRecordPermissionViewBinder? + screenRecordViewBinder?.startButtonOnClicked() + dialog.dismiss() + } setCancelButtonOnClickListener { dialog.dismiss() } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt index 9f7e1ade964a..691bdd4a1b27 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt @@ -79,37 +79,38 @@ class ScreenRecordPermissionViewBinder( override fun bind() { super.bind() initRecordOptionsView() - setStartButtonOnClickListener { _: View? -> - onStartRecordingClicked?.run() - if (selectedScreenShareOption.mode == ENTIRE_SCREEN) { - requestScreenCapture( - captureTarget = null, - displayId = selectedScreenShareOption.displayId, - ) - } - if (selectedScreenShareOption.mode == SINGLE_APP) { - val intent = Intent(dialog.context, MediaProjectionAppSelectorActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + setStartButtonOnClickListener { startButtonOnClicked() } + } - // We can't start activity for result here so we use result receiver to get - // the selected target to capture - intent.putExtra( - MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER, - CaptureTargetResultReceiver(), - ) + fun startButtonOnClicked() { + onStartRecordingClicked?.run() + if (selectedScreenShareOption.mode == ENTIRE_SCREEN) { + requestScreenCapture( + captureTarget = null, + displayId = selectedScreenShareOption.displayId, + ) + } + if (selectedScreenShareOption.mode == SINGLE_APP) { + val intent = Intent(dialog.context, MediaProjectionAppSelectorActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - intent.putExtra( - MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, - hostUserHandle, - ) - intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid) - intent.putExtra( - MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE, - MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name, - ) - activityStarter.startActivity(intent, /* dismissShade= */ true) - } - dialog.dismiss() + // We can't start activity for result here so we use result receiver to get + // the selected target to capture + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER, + CaptureTargetResultReceiver(), + ) + + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, + hostUserHandle, + ) + intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid) + intent.putExtra( + MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE, + MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name, + ) + activityStarter.startActivity(intent, /* dismissShade= */ true) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index e168025b2bf8..c9eb4962ab00 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -162,7 +162,6 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; @@ -1214,14 +1213,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean hasVisibleNotifications() { - if (FooterViewRefactor.isEnabled()) { - return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() - || mMediaDataManager.hasActiveMediaOrRecommendation(); - } else { - return mNotificationStackScrollLayoutController - .getVisibleNotificationCount() != 0 - || mMediaDataManager.hasActiveMediaOrRecommendation(); - } + return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + || mMediaDataManager.hasActiveMediaOrRecommendation(); } @Override @@ -2218,9 +2211,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; - if (!FooterViewRefactor.isEnabled()) { - mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); - } updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index c88e7b827881..14087a0efcfc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -96,8 +95,8 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.SplitShadeStateController; @@ -1022,12 +1021,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum } void updateQsState() { - if (!FooterViewRefactor.isEnabled()) { - // Update full screen state; note that this will be true if the QS panel is only - // partially expanded, and that is fixed with the footer view refactor. - setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled); - } - if (mQsStateUpdateListener != null) { mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling); } @@ -1094,10 +1087,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum // Update the light bar mLightBarController.setQsExpanded(mFullyExpanded); - if (FooterViewRefactor.isEnabled()) { - // Update full screen state - setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled); - } + // Update full screen state + setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled); } float getLockscreenShadeDragProgress() { @@ -2268,10 +2259,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum setExpansionHeight(qsHeight); } - boolean hasNotifications = FooterViewRefactor.isEnabled() - ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() - : mNotificationStackScrollLayoutController.getVisibleNotificationCount() - != 0; + boolean hasNotifications = + mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue(); if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) { // No notifications are visible, let's animate to the height of qs instead if (isQsFragmentCreated()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index ef62d2da9589..a2edd3ab837d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -15,11 +15,18 @@ */ package com.android.systemui.shade.data.repository +import android.annotation.SuppressLint import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch /** Data for the shade, mostly related to expansion of the shade and quick settings. */ interface ShadeRepository { @@ -36,7 +43,7 @@ interface ShadeRepository { * Information about the currently running fling animation, or null if no fling animation is * running. */ - val currentFling: StateFlow<FlingInfo?> + val currentFling: SharedFlow<FlingInfo?> /** * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed, @@ -180,7 +187,8 @@ interface ShadeRepository { /** Business logic for shade interactions */ @SysUISingleton -class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { +class ShadeRepositoryImpl @Inject constructor(@Background val backgroundScope: CoroutineScope) : + ShadeRepository { private val _qsExpansion = MutableStateFlow(0f) @Deprecated("Use ShadeInteractor.qsExpansion instead") override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() @@ -193,8 +201,13 @@ class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { override val udfpsTransitionToFullShadeProgress: StateFlow<Float> = _udfpsTransitionToFullShadeProgress.asStateFlow() - private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null) - override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow() + /** + * Must be a SharedFlow, since the fling is by definition an event and dropping it has extreme + * consequences in some cases (for example, keyguard uses this to decide when to unlock). + */ + @SuppressLint("SharedFlowCreation") + override val currentFling: MutableSharedFlow<FlingInfo?> = + MutableSharedFlow(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val _legacyShadeExpansion = MutableStateFlow(0f) @Deprecated("Use ShadeInteractor.shadeExpansion instead") @@ -294,7 +307,7 @@ class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { } override fun setCurrentFling(info: FlingInfo?) { - _currentFling.value = info + backgroundScope.launch { currentFling.emit(info) } } @Deprecated("Should only be called by NPVC and tests") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt index a747abbc6a6e..1c14d3349027 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt @@ -28,17 +28,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.MeasureScope -import androidx.compose.ui.node.LayoutModifierNode -import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.constrain import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth import kotlinx.coroutines.delay /** Platform-optimized interface for getting current time */ @@ -97,35 +91,3 @@ fun ChronometerText( modifier = modifier.neverDecreaseWidth(), ) } - -/** A modifier that ensures the width of the content only increases and never decreases. */ -private fun Modifier.neverDecreaseWidth(): Modifier { - return this.then(neverDecreaseWidthElement) -} - -private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() { - override fun create(): NeverDecreaseWidthNode { - return NeverDecreaseWidthNode() - } - - override fun update(node: NeverDecreaseWidthNode) { - error("This should never be called") - } -} - -private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode { - private var minWidth = 0 - - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints, - ): MeasureResult { - val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints)) - val width = placeable.width - val height = placeable.height - - minWidth = maxOf(minWidth, width) - - return layout(width, height) { placeable.place(0, 0) } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt new file mode 100644 index 000000000000..505a5fcb18b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.chips.ui.compose.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.constrain + +/** A modifier that ensures the width of the content only increases and never decreases. */ +fun Modifier.neverDecreaseWidth(): Modifier { + return this.then(neverDecreaseWidthElement) +} + +private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() { + override fun create(): NeverDecreaseWidthNode { + return NeverDecreaseWidthNode() + } + + override fun update(node: NeverDecreaseWidthNode) { + error("This should never be called") + } +} + +private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode { + private var minWidth = 0 + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints)) + val width = placeable.width + val height = placeable.height + + minWidth = maxOf(minWidth, width) + + return layout(width, height) { placeable.place(0, 0) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 254b792f8152..d327fc23fd06 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.dagger; -import static com.android.systemui.Flags.predictiveBackAnimateDialogs; - import android.content.Context; import android.os.Handler; import android.os.RemoteException; @@ -28,7 +26,6 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.CoreStartable; import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.animation.AnimationFeatureFlags; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; @@ -226,8 +223,7 @@ public interface CentralSurfacesDependenciesModule { IDreamManager dreamManager, KeyguardStateController keyguardStateController, Lazy<AlternateBouncerInteractor> alternateBouncerInteractor, - InteractionJankMonitor interactionJankMonitor, - AnimationFeatureFlags animationFeatureFlags) { + InteractionJankMonitor interactionJankMonitor) { DialogTransitionAnimator.Callback callback = new DialogTransitionAnimator.Callback() { @Override public boolean isDreaming() { @@ -249,19 +245,6 @@ public interface CentralSurfacesDependenciesModule { return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint(); } }; - return new DialogTransitionAnimator( - mainExecutor, callback, interactionJankMonitor, animationFeatureFlags); - } - - /** */ - @Provides - @SysUISingleton - static AnimationFeatureFlags provideAnimationFeatureFlags() { - return new AnimationFeatureFlags() { - @Override - public boolean isPredictiveBackQsDialogAnim() { - return predictiveBackAnimateDialogs(); - } - }; + return new DialogTransitionAnimator(mainExecutor, callback, interactionJankMonitor); } } 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 32de65be5b5b..d4d3cdf42fb1 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 @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import javax.inject.Inject @@ -43,7 +42,8 @@ internal constructor( private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val renderListInteractor: RenderNotificationListInteractor, private val activeNotificationsInteractor: ActiveNotificationsInteractor, - private val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController, + private val sensitiveNotificationProtectionController: + SensitiveNotificationProtectionController, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -51,14 +51,11 @@ internal constructor( groupExpansionManagerImpl.attach(pipeline) } + // TODO: b/293167744 - Remove controller param. private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { val notifStats = calculateNotifStats(entries) - if (FooterViewRefactor.isEnabled) { - activeNotificationsInteractor.setNotifStats(notifStats) - } else { - controller.setNotifStats(notifStats) - } + activeNotificationsInteractor.setNotifStats(notifStats) renderListInteractor.setRenderedList(entries) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt index fbec6406e9d4..7e2361f24da9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt @@ -26,7 +26,6 @@ import com.android.systemui.shared.notifications.domain.interactor.NotificationS import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.kotlin.FlowDumperImpl @@ -35,7 +34,6 @@ import dagger.assisted.AssistedInject import java.util.Locale import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf @@ -57,9 +55,7 @@ constructor( dumpManager: DumpManager, ) : FlowDumperImpl(dumpManager) { val areNotificationsHiddenInShade: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else if (ModesEmptyShadeFix.isEnabled) { + if (ModesEmptyShadeFix.isEnabled) { zenModeInteractor.areNotificationsHiddenInShade .dumpWhileCollecting("areNotificationsHiddenInShade") .flowOn(bgDispatcher) @@ -70,15 +66,10 @@ constructor( } } - val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - MutableStateFlow(false) - } else { - seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue( - "hasFilteredOutSeenNotifications" - ) - } - } + val hasFilteredOutSeenNotifications: StateFlow<Boolean> = + seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue( + "hasFilteredOutSeenNotifications" + ) val text: Flow<String> by lazy { if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt deleted file mode 100644 index 7e6044eb6869..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.notification.footer.shared - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the FooterView refactor flag state. */ -@Suppress("NOTHING_TO_INLINE") -object FooterViewRefactor { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.notificationsFooterViewRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index d25889820629..a670f69df601 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -41,7 +41,6 @@ import androidx.annotation.NonNull; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.ColorUpdateLogger; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import com.android.systemui.statusbar.notification.row.FooterViewButton; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -63,16 +62,9 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mSettingsButton; private FooterViewButton mHistoryButton; private boolean mShouldBeHidden; - private boolean mShowHistory; - // String cache, for performance reasons. - // Reading them from a Resources object can be quite slow sometimes. - private String mManageNotificationText; - private String mManageNotificationHistoryText; // Footer label private TextView mSeenNotifsFooterTextView; - private String mSeenNotifsFilteredText; - private Drawable mSeenNotifsFilteredIcon; private @StringRes int mClearAllButtonTextId; private @StringRes int mClearAllButtonDescriptionId; @@ -159,8 +151,8 @@ public class FooterView extends StackScrollerDecorView { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); super.dump(pw, args); DumpUtilsKt.withIncreasedIndent(pw, () -> { + // TODO: b/375010573 - update dumps for redesign pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); - pw.println("manageButton showHistory: " + mShowHistory); pw.println("manageButton visibility: " + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); pw.println("dismissButton visibility: " @@ -170,7 +162,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the text label for the "Clear all" button. */ public void setClearAllButtonText(@StringRes int textId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mClearAllButtonTextId == textId) { return; // nothing changed } @@ -187,9 +178,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the accessibility content description for the "Clear all" button. */ public void setClearAllButtonDescription(@StringRes int contentDescriptionId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return; - } if (mClearAllButtonDescriptionId == contentDescriptionId) { return; // nothing changed } @@ -207,7 +195,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the text label for the "Manage"/"History" button. */ public void setManageOrHistoryButtonText(@StringRes int textId) { NotifRedesignFooter.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mManageOrHistoryButtonTextId == textId) { return; // nothing changed } @@ -226,9 +213,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the accessibility content description for the "Clear all" button. */ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) { NotifRedesignFooter.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return; - } if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) { return; // nothing changed } @@ -247,7 +231,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the string for a message to be shown instead of the buttons. */ public void setMessageString(@StringRes int messageId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mMessageStringId == messageId) { return; // nothing changed } @@ -265,7 +248,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */ public void setMessageIcon(@DrawableRes int iconId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mMessageIconId == iconId) { return; // nothing changed } @@ -303,32 +285,17 @@ public class FooterView extends StackScrollerDecorView { mManageOrHistoryButton = findViewById(R.id.manage_text); } mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer); - if (!FooterViewRefactor.isEnabled()) { - updateResources(); - } updateContent(); updateColors(); } /** Show a message instead of the footer buttons. */ public void setFooterLabelVisible(boolean isVisible) { - // In the refactored code, hiding the buttons is handled in the FooterViewModel - if (FooterViewRefactor.isEnabled()) { - if (isVisible) { - mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); - } else { - mSeenNotifsFooterTextView.setVisibility(View.GONE); - } + // Note: hiding the buttons is handled in the FooterViewModel + if (isVisible) { + mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); } else { - if (isVisible) { - mManageOrHistoryButton.setVisibility(View.GONE); - mClearAllButton.setVisibility(View.GONE); - mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); - } else { - mManageOrHistoryButton.setVisibility(View.VISIBLE); - mClearAllButton.setVisibility(View.VISIBLE); - mSeenNotifsFooterTextView.setVisibility(View.GONE); - } + mSeenNotifsFooterTextView.setVisibility(View.GONE); } } @@ -359,10 +326,8 @@ public class FooterView extends StackScrollerDecorView { /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { - if (FooterViewRefactor.isEnabled()) { - if (mClearAllButtonClickListener == listener) return; - mClearAllButtonClickListener = listener; - } + if (mClearAllButtonClickListener == listener) return; + mClearAllButtonClickListener = listener; mClearAllButton.setOnClickListener(listener); } @@ -379,62 +344,17 @@ public class FooterView extends StackScrollerDecorView { || touchY > mContent.getY() + mContent.getHeight(); } - /** Show "History" instead of "Manage" on the start button. */ - public void showHistory(boolean showHistory) { - FooterViewRefactor.assertInLegacyMode(); - if (mShowHistory == showHistory) { - return; - } - mShowHistory = showHistory; - updateContent(); - } - private void updateContent() { - if (FooterViewRefactor.isEnabled()) { - updateClearAllButtonText(); - updateClearAllButtonDescription(); - - if (!NotifRedesignFooter.isEnabled()) { - updateManageOrHistoryButtonText(); - updateManageOrHistoryButtonDescription(); - } - - updateMessageString(); - updateMessageIcon(); - } else { - // NOTE: Prior to the refactor, `updateResources` set the class properties to the right - // string values. It was always being called together with `updateContent`, which - // deals with actually associating those string values with the correct views - // (buttons or text). - // In the new code, the resource IDs are being set in the view binder (through - // setMessageString and similar setters). The setters themselves now deal with - // updating both the resource IDs and the views where appropriate (as in, calling - // `updateMessageString` when the resource ID changes). This eliminates the need for - // `updateResources`, which will eventually be removed. There are, however, still - // situations in which we want to update the views even if the resource IDs didn't - // change, such as configuration changes. - if (mShowHistory) { - mManageOrHistoryButton.setText(mManageNotificationHistoryText); - mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText); - } else { - mManageOrHistoryButton.setText(mManageNotificationText); - mManageOrHistoryButton.setContentDescription(mManageNotificationText); - } - - mClearAllButton.setText(R.string.clear_all_notifications_text); - mClearAllButton.setContentDescription( - mContext.getString(R.string.accessibility_clear_all)); + updateClearAllButtonText(); + updateClearAllButtonDescription(); - mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); - mSeenNotifsFooterTextView - .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); + if (!NotifRedesignFooter.isEnabled()) { + updateManageOrHistoryButtonText(); + updateManageOrHistoryButtonDescription(); } - } - /** Whether the start button shows "History" (true) or "Manage" (false). */ - public boolean isHistoryShown() { - FooterViewRefactor.assertInLegacyMode(); - return mShowHistory; + updateMessageString(); + updateMessageIcon(); } @Override @@ -445,9 +365,6 @@ public class FooterView extends StackScrollerDecorView { } super.onConfigurationChanged(newConfig); updateColors(); - if (!FooterViewRefactor.isEnabled()) { - updateResources(); - } updateContent(); } @@ -502,18 +419,6 @@ public class FooterView extends StackScrollerDecorView { } } - private void updateResources() { - FooterViewRefactor.assertInLegacyMode(); - mManageNotificationText = getContext().getString(R.string.manage_notifications_text); - mManageNotificationHistoryText = getContext() - .getString(R.string.manage_notifications_history_text); - int unlockIconSize = getResources() - .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size); - mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text); - mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed); - mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize); - } - @Override @NonNull public ExpandableViewState createExpandableViewState() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index e724935e3ef4..5696e9f0c5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter.S import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent @@ -144,6 +143,7 @@ class FooterViewModel( ) } +// TODO: b/293167744 - remove this, use new viewmodel style @Module object FooterViewModelModule { @Provides @@ -153,18 +153,13 @@ object FooterViewModelModule { notificationSettingsInteractor: Provider<NotificationSettingsInteractor>, seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, shadeInteractor: Provider<ShadeInteractor>, - ): Optional<FooterViewModel> { - return if (FooterViewRefactor.isEnabled) { - Optional.of( - FooterViewModel( - activeNotificationsInteractor.get(), - notificationSettingsInteractor.get(), - seenNotificationsInteractor.get(), - shadeInteractor.get(), - ) + ): Optional<FooterViewModel> = + Optional.of( + FooterViewModel( + activeNotificationsInteractor.get(), + notificationSettingsInteractor.get(), + seenNotificationsInteractor.get(), + shadeInteractor.get(), ) - } else { - Optional.empty() - } - } + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 071d23283c43..76591ac4e453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -108,7 +108,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil; @@ -703,9 +702,6 @@ public class NotificationStackScrollLayout if (!ModesEmptyShadeFix.isEnabled()) { inflateEmptyShadeView(); } - if (!FooterViewRefactor.isEnabled()) { - inflateFooterView(); - } } /** @@ -741,22 +737,12 @@ public class NotificationStackScrollLayout } void reinflateViews() { - if (!FooterViewRefactor.isEnabled()) { - inflateFooterView(); - updateFooter(); - } if (!ModesEmptyShadeFix.isEnabled()) { inflateEmptyShadeView(); } mSectionsManager.reinflateViews(); } - public void setIsRemoteInputActive(boolean isActive) { - FooterViewRefactor.assertInLegacyMode(); - mIsRemoteInputActive = isActive; - updateFooter(); - } - void sendRemoteInputRowBottomBound(Float bottom) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; if (bottom != null) { @@ -766,43 +752,6 @@ public class NotificationStackScrollLayout mScrollViewFields.sendRemoteInputRowBottomBound(bottom); } - /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ - public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { - FooterViewRefactor.assertInLegacyMode(); - mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications; - } - - @VisibleForTesting - public void updateFooter() { - FooterViewRefactor.assertInLegacyMode(); - if (mFooterView == null || mController == null) { - return; - } - final boolean showHistory = mController.isHistoryEnabled(); - final boolean showDismissView = shouldShowDismissView(); - - updateFooterView(shouldShowFooterView(showDismissView)/* visible */, - showDismissView /* showDismissView */, - showHistory/* showHistory */); - } - - private boolean shouldShowDismissView() { - FooterViewRefactor.assertInLegacyMode(); - return mController.hasActiveClearableNotifications(ROWS_ALL); - } - - private boolean shouldShowFooterView(boolean showDismissView) { - FooterViewRefactor.assertInLegacyMode(); - return (showDismissView || mController.getVisibleNotificationCount() > 0) - && mIsCurrentUserSetup // see: b/193149550 - && !onKeyguard() - && mUpcomingStatusBarState != StatusBarState.KEYGUARD - // quick settings don't affect notifications when not in full screen - && (getQsExpansionFraction() != 1 || !mQsFullScreen) - && !mScreenOffAnimationController.shouldHideNotificationsFooter() - && !mIsRemoteInputActive; - } - void updateBgColor() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -1861,9 +1810,6 @@ public class NotificationStackScrollLayout */ private float getAppearEndPosition() { SceneContainerFlag.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return getAppearEndPositionLegacy(); - } int appearPosition = mAmbientState.getStackTopMargin(); if (mEmptyShadeView.getVisibility() == GONE) { @@ -1883,32 +1829,6 @@ public class NotificationStackScrollLayout return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding()); } - /** - * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't - * need to know about that, so we want to phase this out with the footer view refactor. - */ - private float getAppearEndPositionLegacy() { - FooterViewRefactor.assertInLegacyMode(); - - int appearPosition = mAmbientState.getStackTopMargin(); - int visibleNotifCount = mController.getVisibleNotificationCount(); - if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { - if (isHeadsUpTransition() - || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { - if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) { - appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; - } - appearPosition += getTopHeadsUpPinnedHeight() - + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); - } else if (mShelf.getVisibility() != GONE) { - appearPosition += mShelf.getIntrinsicHeight(); - } - } else { - appearPosition = mEmptyShadeView.getHeight(); - } - return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding()); - } - private boolean isHeadsUpTransition() { return mAmbientState.getTrackedHeadsUpRow() != null; } @@ -1928,8 +1848,7 @@ public class NotificationStackScrollLayout // This can't use expansion fraction as that goes only from 0 to 1. Also when // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3 // and that makes translation jump immediately. - float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition() - : getAppearEndPositionLegacy(); + float appearEndPosition = getAppearEndPosition(); float appearStartPosition = getAppearStartPosition(); float hunAppearFraction = (height - appearStartPosition) / (appearEndPosition - appearStartPosition); @@ -4848,15 +4767,6 @@ public class NotificationStackScrollLayout } } - /** - * Returns whether or not a History button is shown in the footer. If there is no footer, then - * this will return false. - **/ - public boolean isHistoryShown() { - FooterViewRefactor.assertInLegacyMode(); - return mFooterView != null && mFooterView.isHistoryShown(); - } - /** Bind the {@link FooterView} to the NSSL. */ public void setFooterView(@NonNull FooterView footerView) { int index = -1; @@ -4866,18 +4776,6 @@ public class NotificationStackScrollLayout } mFooterView = footerView; addView(mFooterView, index); - if (!FooterViewRefactor.isEnabled()) { - if (mManageButtonClickListener != null) { - mFooterView.setManageButtonClickListener(mManageButtonClickListener); - } - mFooterView.setClearAllButtonClickListener(v -> { - if (mFooterClearAllListener != null) { - mFooterClearAllListener.onClearAll(); - } - clearNotifications(ROWS_ALL, true /* closeShade */); - footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); - }); - } } public void setEmptyShadeView(EmptyShadeView emptyShadeView) { @@ -4890,13 +4788,6 @@ public class NotificationStackScrollLayout addView(mEmptyShadeView, index); } - /** Legacy version, should be removed with the footer refactor flag. */ - public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { - FooterViewRefactor.assertInLegacyMode(); - updateEmptyShadeView(visible, areNotificationsHiddenInShade, - mHasFilteredOutSeenNotifications); - } - /** Trigger an update for the empty shade resources and visibility. */ public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade, boolean hasFilteredOutSeenNotifications) { @@ -4949,18 +4840,6 @@ public class NotificationStackScrollLayout return mEmptyShadeView.isVisible(); } - public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { - FooterViewRefactor.assertInLegacyMode(); - if (mFooterView == null || mNotificationStackSizeCalculator == null) { - return; - } - boolean animate = mIsExpanded && mAnimationsEnabled; - mFooterView.setVisible(visible, animate); - mFooterView.showHistory(showHistory); - mFooterView.setClearAllButtonVisible(showDismissView, animate); - mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); - } - @VisibleForTesting public void setClearAllInProgress(boolean clearAllInProgress) { mClearAllInProgress = clearAllInProgress; @@ -5244,10 +5123,8 @@ public class NotificationStackScrollLayout public void setQsFullScreen(boolean qsFullScreen) { SceneContainerFlag.assertInLegacyMode(); - if (FooterViewRefactor.isEnabled()) { - if (qsFullScreen == mQsFullScreen) { - return; // no change - } + if (qsFullScreen == mQsFullScreen) { + return; // no change } mQsFullScreen = qsFullScreen; updateAlgorithmLayoutMinHeight(); @@ -5266,8 +5143,6 @@ public class NotificationStackScrollLayout public void setQsExpansionFraction(float qsExpansionFraction) { SceneContainerFlag.assertInLegacyMode(); - boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction - && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -5276,9 +5151,6 @@ public class NotificationStackScrollLayout if (getOwnScrollY() > 0) { setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction())); } - if (!FooterViewRefactor.isEnabled() && footerAffected) { - updateFooter(); - } } @VisibleForTesting @@ -5456,14 +5328,6 @@ public class NotificationStackScrollLayout requestChildrenUpdate(); } - void setUpcomingStatusBarState(int upcomingStatusBarState) { - FooterViewRefactor.assertInLegacyMode(); - mUpcomingStatusBarState = upcomingStatusBarState; - if (mUpcomingStatusBarState != mStatusBarState) { - updateFooter(); - } - } - void onStatePostChange(boolean fromShadeLocked) { boolean onKeyguard = onKeyguard(); @@ -5472,9 +5336,6 @@ public class NotificationStackScrollLayout } setExpandingEnabled(!onKeyguard); - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } requestChildrenUpdate(); onUpdateRowStates(); updateVisibility(); @@ -5490,8 +5351,7 @@ public class NotificationStackScrollLayout if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) { return getMinExpansionHeight(); } else { - return FooterViewRefactor.isEnabled() ? getAppearEndPosition() - : getAppearEndPositionLegacy(); + return getAppearEndPosition(); } } @@ -5583,12 +5443,6 @@ public class NotificationStackScrollLayout for (int i = 0; i < childCount; i++) { ExpandableView child = getChildAtIndex(i); child.dump(pw, args); - if (!FooterViewRefactor.isEnabled()) { - if (child instanceof FooterView) { - DumpUtilsKt.withIncreasedIndent(pw, - () -> dumpFooterViewVisibility(pw)); - } - } pw.println(); } int transientViewCount = getTransientViewCount(); @@ -5615,45 +5469,6 @@ public class NotificationStackScrollLayout pw.append(" bottomRadius=").println(mBgCornerRadii[4]); } - private void dumpFooterViewVisibility(IndentingPrintWriter pw) { - FooterViewRefactor.assertInLegacyMode(); - final boolean showDismissView = shouldShowDismissView(); - - pw.println("showFooterView: " + shouldShowFooterView(showDismissView)); - DumpUtilsKt.withIncreasedIndent( - pw, - () -> { - pw.println("showDismissView: " + showDismissView); - DumpUtilsKt.withIncreasedIndent( - pw, - () -> { - pw.println( - "hasActiveClearableNotifications: " - + mController.hasActiveClearableNotifications( - ROWS_ALL)); - }); - pw.println(); - pw.println("showHistory: " + mController.isHistoryEnabled()); - pw.println(); - pw.println( - "visibleNotificationCount: " - + mController.getVisibleNotificationCount()); - pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup); - pw.println("onKeyguard: " + onKeyguard()); - pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState); - if (!SceneContainerFlag.isEnabled()) { - pw.println("QsExpansionFraction: " + getQsExpansionFraction()); - } - pw.println("mQsFullScreen: " + mQsFullScreen); - pw.println( - "mScreenOffAnimationController" - + ".shouldHideNotificationsFooter: " - + mScreenOffAnimationController - .shouldHideNotificationsFooter()); - pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive); - }); - } - public boolean isFullyHidden() { return mAmbientState.isFullyHidden(); } @@ -5764,14 +5579,6 @@ public class NotificationStackScrollLayout clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection); } - /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */ - void clearNotifications(@SelectedRows int selection, boolean closeShade) { - FooterViewRefactor.assertInLegacyMode(); - final boolean hideSilentSection = !mController.hasNotifications( - ROWS_GENTLE, false /* clearable */); - clearNotifications(selection, closeShade, hideSilentSection); - } - /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. @@ -5826,25 +5633,6 @@ public class NotificationStackScrollLayout return canChildBeCleared(row) && matchesSelection(row, selection); } - /** - * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. - */ - public void setManageButtonClickListener(@Nullable OnClickListener listener) { - FooterViewRefactor.assertInLegacyMode(); - mManageButtonClickListener = listener; - if (mFooterView != null) { - mFooterView.setManageButtonClickListener(mManageButtonClickListener); - } - } - - @VisibleForTesting - protected void inflateFooterView() { - FooterViewRefactor.assertInLegacyMode(); - FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_notification_footer, this, false); - setFooterView(footerView); - } - private void inflateEmptyShadeView() { ModesEmptyShadeFix.assertInLegacyMode(); @@ -6091,11 +5879,6 @@ public class NotificationStackScrollLayout mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump; } - void setFooterClearAllListener(FooterClearAllListener listener) { - FooterViewRefactor.assertInLegacyMode(); - mFooterClearAllListener = listener; - } - void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) { mClearAllFinishedWhilePanelExpandedRunnable = runnable; } @@ -6394,17 +6177,6 @@ public class NotificationStackScrollLayout } /** - * Sets whether the current user is set up, which is required to show the footer (b/193149550) - */ - public void setCurrentUserSetup(boolean isCurrentUserSetup) { - FooterViewRefactor.assertInLegacyMode(); - if (mIsCurrentUserSetup != isCurrentUserSetup) { - mIsCurrentUserSetup = isCurrentUserSetup; - updateFooter(); - } - } - - /** * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates * the views. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index a33a9ed2df75..b892bebb3120 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -29,11 +29,8 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.ObjectAnimator; import android.content.res.Configuration; @@ -64,14 +61,10 @@ import com.android.internal.view.OneShotPreDrawListener; import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; import com.android.systemui.Gefingerpoken; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.media.controls.ui.controller.KeyguardMediaController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -92,18 +85,13 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; -import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats; import com.android.systemui.statusbar.notification.collection.NotifCollection; @@ -115,14 +103,15 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; +import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.NotifStackController; -import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; -import com.android.systemui.statusbar.notification.dagger.SilentHeader; -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController; +import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -137,13 +126,8 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; -import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.SplitShadeStateController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.Compile; import com.android.systemui.util.settings.SecureSettings; @@ -179,10 +163,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private HeadsUpTouchHelper mHeadsUpTouchHelper; private final NotificationRoundnessManager mNotificationRoundnessManager; private final TunerService mTunerService; - private final DeviceProvisionedController mDeviceProvisionedController; private final DynamicPrivacyController mDynamicPrivacyController; private final ConfigurationController mConfigurationController; - private final ZenModeController mZenModeController; private final MetricsLogger mMetricsLogger; private final ColorUpdateLogger mColorUpdateLogger; @@ -193,7 +175,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotifPipeline mNotifPipeline; private final NotifCollection mNotifCollection; private final UiEventLogger mUiEventLogger; - private final NotificationRemoteInputManager mRemoteInputManager; private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; private final Provider<WindowRootView> mWindowRootView; @@ -201,9 +182,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; private final PowerInteractor mPowerInteractor; - private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final NotificationLockscreenUserManager mLockscreenUserManager; - private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final InteractionJankMonitor mJankMonitor; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; @@ -211,8 +190,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationStackScrollLogger mLogger; private final GroupExpansionManager mGroupExpansionManager; - private final SeenNotificationsInteractor mSeenNotificationsInteractor; - private final KeyguardTransitionRepository mKeyguardTransitionRepo; private NotificationStackScrollLayout mView; private TouchHandler mTouchHandler; private NotificationSwipeHelper mSwipeHelper; @@ -220,7 +197,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private Boolean mHistoryEnabled; private int mBarState; private HeadsUpAppearanceController mHeadsUpAppearanceController; - private boolean mIsInTransitionToAod = false; private final NotificationTargetsHelper mNotificationTargetsHelper; private final SecureSettings mSecureSettings; @@ -235,11 +211,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationListContainerImpl mNotificationListContainer = new NotificationListContainerImpl(); + // TODO: b/293167744 - Remove this. private final NotifStackController mNotifStackController = - new NotifStackControllerImpl(); - - @Nullable - private NotificationActivityStarter mNotificationActivityStarter; + new DefaultNotifStackController(); @VisibleForTesting final View.OnAttachStateChangeListener mOnAttachStateChangeListener = @@ -248,9 +222,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onViewAttachedToWindow(View v) { mColorUpdateLogger.logTriggerEvent("NSSLC.onViewAttachedToWindow()"); mConfigurationController.addCallback(mConfigurationListener); - if (!FooterViewRefactor.isEnabled()) { - mZenModeController.addCallback(mZenModeControllerCallback); - } final int newBarState = mStatusBarStateController.getState(); if (newBarState != mBarState) { mStateListener.onStateChanged(newBarState); @@ -264,9 +235,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onViewDetachedFromWindow(View v) { mColorUpdateLogger.logTriggerEvent("NSSLC.onViewDetachedFromWindow()"); mConfigurationController.removeCallback(mConfigurationListener); - if (!FooterViewRefactor.isEnabled()) { - mZenModeController.removeCallback(mZenModeControllerCallback); - } mStatusBarStateController.removeCallback(mStateListener); } }; @@ -287,28 +255,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Nullable private ObjectAnimator mHideAlphaAnimator = null; - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateCurrentUserIsSetup(); - } - - @Override - public void onUserSwitched() { - updateCurrentUserIsSetup(); - } - - @Override - public void onUserSetupChanged() { - updateCurrentUserIsSetup(); - } - - private void updateCurrentUserIsSetup() { - mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); - } - }; - private final Runnable mSensitiveStateChangedListener = new Runnable() { @Override public void run() { @@ -318,20 +264,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { - if (!FooterViewRefactor.isEnabled()) { - // Let's update the footer once the notifications have been updated (in the next frame) - mView.post(this::updateFooter); - } - }; - @VisibleForTesting final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onDensityOrFontScaleChanged() { - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - } mView.reinflateViews(); } @@ -351,10 +287,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateBgColor(); mView.updateDecorViews(); mView.reinflateViews(); - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - updateFooter(); - } } @Override @@ -363,7 +295,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private NotifStats mNotifStats = NotifStats.getEmpty(); private float mMaxAlphaForKeyguard = 1.0f; private String mMaxAlphaForKeyguardSource = "constructor"; private float mMaxAlphaForUnhide = 1.0f; @@ -401,19 +332,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @Override - public void onUpcomingStateChanged(int newState) { - if (!FooterViewRefactor.isEnabled()) { - mView.setUpcomingStatusBarState(newState); - } - } - - @Override public void onStatePostChange() { updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); - if (!FooterViewRefactor.isEnabled()) { - updateImportantForAccessibility(); - } } }; @@ -422,9 +343,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onUserChanged(int userId) { updateSensitivenessWithAnimation(false); mHistoryEnabled = null; - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } } }; @@ -656,7 +574,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { == null) { mHeadsUpManager.removeNotification( row.getEntry().getSbn().getKey(), - /* removeImmediately= */ true , + /* removeImmediately= */ true, /* reason= */ "onChildSnappedBack" ); } @@ -714,14 +632,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private final ZenModeController.Callback mZenModeControllerCallback = - new ZenModeController.Callback() { - @Override - public void onZenChanged(int zen) { - updateShowEmptyShadeView(); - } - }; - @Inject public NotificationStackScrollLayoutController( NotificationStackScrollLayout view, @@ -734,16 +644,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { Provider<IStatusBarService> statusBarService, NotificationRoundnessManager notificationRoundnessManager, TunerService tunerService, - DeviceProvisionedController deviceProvisionedController, DynamicPrivacyController dynamicPrivacyController, @ShadeDisplayAware ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, KeyguardMediaController keyguardMediaController, KeyguardBypassController keyguardBypassController, PowerInteractor powerInteractor, - PrimaryBouncerInteractor primaryBouncerInteractor, - KeyguardTransitionRepository keyguardTransitionRepo, - ZenModeController zenModeController, NotificationLockscreenUserManager lockscreenUserManager, MetricsLogger metricsLogger, ColorUpdateLogger colorUpdateLogger, @@ -752,14 +658,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { FalsingManager falsingManager, NotificationSwipeHelper.Builder notificationSwipeHelperBuilder, GroupExpansionManager groupManager, - @SilentHeader SectionHeaderController silentHeaderController, NotifPipeline notifPipeline, NotifCollection notifCollection, LockscreenShadeTransitionController lockscreenShadeTransitionController, UiEventLogger uiEventLogger, - NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, - SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, Provider<WindowRootView> windowRootView, @@ -775,7 +678,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { SensitiveNotificationProtectionController sensitiveNotificationProtectionController, WallpaperInteractor wallpaperInteractor) { mView = view; - mKeyguardTransitionRepo = keyguardTransitionRepo; mViewBinder = viewBinder; mStackStateLogger = stackLogger; mLogger = logger; @@ -795,15 +697,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { } mNotificationRoundnessManager = notificationRoundnessManager; mTunerService = tunerService; - mDeviceProvisionedController = deviceProvisionedController; mDynamicPrivacyController = dynamicPrivacyController; mConfigurationController = configurationController; mStatusBarStateController = statusBarStateController; mKeyguardMediaController = keyguardMediaController; mKeyguardBypassController = keyguardBypassController; mPowerInteractor = powerInteractor; - mPrimaryBouncerInteractor = primaryBouncerInteractor; - mZenModeController = zenModeController; mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; mColorUpdateLogger = colorUpdateLogger; @@ -815,13 +714,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mJankMonitor = jankMonitor; mNotificationStackSizeCalculator = notificationStackSizeCalculator; mGroupExpansionManager = groupManager; - mSilentHeaderController = silentHeaderController; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; mUiEventLogger = uiEventLogger; - mRemoteInputManager = remoteInputManager; mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; - mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; mWindowRootView = windowRootView; mNotificationTargetsHelper = notificationTargetsHelper; @@ -850,18 +746,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setClearAllAnimationListener(this::onAnimationEnd); mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - if (!FooterViewRefactor.isEnabled()) { - mView.setFooterClearAllListener(() -> - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); - mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); - mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { - @Override - public void onRemoteInputActive(boolean active) { - mView.setIsRemoteInputActive(active); - } - }); - } - mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> { + mView.setClearAllFinishedWhilePanelExpandedRunnable(() -> { final Runnable doCollapseRunnable = () -> mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE); @@ -889,19 +774,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled()); mKeyguardBypassController .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled); - if (!FooterViewRefactor.isEnabled()) { - mView.setManageButtonClickListener(v -> { - if (mNotificationActivityStarter != null) { - mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown()); - } - }); - } if (!SceneContainerFlag.isEnabled()) { mHeadsUpManager.addListener(mOnHeadsUpChangedListener); } mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed); - mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener); mLockscreenShadeTransitionController.setStackScroller(this); @@ -914,9 +791,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { switch (key) { case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: mHistoryEnabled = null; // invalidate - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } break; case HIGH_PRIORITY: mView.setHighPriorityBeforeSpeedBump("1".equals(newValue)); @@ -938,12 +812,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return kotlin.Unit.INSTANCE; }); - if (!FooterViewRefactor.isEnabled()) { - // attach callback, and then call it to update mView immediately - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - mDeviceProvisionedListener.onDeviceProvisionedChanged(); - } - if (screenshareNotificationHiding()) { mSensitiveNotificationProtectionController .registerSensitiveStateListener(mSensitiveStateChangedListener); @@ -953,20 +821,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - if (!FooterViewRefactor.isEnabled()) { - mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); - } mGroupExpansionManager.registerGroupExpansionChangeListener( (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); mViewBinder.bindWhileAttached(mView, this); - if (!FooterViewRefactor.isEnabled()) { - collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), - this::onKeyguardTransitionChanged); - } - mView.setWallpaperInteractor(mWallpaperInteractor); } @@ -1168,11 +1028,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView != null && mView.isAddOrRemoveAnimationPending(); } - public int getVisibleNotificationCount() { - FooterViewRefactor.assertInLegacyMode(); - return mNotifStats.getNumActiveNotifs(); - } - public boolean isHistoryEnabled() { Boolean historyEnabled = mHistoryEnabled; if (historyEnabled == null) { @@ -1284,9 +1139,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void setQsFullScreen(boolean fullScreen) { mView.setQsFullScreen(fullScreen); - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - } } public void setScrollingEnabled(boolean enabled) { @@ -1464,64 +1316,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { } /** - * Set the visibility of the view, and propagate it to specific children. + * Set the visibility of the view. * * @param visible either the view is visible or not. */ public void updateVisibility(boolean visible) { mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - - // Refactor note: the empty shade's visibility doesn't seem to actually depend on the - // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not - // modeled in the refactored code. - if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) { - // Synchronize EmptyShadeView visibility with the parent container. - updateShowEmptyShadeView(); - updateImportantForAccessibility(); - } - } - - /** - * Update whether we should show the empty shade view ("no notifications" in the shade). - * <p> - * When in split mode, notifications are always visible regardless of the state of the - * QuickSettings panel. That being the case, empty view is always shown if the other conditions - * are true. - */ - public void updateShowEmptyShadeView() { - FooterViewRefactor.assertInLegacyMode(); - - Trace.beginSection("NSSLC.updateShowEmptyShadeView"); - - final boolean shouldShow = getVisibleNotificationCount() == 0 - && !mView.isQsFullScreen() - // Hide empty shade view when in transition to AOD. - // That avoids "No Notifications" to blink when transitioning to AOD. - // For more details, see: b/228790482 - && !mIsInTransitionToAod - // Don't show any notification content if the bouncer is showing. See b/267060171. - && !mPrimaryBouncerInteractor.isBouncerShowing(); - - mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); - - Trace.endSection(); - } - - /** - * Update the importantForAccessibility of NotificationStackScrollLayout. - * <p> - * We want the NSSL to be unimportant for accessibility when there's no - * notifications in it while the device is on lock screen, to avoid unlablel NSSL view. - * Otherwise, we want it to be important for accessibility to enable accessibility - * auto-scrolling in NSSL. - */ - public void updateImportantForAccessibility() { - FooterViewRefactor.assertInLegacyMode(); - if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { - mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } } public boolean isShowingEmptyShadeView() { @@ -1577,34 +1377,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setPulsing(pulsing, animatePulse); } - /** - * Return whether there are any clearable notifications - */ - public boolean hasActiveClearableNotifications(@SelectedRows int selection) { - FooterViewRefactor.assertInLegacyMode(); - return hasNotifications(selection, true /* clearable */); - } - - public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { - FooterViewRefactor.assertInLegacyMode(); - boolean hasAlertingMatchingClearable = isClearable - ? mNotifStats.getHasClearableAlertingNotifs() - : mNotifStats.getHasNonClearableAlertingNotifs(); - boolean hasSilentMatchingClearable = isClearable - ? mNotifStats.getHasClearableSilentNotifs() - : mNotifStats.getHasNonClearableSilentNotifs(); - switch (selection) { - case ROWS_GENTLE: - return hasSilentMatchingClearable; - case ROWS_HIGH_PRIORITY: - return hasAlertingMatchingClearable; - case ROWS_ALL: - return hasSilentMatchingClearable || hasAlertingMatchingClearable; - default: - throw new IllegalStateException("Bad selection: " + selection); - } - } - /** Sets whether the NSSL is displayed over the unoccluded Lockscreen. */ public void setOnLockscreen(boolean isOnLockscreen) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; @@ -1637,9 +1409,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } } public void lockScrollTo(NotificationEntry entry) { @@ -1662,13 +1431,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { }; } - public void updateFooter() { - FooterViewRefactor.assertInLegacyMode(); - Trace.beginSection("NSSLC.updateFooter"); - mView.updateFooter(); - Trace.endSection(); - } - public void onUpdateRowStates() { mView.onUpdateRowStates(); } @@ -1695,18 +1457,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getTransientViewCount(); } - public View getTransientView(int i) { - return mView.getTransientView(i); - } - public NotificationStackScrollLayout getView() { return mView; } - public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) { - return mView.calculateGapHeight(previousView, child, count); - } - NotificationRoundnessManager getNotificationRoundnessManager() { return mNotificationRoundnessManager; } @@ -1772,13 +1526,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return NotificationSwipeHelper.isTouchInView(event, view); } - public void clearSilentNotifications() { - FooterViewRefactor.assertInLegacyMode(); - // Leave the shade open if there will be other notifs left over to clear - final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); - mView.clearNotifications(ROWS_GENTLE, closeShade); - } - private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows) { if (selectedRows == ROWS_ALL) { @@ -1880,10 +1627,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.animateNextTopPaddingChange(); } - public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) { - mNotificationActivityStarter = activityStarter; - } - public NotificationTargetsHelper getNotificationTargetsHelper() { return mNotificationTargetsHelper; } @@ -1898,18 +1641,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @VisibleForTesting - void onKeyguardTransitionChanged(TransitionStep transitionStep) { - FooterViewRefactor.assertInLegacyMode(); - boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD) - && (transitionStep.getFrom().equals(KeyguardState.GONE) - || transitionStep.getFrom().equals(KeyguardState.OCCLUDED)); - if (mIsInTransitionToAod != isTransitionToAod) { - mIsInTransitionToAod = isTransitionToAod; - updateShowEmptyShadeView(); - } - } - - @VisibleForTesting TouchHandler getTouchHandler() { return mTouchHandler; } @@ -2288,22 +2019,4 @@ public class NotificationStackScrollLayoutController implements Dumpable { && !mSwipeHelper.isSwiping(); } } - - private class NotifStackControllerImpl implements NotifStackController { - @Override - public void setNotifStats(@NonNull NotifStats notifStats) { - FooterViewRefactor.assertInLegacyMode(); - mNotifStats = notifStats; - - if (!FooterViewRefactor.isEnabled()) { - mView.setHasFilteredOutSeenNotifications( - mSeenNotificationsInteractor - .getHasFilteredOutSeenNotifications().getValue()); - - updateFooter(); - updateShowEmptyShadeView(); - updateImportantForAccessibility(); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 1653029dc994..06b989aaab57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -35,7 +35,6 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -463,26 +462,23 @@ public class StackScrollAlgorithm { if (v == ambientState.getShelf()) { continue; } - if (FooterViewRefactor.isEnabled()) { - if (v instanceof EmptyShadeView) { - emptyShadeVisible = true; - } - if (v instanceof FooterView footerView) { - if (emptyShadeVisible || notGoneIndex == 0) { - // if the empty shade is visible or the footer is the first visible - // view, we're in a transitory state so let's leave the footer alone. - if (Flags.notificationsFooterVisibilityFix() - && !SceneContainerFlag.isEnabled()) { - // ...except for the hidden state, to prevent it from flashing on - // the screen (this piece is copied from updateChild, and is not - // necessary in flexiglass). - if (footerView.shouldBeHidden() - || !ambientState.isShadeExpanded()) { - footerView.getViewState().hidden = true; - } + if (v instanceof EmptyShadeView) { + emptyShadeVisible = true; + } + if (v instanceof FooterView footerView) { + if (emptyShadeVisible || notGoneIndex == 0) { + // if the empty shade is visible or the footer is the first visible + // view, we're in a transitory state so let's leave the footer alone. + if (Flags.notificationsFooterVisibilityFix() + && !SceneContainerFlag.isEnabled()) { + // ...except for the hidden state, to prevent it from flashing on + // the screen (this piece is copied from updateChild, and is not + // necessary in flexiglass). + if (footerView.shouldBeHidden() || !ambientState.isShadeExpanded()) { + footerView.getViewState().hidden = true; } - continue; } + continue; } } @@ -699,44 +695,28 @@ public class StackScrollAlgorithm { viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation() ); if (view instanceof FooterView) { - if (FooterViewRefactor.isEnabled()) { - if (SceneContainerFlag.isEnabled()) { - final float footerEnd = - stackTop + viewState.getYTranslation() + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff(); - ((FooterView.FooterViewState) viewState).hideContent = - noSpaceForFooter || (ambientState.isClearAllInProgress() - && !hasNonClearableNotifs(algorithmState)); - } else { - // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed - // already, so we shouldn't need to use ambientState here. However, - // currently it doesn't get updated quickly enough and can cause the footer to - // flash when closing the shade. As such, we temporarily also check the - // ambientState directly. - if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { - viewState.hidden = true; - } else { - final float footerEnd = algorithmState.mCurrentExpandedYPosition - + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = - footerEnd > ambientState.getStackEndHeight(); - ((FooterView.FooterViewState) viewState).hideContent = - noSpaceForFooter || (ambientState.isClearAllInProgress() - && !hasNonClearableNotifs(algorithmState)); - } - } + if (SceneContainerFlag.isEnabled()) { + final float footerEnd = + stackTop + viewState.getYTranslation() + view.getIntrinsicHeight(); + final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff(); + ((FooterView.FooterViewState) viewState).hideContent = + noSpaceForFooter || (ambientState.isClearAllInProgress() + && !hasNonClearableNotifs(algorithmState)); } else { - final boolean shadeClosed = !ambientState.isShadeExpanded(); - final boolean isShelfShowing = algorithmState.firstViewInShelf != null; - if (shadeClosed) { + // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed + // already, so we shouldn't need to use ambientState here. However, + // currently it doesn't get updated quickly enough and can cause the footer to + // flash when closing the shade. As such, we temporarily also check the + // ambientState directly. + if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { viewState.hidden = true; } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); + final boolean noSpaceForFooter = + footerEnd > ambientState.getStackEndHeight(); ((FooterView.FooterViewState) viewState).hideContent = - isShelfShowing || noSpaceForFooter - || (ambientState.isClearAllInProgress() + noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index b4561686b7b2..1d7e658932ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -40,7 +40,6 @@ import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyS import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder @@ -108,25 +107,20 @@ constructor( launch { bindShelf(shelf) } bindHideList(viewController, viewModel, hiderTracker) - if (FooterViewRefactor.isEnabled) { - val hasNonClearableSilentNotifications: StateFlow<Boolean> = - viewModel.hasNonClearableSilentNotifications.stateIn(this) - launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } - launch { - if (ModesEmptyShadeFix.isEnabled) { - reinflateAndBindEmptyShade(view) - } else { - bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view) - } + val hasNonClearableSilentNotifications: StateFlow<Boolean> = + viewModel.hasNonClearableSilentNotifications.stateIn(this) + launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } + launch { + if (ModesEmptyShadeFix.isEnabled) { + reinflateAndBindEmptyShade(view) + } else { + bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view) } - launch { - bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) - } - launch { - viewModel.isImportantForAccessibility.collect { isImportantForAccessibility - -> - view.setImportantForAccessibilityYesNo(isImportantForAccessibility) - } + } + launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) } + launch { + viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> + view.setImportantForAccessibilityYesNo(isImportantForAccessibility) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index ea714608ea66..0b2b84e60f4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer @@ -81,9 +80,6 @@ constructor( controller.setOverExpansion(0f) controller.setOverScrollAmount(0) - if (!FooterViewRefactor.isEnabled) { - controller.updateFooter() - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 38390e7bdb39..fcc671a5bae6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel @@ -75,46 +74,37 @@ constructor( * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL. * See b/242235264 for more details. */ - val isImportantForAccessibility: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(true) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - notificationStackInteractor.isShowingOnLockscreen, - ) { hasNotifications, isShowingOnLockscreen -> - hasNotifications || !isShowingOnLockscreen - } - .distinctUntilChanged() - .dumpWhileCollecting("isImportantForAccessibility") - .flowOn(bgDispatcher) - } - } + val isImportantForAccessibility: Flow<Boolean> = + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + notificationStackInteractor.isShowingOnLockscreen, + ) { hasNotifications, isShowingOnLockscreen -> + hasNotifications || !isShowingOnLockscreen + } + .distinctUntilChanged() + .dumpWhileCollecting("isImportantForAccessibility") + .flowOn(bgDispatcher) val shouldShowEmptyShadeView: Flow<Boolean> by lazy { ModesEmptyShadeFix.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - shadeInteractor.isQsFullscreen, - notificationStackInteractor.isShowingOnLockscreen, - ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> - when { - hasNotifications -> false - isQsFullScreen -> false - // Do not show the empty shade if the lockscreen is visible (including AOD - // b/228790482 and bouncer b/267060171), except if the shade is opened on - // top. - isShowingOnLockscreen -> false - else -> true - } + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + shadeInteractor.isQsFullscreen, + notificationStackInteractor.isShowingOnLockscreen, + ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> + when { + hasNotifications -> false + isQsFullScreen -> false + // Do not show the empty shade if the lockscreen is visible (including AOD + // b/228790482 and bouncer b/267060171), except if the shade is opened on + // top. + isShowingOnLockscreen -> false + else -> true } - .distinctUntilChanged() - .dumpWhileCollecting("shouldShowEmptyShadeView") - .flowOn(bgDispatcher) - } + } + .distinctUntilChanged() + .dumpWhileCollecting("shouldShowEmptyShadeView") + .flowOn(bgDispatcher) } val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy { @@ -164,18 +154,14 @@ constructor( */ val shouldHideFooterView: Flow<Boolean> by lazy { SceneContainerFlag.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - // When the shade is closed, the footer is still present in the list, but not visible. - // This prevents the footer from being shown when a HUN is present, while still allowing - // the footer to be counted as part of the shade for measurements. - shadeInteractor.shadeExpansion - .map { it == 0f } - .distinctUntilChanged() - .dumpWhileCollecting("shouldHideFooterView") - .flowOn(bgDispatcher) - } + // When the shade is closed, the footer is still present in the list, but not visible. + // This prevents the footer from being shown when a HUN is present, while still allowing + // the footer to be counted as part of the shade for measurements. + shadeInteractor.shadeExpansion + .map { it == 0f } + .distinctUntilChanged() + .dumpWhileCollecting("shouldHideFooterView") + .flowOn(bgDispatcher) } /** @@ -188,68 +174,64 @@ constructor( */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { SceneContainerFlag.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(AnimatedValue.NotAnimating(false)) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - userSetupInteractor.isUserSetUp, - notificationStackInteractor.isShowingOnLockscreen, - shadeInteractor.isQsFullscreen, - remoteInputInteractor.isRemoteInputActive, - ) { - hasNotifications, - isUserSetUp, - isShowingOnLockscreen, - qsFullScreen, - isRemoteInputActive -> - when { - !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Hide the footer until the user setup is complete, to prevent access - // to settings (b/193149550). - !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Do not show the footer if the lockscreen is visible (incl. AOD), - // except if the shade is opened on top. See also b/219680200. - // Do not animate, as that makes the footer appear briefly when - // transitioning between the shade and keyguard. - isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION - // Do not show the footer if quick settings are fully expanded (except - // for the foldable split shade view). See b/201427195 && b/222699879. - qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Hide the footer if remote input is active (i.e. user is replying to a - // notification). See b/75984847. - isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - else -> VisibilityChange.APPEAR_WITH_ANIMATION - } + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + userSetupInteractor.isUserSetUp, + notificationStackInteractor.isShowingOnLockscreen, + shadeInteractor.isQsFullscreen, + remoteInputInteractor.isRemoteInputActive, + ) { + hasNotifications, + isUserSetUp, + isShowingOnLockscreen, + qsFullScreen, + isRemoteInputActive -> + when { + !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Hide the footer until the user setup is complete, to prevent access + // to settings (b/193149550). + !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Do not show the footer if the lockscreen is visible (incl. AOD), + // except if the shade is opened on top. See also b/219680200. + // Do not animate, as that makes the footer appear briefly when + // transitioning between the shade and keyguard. + isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION + // Do not show the footer if quick settings are fully expanded (except + // for the foldable split shade view). See b/201427195 && b/222699879. + qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Hide the footer if remote input is active (i.e. user is replying to a + // notification). See b/75984847. + isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + else -> VisibilityChange.APPEAR_WITH_ANIMATION } - .distinctUntilChanged( - // Equivalent unless visibility changes - areEquivalent = { a: VisibilityChange, b: VisibilityChange -> - a.visible == b.visible - } - ) - // Should we animate the visibility change? - .sample( - // TODO(b/322167853): This check is currently duplicated in FooterViewModel, - // but instead it should be a field in ShadeAnimationInteractor. - combine( - shadeInteractor.isShadeFullyExpanded, - shadeInteractor.isShadeTouchable, - ::Pair, - ) - .onStart { emit(Pair(false, false)) } - ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) -> - // Animate if the shade is interactive, but NOT on the lockscreen. Having - // animations enabled while on the lockscreen makes the footer appear briefly - // when transitioning between the shade and keyguard. - val shouldAnimate = - isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate - AnimatableEvent(visibilityChange.visible, shouldAnimate) + } + .distinctUntilChanged( + // Equivalent unless visibility changes + areEquivalent = { a: VisibilityChange, b: VisibilityChange -> + a.visible == b.visible } - .toAnimatedValueFlow() - .dumpWhileCollecting("shouldIncludeFooterView") - .flowOn(bgDispatcher) - } + ) + // Should we animate the visibility change? + .sample( + // TODO(b/322167853): This check is currently duplicated in FooterViewModel, + // but instead it should be a field in ShadeAnimationInteractor. + combine( + shadeInteractor.isShadeFullyExpanded, + shadeInteractor.isShadeTouchable, + ::Pair, + ) + .onStart { emit(Pair(false, false)) } + ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) -> + // Animate if the shade is interactive, but NOT on the lockscreen. Having + // animations enabled while on the lockscreen makes the footer appear briefly + // when transitioning between the shade and keyguard. + val shouldAnimate = + isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate + AnimatableEvent(visibilityChange.visible, shouldAnimate) + } + .toAnimatedValueFlow() + .dumpWhileCollecting("shouldIncludeFooterView") + .flowOn(bgDispatcher) } // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass. @@ -328,25 +310,15 @@ constructor( APPEAR_WITH_ANIMATION(visible = true, canAnimate = true), } - val hasClearableAlertingNotifications: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting( - "hasClearableAlertingNotifications" - ) - } - } + val hasClearableAlertingNotifications: Flow<Boolean> = + activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting( + "hasClearableAlertingNotifications" + ) - val hasNonClearableSilentNotifications: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting( - "hasNonClearableSilentNotifications" - ) - } - } + val hasNonClearableSilentNotifications: Flow<Boolean> = + activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting( + "hasNonClearableSilentNotifications" + ) val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 1474789ea0e3..3d6cd7e49dfe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1487,8 +1487,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback); mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener); mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); - mStackScrollerController.setNotificationActivityStarter( - mNotificationActivityStarterLazy.get()); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarterLazy.get()); mShadeController.setNotificationPresenter(mPresenterLazy.get()); mNotificationsController.initialize( 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 3749b96199f6..8443edd6aa87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowInsets.Type.navigationBars; -import static com.android.systemui.Flags.predictiveBackAnimateBouncer; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -328,7 +327,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private float mQsExpansion; final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); - private boolean mIsBackAnimationEnabled; private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private final ActivityStarter mActivityStarter; @@ -434,7 +432,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); mAlternateBouncerInteractor = alternateBouncerInteractor; mBouncerInteractor = bouncerInteractor; - mIsBackAnimationEnabled = predictiveBackAnimateBouncer(); mUdfpsOverlayInteractor = udfpsOverlayInteractor; mActivityStarter = activityStarter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; @@ -630,7 +627,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean shouldPlayBackAnimation() { // Suppress back animation when bouncer shouldn't be dismissed on back invocation. - return !needsFullscreenBouncer() && mIsBackAnimationEnabled; + return !needsFullscreenBouncer(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 03324d2a3e6a..c47ed1722bb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.Flags.predictiveBackAnimateDialogs; - import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; @@ -285,15 +283,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh for (int i = 0; i < mOnCreateRunnables.size(); i++) { mOnCreateRunnables.get(i).run(); } - if (predictiveBackAnimateDialogs()) { - View targetView = getWindow().getDecorView(); - DialogKt.registerAnimationOnBackInvoked( - /* dialog = */ this, - /* targetView = */ targetView, - /* backAnimationSpec= */mDelegate.getBackAnimationSpec( - () -> targetView.getResources().getDisplayMetrics()) - ); - } + View targetView = getWindow().getDecorView(); + DialogKt.registerAnimationOnBackInvoked( + /* dialog = */ this, + /* targetView = */ targetView, + /* backAnimationSpec= */mDelegate.getBackAnimationSpec( + () -> targetView.getResources().getDisplayMetrics()) + ); } private void updateWindowSize() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 31cae79c6b94..81d06a8db0b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -32,6 +32,7 @@ import android.os.Trace; import androidx.annotation.VisibleForTesting; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -241,7 +242,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController { private void setKeyguardFadingAway(boolean keyguardFadingAway) { if (mKeyguardFadingAway != keyguardFadingAway) { - Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway", + TrackTracer.instantForGroup("keyguard", "FadingAway", keyguardFadingAway ? 1 : 0); mKeyguardFadingAway = keyguardFadingAway; invokeForEachCallback(Callback::onKeyguardFadingAwayChanged); @@ -356,7 +357,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController { @Override public void notifyKeyguardGoingAway(boolean keyguardGoingAway) { if (mKeyguardGoingAway != keyguardGoingAway) { - Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway", + Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguard##GoingAway", keyguardGoingAway ? 1 : 0); mKeyguardGoingAway = keyguardGoingAway; mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index 12ed647fdee7..fdc2d8d96f9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.policy.domain.interactor -import android.app.NotificationManager.INTERRUPTION_FILTER_NONE import android.content.Context +import android.media.AudioManager import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT @@ -29,6 +29,7 @@ import com.android.settingslib.notification.data.repository.ZenModeRepository import com.android.settingslib.notification.modes.ZenIcon import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode +import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.modes.shared.ModesUi import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository @@ -67,6 +68,17 @@ constructor( deviceProvisioningRepository: DeviceProvisioningRepository, userSetupRepository: UserSetupRepository, ) { + /** + * List of predicates to determine if the [ZenMode] blocks an audio stream. Typical use case + * would be: `zenModeByStreamPredicates[stream](zenMode)` + */ + private val zenModeByStreamPredicates = + mapOf<Int, (ZenMode) -> Boolean>( + AudioManager.STREAM_MUSIC to { it.policy.priorityCategoryMedia == STATE_DISALLOW }, + AudioManager.STREAM_ALARM to { it.policy.priorityCategoryAlarms == STATE_DISALLOW }, + AudioManager.STREAM_SYSTEM to { it.policy.priorityCategorySystem == STATE_DISALLOW }, + ) + val isZenAvailable: Flow<Boolean> = combine( deviceProvisioningRepository.isDeviceProvisioned, @@ -125,21 +137,16 @@ constructor( .flowOn(bgDispatcher) .distinctUntilChanged() - val activeModesBlockingEverything: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode -> - mode.interruptionFilter == INTERRUPTION_FILTER_NONE - } - - val activeModesBlockingMedia: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode -> - mode.policy.priorityCategoryMedia == STATE_DISALLOW - } - - val activeModesBlockingAlarms: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode -> - mode.policy.priorityCategoryAlarms == STATE_DISALLOW - } + fun canBeBlockedByZenMode(stream: AudioStream): Boolean = + zenModeByStreamPredicates.containsKey(stream.value) - private fun getFilteredActiveModesFlow(predicate: (ZenMode) -> Boolean): Flow<ActiveZenModes> { + fun activeModesBlockingStream(stream: AudioStream): Flow<ActiveZenModes> { + val isBlockingStream = zenModeByStreamPredicates[stream.value] + require(isBlockingStream != null) { + "$stream is unsupported. Use canBeBlockedByZenMode to check if the stream can be affected by the Zen Mode." + } return modes - .map { modes -> modes.filter { mode -> predicate(mode) } } + .map { modes -> modes.filter { isBlockingStream(it) } } .map { modes -> buildActiveZenModes(modes) } .flowOn(bgDispatcher) .distinctUntilChanged() @@ -194,7 +201,6 @@ constructor( ) null } - ZEN_DURATION_FOREVER -> null else -> Duration.ofMinutes(zenDuration.toLong()) } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt index ae32b7a6175c..bce55cbdcc4a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt @@ -50,7 +50,7 @@ fun BackGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 73c54af595d9..284e23e5a288 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -18,7 +18,6 @@ package com.android.systemui.touchpad.tutorial.ui.composable import android.view.MotionEvent import androidx.activity.compose.BackHandler -import androidx.annotation.RawRes import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box @@ -27,77 +26,21 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import kotlinx.coroutines.flow.Flow -sealed interface GestureUiState { - data object NotStarted : GestureUiState - - data class Finished(@RawRes val successAnimation: Int) : GestureUiState - - data class InProgress( - val progress: Float = 0f, - val progressStartMarker: String, - val progressEndMarker: String, - ) : GestureUiState - - data object Error : GestureUiState -} - -fun GestureState.toGestureUiState( - progressStartMarker: String, - progressEndMarker: String, - successAnimation: Int, -): GestureUiState { - return when (this) { - GestureState.NotStarted -> NotStarted - is GestureState.InProgress -> - GestureUiState.InProgress(this.progress, progressStartMarker, progressEndMarker) - is GestureState.Finished -> GestureUiState.Finished(successAnimation) - GestureState.Error -> GestureUiState.Error - } -} - -fun GestureUiState.toTutorialActionState(previousState: TutorialActionState): TutorialActionState { - return when (this) { - NotStarted -> TutorialActionState.NotStarted - is GestureUiState.InProgress -> { - val inProgress = - TutorialActionState.InProgress( - progress = progress, - startMarker = progressStartMarker, - endMarker = progressEndMarker, - ) - if ( - previousState is TutorialActionState.InProgressAfterError || - previousState is TutorialActionState.Error - ) { - return TutorialActionState.InProgressAfterError(inProgress) - } else { - return inProgress - } - } - is Finished -> TutorialActionState.Finished(successAnimation) - GestureUiState.Error -> TutorialActionState.Error - } -} - @Composable fun GestureTutorialScreen( screenConfig: TutorialScreenConfig, - gestureUiStateFlow: Flow<GestureUiState>, + tutorialStateFlow: Flow<TutorialActionState>, motionEventConsumer: (MotionEvent) -> Boolean, easterEggTriggeredFlow: Flow<Boolean>, onEasterEggFinished: () -> Unit, @@ -106,25 +49,21 @@ fun GestureTutorialScreen( ) { BackHandler(onBack = onBack) val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false) - val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted) + val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted) TouchpadGesturesHandlingBox( motionEventConsumer, - gestureState, + tutorialState, easterEggTriggered, onEasterEggFinished, ) { - var lastState: TutorialActionState by remember { - mutableStateOf(TutorialActionState.NotStarted) - } - lastState = gestureState.toTutorialActionState(lastState) - ActionTutorialContent(lastState, onDoneButtonClicked, screenConfig) + ActionTutorialContent(tutorialState, onDoneButtonClicked, screenConfig) } } @Composable private fun TouchpadGesturesHandlingBox( motionEventConsumer: (MotionEvent) -> Boolean, - gestureState: GestureUiState, + tutorialState: TutorialActionState, easterEggTriggered: Boolean, onEasterEggFinished: () -> Unit, modifier: Modifier = Modifier, @@ -150,7 +89,7 @@ private fun TouchpadGesturesHandlingBox( .pointerInteropFilter( onTouchEvent = { event -> // FINISHED is the final state so we don't need to process touches anymore - if (gestureState is Finished) { + if (tutorialState is TutorialActionState.Finished) { false } else { motionEventConsumer(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt index 4f1f40dc4c05..4acdb6070200 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt @@ -49,7 +49,7 @@ fun HomeGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt index 6c9e26c4b7ea..8dd53a7fb815 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt @@ -50,7 +50,7 @@ fun RecentAppsGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt index 8e53669a7841..7a3d4d1ba88a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt @@ -17,12 +17,12 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import com.android.systemui.util.kotlin.pairwiseBy import kotlinx.coroutines.flow.Flow @@ -30,21 +30,26 @@ import kotlinx.coroutines.flow.Flow class BackGestureScreenViewModel(val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.pairwiseBy(GestureState.NotStarted) { previous, current -> - toGestureUiState(current, previous) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .pairwiseBy(NotStarted) { previous, current -> + current to toAnimationProperties(current, previous) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) } - private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState { + private fun toAnimationProperties( + current: GestureState, + previous: GestureState, + ): TutorialAnimationProperties { val (startMarker, endMarker) = if (current is InProgress && current.direction == GestureDirection.LEFT) { "gesture to L" to "end progress L" } else "gesture to R" to "end progress R" - return current.toGestureUiState( + return TutorialAnimationProperties( progressStartMarker = startMarker, progressEndMarker = endMarker, successAnimation = successAnimation(previous), diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt index 9d6f568fa1b1..c75d44f01e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt @@ -17,9 +17,8 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map class HomeGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.map { - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "release playback realtime", - successAnimation = R.raw.trackpad_home_success, - ) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "release playback realtime", + successAnimation = R.raw.trackpad_home_success, + ) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt index 97528583277f..9fab5f3641a4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt @@ -17,9 +17,8 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map class RecentAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.map { - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "onPause", - successAnimation = R.raw.trackpad_recent_apps_success, - ) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "onPause", + successAnimation = R.raw.trackpad_recent_apps_success, + ) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt index 31e953d6643c..3b6e3c76cdeb 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt @@ -17,11 +17,62 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState +import androidx.annotation.RawRes +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow interface TouchpadTutorialScreenViewModel { - val gestureUiState: Flow<GestureUiState> + val tutorialState: Flow<TutorialActionState> fun handleEvent(event: MotionEvent): Boolean } + +data class TutorialAnimationProperties( + val progressStartMarker: String, + val progressEndMarker: String, + @RawRes val successAnimation: Int, +) + +fun Flow<Pair<GestureState, TutorialAnimationProperties>>.mapToTutorialState(): + Flow<TutorialActionState> { + return flow<TutorialActionState> { + var lastState: TutorialActionState = TutorialActionState.NotStarted + collect { (gestureState, animationProperties) -> + val newState = gestureState.toTutorialActionState(animationProperties, lastState) + lastState = newState + emit(newState) + } + } +} + +fun GestureState.toTutorialActionState( + properties: TutorialAnimationProperties, + previousState: TutorialActionState, +): TutorialActionState { + return when (this) { + NotStarted -> TutorialActionState.NotStarted + is InProgress -> { + val inProgress = + TutorialActionState.InProgress( + progress = progress, + startMarker = properties.progressStartMarker, + endMarker = properties.progressEndMarker, + ) + if ( + previousState is TutorialActionState.InProgressAfterError || + previousState is TutorialActionState.Error + ) { + TutorialActionState.InProgressAfterError(inProgress) + } else { + inProgress + } + } + is Finished -> TutorialActionState.Finished(properties.successAnimation) + GestureState.Error -> TutorialActionState.Error + } +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt index 65970978b4ec..7d3966b98782 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt @@ -17,8 +17,9 @@ package com.android.systemui.unfold import android.content.Context import android.hardware.devicestate.DeviceStateManager -import android.os.Trace import com.android.app.tracing.TraceStateLogger +import com.android.app.tracing.coroutines.TrackTracer +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -29,7 +30,6 @@ import com.android.systemui.util.Utils.isDeviceFoldable import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope -import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.plus /** @@ -45,7 +45,7 @@ constructor( @Application applicationScope: CoroutineScope, @Background private val coroutineContext: CoroutineContext, private val deviceStateRepository: DeviceStateRepository, - private val deviceStateManager: DeviceStateManager + private val deviceStateManager: DeviceStateManager, ) : CoreStartable { private val isFoldable: Boolean = isDeviceFoldable(context.resources, deviceStateManager) @@ -61,7 +61,7 @@ constructor( bgScope.launch { foldStateRepository.hingeAngle.collect { - Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt()) + TrackTracer.instantForGroup("unfold", "hingeAngle", it.toInt()) } } bgScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt index fa1088426351..3b0c8a6b46f8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt @@ -46,7 +46,7 @@ class VolumeDialogCallbacksInteractor constructor( private val volumeDialogController: VolumeDialogController, @VolumeDialogPlugin private val coroutineScope: CoroutineScope, - @Background private val bgHandler: Handler, + @Background private val bgHandler: Handler?, ) { @SuppressLint("SharedFlowCreation") // event-bus needed diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt index 88af210b6a36..940c79c78d76 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt @@ -19,7 +19,6 @@ package com.android.systemui.volume.dialog.sliders.dagger import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder -import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder import dagger.BindsInstance import dagger.Subcomponent @@ -34,8 +33,6 @@ interface VolumeDialogSliderComponent { fun sliderViewBinder(): VolumeDialogSliderViewBinder - fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder - fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt deleted file mode 100644 index 4ecac7a81893..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.volume.dialog.sliders.ui - -import android.annotation.SuppressLint -import android.view.View -import com.android.systemui.res.R -import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope -import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel -import com.google.android.material.slider.Slider -import javax.inject.Inject - -@VolumeDialogSliderScope -class VolumeDialogSliderTouchesViewBinder -@Inject -constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) { - - @SuppressLint("ClickableViewAccessibility") - fun bind(view: View) { - with(view.requireViewById<Slider>(R.id.volume_dialog_slider)) { - setOnTouchListener { _, event -> - viewModel.onTouchEvent(event) - false - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index 67ffb0602860..ccd16ac5a331 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -23,6 +23,7 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.res.R import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.google.android.material.slider.Slider @@ -35,7 +36,10 @@ import kotlinx.coroutines.flow.onEach @VolumeDialogSliderScope class VolumeDialogSliderViewBinder @Inject -constructor(private val viewModel: VolumeDialogSliderViewModel) { +constructor( + private val viewModel: VolumeDialogSliderViewModel, + private val inputViewModel: VolumeDialogSliderInputEventsViewModel, +) { private val sliderValueProperty = object : FloatPropertyCompat<Slider>("value") { @@ -51,12 +55,16 @@ constructor(private val viewModel: VolumeDialogSliderViewModel) { dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY } + @SuppressLint("ClickableViewAccessibility") fun CoroutineScope.bind(view: View) { var isInitialUpdate = true val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider) val animation = SpringAnimation(sliderView, sliderValueProperty) animation.spring = springForce - + sliderView.setOnTouchListener { _, event -> + inputViewModel.onTouchEvent(event) + false + } sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) } @@ -82,7 +90,7 @@ constructor(private val viewModel: VolumeDialogSliderViewModel) { // coerce the current value to the new value range before animating it. This prevents // animating from the value that is outside of current [valueFrom, valueTo]. value = value.coerceIn(valueFrom, valueTo) - setTrackIconActiveStart(model.iconRes) + trackIconActiveStart = model.icon if (isInitialUpdate) { value = model.value } else { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt index f066b56e7de0..75d427acc05b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt @@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) { viewsToAnimate: Array<View>, ) { with(component.sliderViewBinder()) { bind(sliderContainer) } - with(component.sliderTouchesViewBinder()) { bind(sliderContainer) } with(component.sliderHapticsViewBinder()) { bind(sliderContainer) } with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt index 5c39b6f9359c..daf4c8275d20 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -16,13 +16,16 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.drawable.Drawable import android.media.AudioManager import androidx.annotation.DrawableRes -import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.res.R +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -31,11 +34,12 @@ import kotlinx.coroutines.flow.flowOf class VolumeDialogSliderIconProvider @Inject constructor( - private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor, + private val context: Context, + private val zenModeInteractor: ZenModeInteractor, private val audioVolumeInteractor: AudioVolumeInteractor, ) { - @DrawableRes + @SuppressLint("UseCompatLoadingForDrawables") fun getStreamIcon( stream: Int, level: Int, @@ -43,54 +47,71 @@ constructor( levelMax: Int, isMuted: Boolean, isRoutedToBluetooth: Boolean, - ): Flow<Int> { + ): Flow<Drawable> { return combine( - notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)), + zenModeInteractor.activeModesBlockingStream(AudioStream(stream)), ringerModeForStream(stream), - ) { isZenMuted, ringerMode -> - val isStreamOffline = level == 0 || isMuted - if (isZenMuted) { - // TODO(b/372466264) use icon for the corresponding zenmode - return@combine com.android.internal.R.drawable.ic_qs_dnd - } - when (ringerMode?.value) { - AudioManager.RINGER_MODE_VIBRATE -> - return@combine R.drawable.ic_volume_ringer_vibrate - AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off - } - if (isRoutedToBluetooth) { - return@combine if (stream == AudioManager.STREAM_VOICE_CALL) { - R.drawable.ic_volume_bt_sco - } else { - if (isStreamOffline) { - R.drawable.ic_volume_media_bt_mute - } else { - R.drawable.ic_volume_media_bt - } - } + ) { activeModesBlockingStream, ringerMode -> + if (activeModesBlockingStream.mainMode?.icon != null) { + return@combine activeModesBlockingStream.mainMode.icon.drawable + } else { + context.getDrawable( + getIconRes( + stream, + level, + levelMin, + levelMax, + isMuted, + isRoutedToBluetooth, + ringerMode, + ) + )!! } + } + } - return@combine if (isStreamOffline) { - getMutedIconForStream(stream) ?: getIconForStream(stream) + @DrawableRes + private fun getIconRes( + stream: Int, + level: Int, + levelMin: Int, + levelMax: Int, + isMuted: Boolean, + isRoutedToBluetooth: Boolean, + ringerMode: RingerMode?, + ): Int { + val isStreamOffline = level == 0 || isMuted + when (ringerMode?.value) { + AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate + AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off + } + if (isRoutedToBluetooth) { + return if (stream == AudioManager.STREAM_VOICE_CALL) { + R.drawable.ic_volume_bt_sco } else { - if (level < (levelMax + levelMin) / 2) { - // This icon is different on TV - R.drawable.ic_volume_media_low + if (isStreamOffline) { + R.drawable.ic_volume_media_bt_mute } else { - getIconForStream(stream) + R.drawable.ic_volume_media_bt } } } - } - @DrawableRes - private fun getMutedIconForStream(stream: Int): Int? { - return when (stream) { - AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute - AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute - AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute - AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute - else -> null + return if (isStreamOffline) { + when (stream) { + AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute + AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute + AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute + AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute + else -> null + } ?: getIconForStream(stream) + } else { + if (level < (levelMax + levelMin) / 2) { + // This icon is different on TV + R.drawable.ic_volume_media_low + } else { + getIconForStream(stream) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt index 5750c049082f..8df9e788905c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -16,21 +16,21 @@ package com.android.systemui.volume.dialog.sliders.ui.viewmodel -import androidx.annotation.DrawableRes +import android.graphics.drawable.Drawable import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel data class VolumeDialogSliderStateModel( val minValue: Float, val maxValue: Float, val value: Float, - @DrawableRes val iconRes: Int, + val icon: Drawable, ) -fun VolumeDialogStreamModel.toStateModel(@DrawableRes iconRes: Int): VolumeDialogSliderStateModel { +fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel { return VolumeDialogSliderStateModel( minValue = levelMin.toFloat(), value = level.toFloat(), maxValue = levelMax.toFloat(), - iconRes = iconRes, + icon = icon, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index 6d8457be1014..06d9426fbe04 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -66,7 +66,8 @@ constructor( private val model: Flow<VolumeDialogStreamModel> = interactor.slider .filter { - val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0 + val currentVolumeUpdate = userVolumeUpdates.value ?: return@filter true + val lastVolumeUpdateTime = currentVolumeUpdate.timestampMillis getTimestampMillis() - lastVolumeUpdateTime > VOLUME_UPDATE_GRACE_PERIOD } .stateIn(coroutineScope, SharingStarted.Eagerly, null) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt deleted file mode 100644 index e5cf62b91677..000000000000 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.volume.dialog.ui - -import android.content.Context -import android.content.res.Resources -import com.android.systemui.dagger.qualifiers.UiBackground -import com.android.systemui.res.R -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.onConfigChanged -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog -import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope -import javax.inject.Inject -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn - -/** - * Provides cached resources [Flow]s that update when the configuration changes. - * - * Consume or use [kotlinx.coroutines.flow.first] to get the value. - */ -@VolumeDialogScope -class VolumeDialogResources -@Inject -constructor( - @VolumeDialog private val coroutineScope: CoroutineScope, - @UiBackground private val uiBackgroundContext: CoroutineContext, - private val context: Context, - private val configurationController: ConfigurationController, -) { - - val dialogShowDurationMillis: Flow<Long> = configurationResource { - getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong() - } - - val dialogHideDurationMillis: Flow<Long> = configurationResource { - getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong() - } - - private fun <T> configurationResource(get: Resources.() -> T): Flow<T> = - configurationController.onConfigChanged - .map { context.resources.get() } - .onStart { emit(context.resources.get()) } - .flowOn(uiBackgroundContext) - .stateIn(coroutineScope, SharingStarted.Eagerly, null) - .filterNotNull() -} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt index a3166a9978f4..1da2491c68b6 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.dialog.ui.binder import android.app.Dialog +import android.content.res.Resources import android.graphics.Rect import android.graphics.Region import android.view.View @@ -25,6 +26,7 @@ import android.view.ViewTreeObserver import android.view.ViewTreeObserver.InternalInsetsInfo import androidx.constraintlayout.motion.widget.MotionLayout import com.android.internal.view.RotationPolicy +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.util.children import com.android.systemui.volume.SystemUIInterpolators @@ -33,7 +35,6 @@ import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerVie import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder -import com.android.systemui.volume.dialog.ui.VolumeDialogResources import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel @@ -42,7 +43,6 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach @@ -56,7 +56,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine class VolumeDialogViewBinder @Inject constructor( - private val volumeResources: VolumeDialogResources, + @Main resources: Resources, private val viewModel: VolumeDialogViewModel, private val jankListenerFactory: JankListenerFactory, private val tracer: VolumeTracer, @@ -65,6 +65,11 @@ constructor( private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder, ) { + private val dialogShowAnimationDurationMs = + resources.getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong() + private val dialogHideAnimationDurationMs = + resources.getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong() + fun CoroutineScope.bind(dialog: Dialog) { // Root view of the Volume Dialog. val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root) @@ -99,12 +104,12 @@ constructor( is VolumeDialogVisibilityModel.Visible -> { tracer.traceVisibilityEnd(it) calculateTranslationX(view)?.let(view::setTranslationX) - view.animateShow(volumeResources.dialogShowDurationMillis.first()) + view.animateShow(dialogShowAnimationDurationMs) } is VolumeDialogVisibilityModel.Dismissed -> { tracer.traceVisibilityEnd(it) view.animateHide( - duration = volumeResources.dialogHideDurationMillis.first(), + duration = dialogHideAnimationDurationMs, translationX = calculateTranslationX(view), ) dialog.dismiss() diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt index b20dffb8ac33..7a6ede4c8b9c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt @@ -44,9 +44,9 @@ class VolumeDialogViewModel @Inject constructor( private val context: Context, - private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, + dialogVisibilityInteractor: VolumeDialogVisibilityInteractor, volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor, - private val volumeDialogStateInteractor: VolumeDialogStateInteractor, + volumeDialogStateInteractor: VolumeDialogStateInteractor, devicePostureController: DevicePostureController, configurationController: ConfigurationController, ) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index cec3d1eb86f0..5b8d9b045475 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -18,9 +18,6 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel import android.content.Context import android.media.AudioManager -import android.media.AudioManager.STREAM_ALARM -import android.media.AudioManager.STREAM_MUSIC -import android.media.AudioManager.STREAM_NOTIFICATION import android.util.Log import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.logging.UiEventLogger @@ -34,8 +31,6 @@ import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel import com.android.systemui.modes.shared.ModesUiIcons import com.android.systemui.res.R import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor -import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes -import com.android.systemui.util.kotlin.combine import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted @@ -43,12 +38,15 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn @@ -101,48 +99,16 @@ constructor( ) override val slider: StateFlow<SliderState> = - if (ModesUiIcons.isEnabled) { - combine( - audioVolumeInteractor.getAudioStream(audioStream), - audioVolumeInteractor.canChangeVolume(audioStream), - audioVolumeInteractor.ringerMode, - zenModeInteractor.activeModesBlockingEverything, - zenModeInteractor.activeModesBlockingAlarms, - zenModeInteractor.activeModesBlockingMedia, - ) { - model, - isEnabled, - ringerMode, - modesBlockingEverything, - modesBlockingAlarms, - modesBlockingMedia -> - volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume) - model.toState( - isEnabled, - ringerMode, - getStreamDisabledMessage( - modesBlockingEverything, - modesBlockingAlarms, - modesBlockingMedia, - ), - ) - } - .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) - } else { - combine( - audioVolumeInteractor.getAudioStream(audioStream), - audioVolumeInteractor.canChangeVolume(audioStream), - audioVolumeInteractor.ringerMode, - ) { model, isEnabled, ringerMode -> - volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume) - model.toState( - isEnabled, - ringerMode, - getStreamDisabledMessageWithoutModes(audioStream), - ) - } - .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) - } + combine( + audioVolumeInteractor.getAudioStream(audioStream), + audioVolumeInteractor.canChangeVolume(audioStream), + audioVolumeInteractor.ringerMode, + streamDisabledMessage(), + ) { model, isEnabled, ringerMode, streamDisabledMessage -> + volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume) + model.toState(isEnabled, ringerMode, streamDisabledMessage) + } + .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) init { volumeChanges @@ -229,40 +195,32 @@ constructor( ) } - private fun getStreamDisabledMessage( - blockingEverything: ActiveZenModes, - blockingAlarms: ActiveZenModes, - blockingMedia: ActiveZenModes, - ): String { - // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING. - // In fact, VOICE_CALL should not be affected by interruption filtering at all. - return if (audioStream.value == STREAM_NOTIFICATION) { - context.getString(R.string.stream_notification_unavailable) - } else { - val blockingModeName = - when { - blockingEverything.mainMode != null -> blockingEverything.mainMode.name - audioStream.value == STREAM_ALARM -> blockingAlarms.mainMode?.name - audioStream.value == STREAM_MUSIC -> blockingMedia.mainMode?.name - else -> null - } - - if (blockingModeName != null) { - context.getString(R.string.stream_unavailable_by_modes, blockingModeName) + // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING. + // In fact, VOICE_CALL should not be affected by interruption filtering at all. + private fun streamDisabledMessage(): Flow<String> { + return if (ModesUiIcons.isEnabled) { + if (audioStream.value == AudioManager.STREAM_NOTIFICATION) { + flowOf(context.getString(R.string.stream_notification_unavailable)) } else { - // Should not actually be visible, but as a catch-all. - context.getString(R.string.stream_unavailable_by_unknown) + if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) { + zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes + -> + blockingZenModes.mainMode?.name?.let { + context.getString(R.string.stream_unavailable_by_modes, it) + } ?: context.getString(R.string.stream_unavailable_by_unknown) + } + } else { + flowOf(context.getString(R.string.stream_unavailable_by_unknown)) + } } - } - } - - private fun getStreamDisabledMessageWithoutModes(audioStream: AudioStream): String { - // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING. - // In fact, VOICE_CALL should not be affected by interruption filtering at all. - return if (audioStream.value == STREAM_NOTIFICATION) { - context.getString(R.string.stream_notification_unavailable) } else { - context.getString(R.string.stream_alarm_unavailable) + flowOf( + if (audioStream.value == AudioManager.STREAM_NOTIFICATION) { + context.getString(R.string.stream_notification_unavailable) + } else { + context.getString(R.string.stream_alarm_unavailable) + } + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 7a3089f33276..7a3089f33276 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java index 7ba797c03a0d..86063acbf2e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java @@ -39,7 +39,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.AudioDeviceAttributes; @@ -1286,13 +1285,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { } @Test - public void setColorFilter_setColorFilterToDrawable() { - mMediaSwitchingController.setColorFilter(mDrawable, true); - - verify(mDrawable).setColorFilter(any(PorterDuffColorFilter.class)); - } - - @Test public void resetGroupMediaDevices_clearGroupDevices() { final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class); final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 2aa300df4f7c..2aa300df4f7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 2c37f510a45c..77bac59b9dcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -33,15 +32,14 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController +import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -82,9 +80,9 @@ class StackCoordinatorTest : SysuiTestCase() { sensitiveNotificationProtectionController, ) coordinator.attach(pipeline) - val captor = argumentCaptor<OnAfterRenderListListener>() - verify(pipeline).addOnAfterRenderListListener(captor.capture()) - afterRenderListListener = captor.lastValue + afterRenderListListener = withArgCaptor { + verify(pipeline).addOnAfterRenderListListener(capture()) + } } @Test @@ -94,93 +92,9 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetRenderedListOnInteractor_footerFlagOn() { - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(renderListInteractor).setRenderedList(eq(listOf(entry))) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) fun testSetNotificationStats_clearableAlerting() { whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = true, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) - fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - whenever(section.bucket).thenReturn(BUCKET_ALERTING) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = true, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_clearableSilent() { - whenever(section.bucket).thenReturn(BUCKET_SILENT) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = true, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) - fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - whenever(section.bucket).thenReturn(BUCKET_SILENT) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = true, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_clearableAlerting() { - whenever(section.bucket).thenReturn(BUCKET_ALERTING) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(activeNotificationsInteractor) .setNotifStats( NotifStats( @@ -195,12 +109,8 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags( - FooterViewRefactor.FLAG_NAME, - FLAG_SCREENSHARE_NOTIFICATION_HIDING, - FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - ) - fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableAlerting() { + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() { whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) @@ -218,8 +128,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_clearableSilent() { + fun testSetNotificationStats_clearableSilent() { whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(activeNotificationsInteractor) @@ -236,12 +145,8 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags( - FooterViewRefactor.FLAG_NAME, - FLAG_SCREENSHARE_NOTIFICATION_HIDING, - FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - ) - fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableSilent() { + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() { whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) @@ -259,8 +164,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() { + fun testSetNotificationStats_nonClearableRedacted() { entry.setSensitive(true, true) whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index 25138fd0ff83..57a12df0cfee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -123,7 +124,8 @@ class IconManagerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun testCreateIcons_chipNotifIconFlagEnabled_statusBarChipIconIsNull() { + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagDisabled_statusBarChipIconIsNotNull() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) entry?.let { iconManager.createIcons(it) } @@ -133,6 +135,17 @@ class IconManagerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagEnabled_statusBarChipIconIsNull() { + val entry = + notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) + entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() + + assertThat(entry?.icons?.statusBarChipIcon).isNull() + } + + @Test fun testCreateIcons_importantConversation_shortcutIcon() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) @@ -158,7 +171,7 @@ class IconManagerTest : SysuiTestCase() { notificationEntry( hasShortcut = false, hasMessageSenderIcon = false, - hasLargeIcon = true + hasLargeIcon = true, ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } @@ -172,7 +185,7 @@ class IconManagerTest : SysuiTestCase() { notificationEntry( hasShortcut = false, hasMessageSenderIcon = false, - hasLargeIcon = false + hasLargeIcon = false, ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } @@ -187,7 +200,7 @@ class IconManagerTest : SysuiTestCase() { hasShortcut = true, hasMessageSenderIcon = true, useMessagingStyle = false, - hasLargeIcon = true + hasLargeIcon = true, ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } @@ -205,7 +218,8 @@ class IconManagerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun testCreateIcons_sensitiveImportantConversation() { + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun testCreateIcons_cdFlagDisabled_sensitiveImportantConversation() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) entry?.setSensitive(true, true) @@ -219,8 +233,24 @@ class IconManagerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun testCreateIcons_cdFlagEnabled_sensitiveImportantConversation() { + val entry = + notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) + entry?.setSensitive(true, true) + entry?.channel?.isImportantConversation = true + entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() + assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc) + assertThat(entry?.icons?.statusBarChipIcon).isNull() + assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc) + assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc) + } + + @Test @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) - fun testUpdateIcons_sensitiveImportantConversation() { + @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun testUpdateIcons_cdFlagDisabled_sensitiveImportantConversation() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) entry?.setSensitive(true, true) @@ -236,6 +266,23 @@ class IconManagerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun testUpdateIcons_cdFlagEnabled_sensitiveImportantConversation() { + val entry = + notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) + entry?.setSensitive(true, true) + entry?.channel?.isImportantConversation = true + entry?.let { iconManager.createIcons(it) } + // Updating the icons after creation shouldn't break anything + entry?.let { iconManager.updateIcons(it) } + testScope.runCurrent() + assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc) + assertThat(entry?.icons?.statusBarChipIcon).isNull() + assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc) + assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc) + } + + @Test fun testUpdateIcons_sensitivityChange() { val entry = notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false) @@ -254,7 +301,7 @@ class IconManagerTest : SysuiTestCase() { hasShortcut: Boolean, hasMessageSenderIcon: Boolean, useMessagingStyle: Boolean = true, - hasLargeIcon: Boolean + hasLargeIcon: Boolean, ): NotificationEntry? { val n = Notification.Builder(mContext, "id") @@ -270,7 +317,7 @@ class IconManagerTest : SysuiTestCase() { SystemClock.currentThreadTimeMillis(), Person.Builder() .setIcon(if (hasMessageSenderIcon) messageIc else null) - .build() + .build(), ) ) if (useMessagingStyle) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index e1a891662889..3763282cdebc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; @@ -28,17 +27,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; -import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; @@ -64,7 +60,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; -import android.widget.TextView; import androidx.test.filters.SmallTest; @@ -92,8 +87,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; -import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.headsup.AvalancheController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -603,158 +596,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void manageNotifications_visible() { - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - when(view.willBeGone()).thenReturn(true); - - mStackScroller.updateFooterView(true, false, true); - - verify(view).setVisible(eq(true), anyBoolean()); - verify(view).setClearAllButtonVisible(eq(false), anyBoolean()); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void clearAll_visible() { - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - when(view.willBeGone()).thenReturn(true); - - mStackScroller.updateFooterView(true, true, true); - - verify(view).setVisible(eq(true), anyBoolean()); - verify(view).setClearAllButtonVisible(eq(true), anyBoolean()); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testInflateFooterView() { - mStackScroller.inflateFooterView(); - ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class); - verify(mStackScroller).setFooterView(captor.capture()); - - assertNotNull(captor.getValue().findViewById(R.id.manage_text)); - assertNotNull(captor.getValue().findViewById(R.id.dismiss_text)); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_noNotifications() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_remoteInput() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - mStackScroller.setIsRemoteInputActive(true); - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_withoutNotifications() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(false); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_oneClearableNotification() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_withoutHistory() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false); - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_oneClearableNotification_beforeUserSetup() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(false); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_oneNonClearableNotification() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(false); - when(mEmptyShadeView.getVisibility()).thenReturn(GONE); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true); - } - - @Test public void testFooterPosition_atEnd() { // add footer FooterView view = mock(FooterView.class); @@ -772,19 +613,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, - ModesEmptyShadeFix.FLAG_NAME, - NotifRedesignFooter.FLAG_NAME}) - public void testReInflatesFooterViews() { - when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text); - clearInvocations(mStackScroller); - mStackScroller.reinflateViews(); - verify(mStackScroller).setFooterView(any()); - verify(mStackScroller).setEmptyShadeView(any()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) public void testReInflatesEmptyShadeView() { when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text); @@ -1231,31 +1059,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void hasFilteredOutSeenNotifs_updateFooter() { - mStackScroller.setCurrentUserSetup(true); - - // add footer - mStackScroller.inflateFooterView(); - TextView footerLabel = - mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer); - - mStackScroller.setHasFilteredOutSeenNotifications(true); - mStackScroller.updateFooter(); - - assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME}) - public void hasFilteredOutSeenNotifs_updateEmptyShadeView() { - mStackScroller.setHasFilteredOutSeenNotifications(true); - mStackScroller.updateEmptyShadeView(true, false); - - verify(mEmptyShadeView).setFooterText(not(eq(0))); - } - - @Test @DisableSceneContainer public void testWindowInsetAnimationProgress_updatesBottomInset() { int imeInset = 100; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt index 59ad38a87146..59ad38a87146 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt index 17093291e8b0..2a1877adc172 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt @@ -24,7 +24,6 @@ fun fakeDialogTransitionAnimator( @Main mainExecutor: Executor, isUnlocked: Boolean = true, isShowingAlternateAuthOnUnlock: Boolean = false, - isPredictiveBackQsDialogAnim: Boolean = false, interactionJankMonitor: InteractionJankMonitor, ): DialogTransitionAnimator { return DialogTransitionAnimator( @@ -35,10 +34,6 @@ fun fakeDialogTransitionAnimator( isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock, ), interactionJankMonitor = interactionJankMonitor, - featureFlags = - object : AnimationFeatureFlags { - override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim - }, transitionAnimator = fakeTransitionAnimator(mainExecutor), isForTesting = true, ) @@ -50,6 +45,8 @@ private class FakeCallback( private val isShowingAlternateAuthOnUnlock: Boolean = false, ) : DialogTransitionAnimator.Callback { override fun isDreaming(): Boolean = isDreaming + override fun isUnlocked(): Boolean = isUnlocked + override fun isShowingAlternateAuthOnUnlock() = isShowingAlternateAuthOnUnlock } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 47991b3b9689..3df3ee983ecf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -154,6 +154,7 @@ val Kosmos.shortcutHelperCoreStartable by shortcutHelperStateRepository, activityStarter, testScope, + customInputGesturesRepository ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 4a86fd5e49ff..74deaab67766 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -21,6 +21,8 @@ import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -37,8 +39,8 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress.asStateFlow() - private val _currentFling: MutableStateFlow<FlingInfo?> = MutableStateFlow(null) - override val currentFling: StateFlow<FlingInfo?> = _currentFling.asStateFlow() + override val currentFling: MutableSharedFlow<FlingInfo?> = + MutableSharedFlow(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val _lockscreenShadeExpansion = MutableStateFlow(0f) override val lockscreenShadeExpansion = _lockscreenShadeExpansion.asStateFlow() @@ -139,7 +141,7 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { } override fun setCurrentFling(info: FlingInfo?) { - _currentFling.value = info + currentFling.tryEmit(info) } override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index ac545dfb06cc..c4086c5b3835 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -40,6 +40,7 @@ java_defaults { "junit-params", "platform-parametric-runner-lib", + "platform-compat-test-rules", // To make sure it won't cause VerifyError (b/324063814) "platformprotosnano", diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt index 882c91c43ee9..540b0822319c 100644 --- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt +++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/compat/RavenwoodCompatFrameworkTest.kt @@ -16,31 +16,52 @@ package com.android.ravenwoodtest.bivalenttest.compat import android.app.compat.CompatChanges +import android.compat.testing.PlatformCompatChangeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.ravenwood.RavenwoodEnvironment.CompatIdsForTest -import org.junit.Assert +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class RavenwoodCompatFrameworkTest { + + @get:Rule + val compatRule = PlatformCompatChangeRule() + @Test fun testEnabled() { - Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1)) + assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_1)) } @Test fun testDisabled() { - Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_2)) + assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_2)) } @Test fun testEnabledAfterSForUApps() { - Assert.assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_3)) + assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_3)) } @Test fun testEnabledAfterUForUApps() { - Assert.assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4)) + assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_4)) + } + + @Test + @EnableCompatChanges(CompatIdsForTest.TEST_COMPAT_ID_5) + fun testEnableCompatChanges() { + assertTrue(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_5)) + } + + @Test + @DisableCompatChanges(CompatIdsForTest.TEST_COMPAT_ID_5) + fun testDisableCompatChanges() { + assertFalse(CompatChanges.isChangeEnabled(CompatIdsForTest.TEST_COMPAT_ID_5)) } } diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java index a69ececd5373..e3d7062ddb4e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -18,6 +18,8 @@ package com.android.server.accessibility; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDICATOR_DELAY_TIME; + import android.accessibilityservice.AccessibilityTrace; import android.annotation.NonNull; import android.content.ContentResolver; @@ -286,8 +288,6 @@ public class AutoclickController extends BaseEventStreamTransformation { } public void update() { - // TODO(b/383901288): update delay time once determined by UX. - long SHOW_INDICATOR_DELAY_TIME = 150; long scheduledShowIndicatorTime = SystemClock.uptimeMillis() + SHOW_INDICATOR_DELAY_TIME; // If there already is a scheduled show indicator at time before the updated time, just @@ -432,6 +432,10 @@ public class AutoclickController extends BaseEventStreamTransformation { */ public void updateDelay(int delay) { mDelay = delay; + + if (Flags.enableAutoclickIndicator() && mAutoclickIndicatorView != null) { + mAutoclickIndicatorView.setAnimationDuration(delay - SHOW_INDICATOR_DELAY_TIME); + } } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java index 54c31e5bca79..816d8e456a9a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java @@ -16,25 +16,43 @@ package com.android.server.accessibility; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; import android.util.DisplayMetrics; import android.view.View; +import android.view.accessibility.AccessibilityManager; +import android.view.animation.LinearInterpolator; // A visual indicator for the autoclick feature. public class AutoclickIndicatorView extends View { private static final String TAG = AutoclickIndicatorView.class.getSimpleName(); + // TODO(b/383901288): update delay time once determined by UX. + static final int SHOW_INDICATOR_DELAY_TIME = 150; + + static final int MINIMAL_ANIMATION_DURATION = 50; + // TODO(b/383901288): allow users to customize the indicator area. static final float RADIUS = 50; private final Paint mPaint; + private final ValueAnimator mAnimator; + + private final RectF mRingRect; + // x and y coordinates of the visual indicator. private float mX; private float mY; + // Current sweep angle of the animated ring. + private float mSweepAngle; + + private int mAnimationDuration = AccessibilityManager.AUTOCLICK_DELAY_DEFAULT; + // Status of whether the visual indicator should display or not. private boolean showIndicator = false; @@ -46,6 +64,18 @@ public class AutoclickIndicatorView extends View { mPaint.setARGB(255, 52, 103, 235); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(10); + + mAnimator = ValueAnimator.ofFloat(0, 360); + mAnimator.setDuration(mAnimationDuration); + mAnimator.setInterpolator(new LinearInterpolator()); + mAnimator.addUpdateListener( + animation -> { + mSweepAngle = (float) animation.getAnimatedValue(); + // Redraw the view with the updated angle. + invalidate(); + }); + + mRingRect = new RectF(); } @Override @@ -53,7 +83,12 @@ public class AutoclickIndicatorView extends View { super.onDraw(canvas); if (showIndicator) { - canvas.drawCircle(mX, mY, RADIUS, mPaint); + mRingRect.set( + /* left= */ mX - RADIUS, + /* top= */ mY - RADIUS, + /* right= */ mX + RADIUS, + /* bottom= */ mY + RADIUS); + canvas.drawArc(mRingRect, /* startAngle= */ -90, mSweepAngle, false, mPaint); } } @@ -75,10 +110,17 @@ public class AutoclickIndicatorView extends View { public void redrawIndicator() { showIndicator = true; invalidate(); + mAnimator.start(); } public void clearIndicator() { showIndicator = false; + mAnimator.cancel(); invalidate(); } + + public void setAnimationDuration(int duration) { + mAnimationDuration = Math.max(duration, MINIMAL_ANIMATION_DURATION); + mAnimator.setDuration(mAnimationDuration); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index f15b8eec3f6b..cd46b38272c2 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -38,7 +38,7 @@ public class TouchState { // Pointer-related constants // This constant captures the current implementation detail that // pointer IDs are between 0 and 31 inclusive (subject to change). - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + // (See MAX_POINTER_ID in frameworks/native/include/input/Input.h) public static final int MAX_POINTER_COUNT = 32; // Constant referring to the ids bits of all pointers. public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; 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 1f3b31692289..aeb2f5e9be84 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -176,7 +176,7 @@ public class VirtualDeviceManagerService extends SystemService { public VirtualDeviceManagerService(Context context) { super(context); mImpl = new VirtualDeviceManagerImpl(); - mNativeImpl = Flags.enableNativeVdm() ? new VirtualDeviceManagerNativeImpl() : null; + mNativeImpl = new VirtualDeviceManagerNativeImpl(); mLocalService = new LocalService(); } @@ -208,9 +208,7 @@ public class VirtualDeviceManagerService extends SystemService { @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); - if (Flags.enableNativeVdm()) { - publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl); - } + publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl); publishLocalService(VirtualDeviceManagerInternal.class, mLocalService); ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService( ActivityTaskManagerInternal.class); diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 778c6864282d..31f6ef9fc062 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1708,7 +1708,7 @@ public class BinaryTransparencyService extends SystemService { private class PackageUpdatedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) { + if (!Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { return; } diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index dce9760b3971..6459016eec75 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -66,8 +66,7 @@ import com.android.server.wm.WindowManagerInternal; /** * The service that listens for gestures detected in sensor firmware and starts the intent * accordingly. - * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be - * added.</p> + * * @hide */ public class GestureLauncherService extends SystemService { @@ -109,10 +108,22 @@ public class GestureLauncherService extends SystemService { @VisibleForTesting static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000; - /** Indicates camera should be launched on power double tap. */ + /** Configuration value indicating double tap power gesture is disabled. */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_DISABLED_MODE = 0; + + /** Configuration value indicating double tap power gesture should launch camera. */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE = 1; + + /** + * Configuration value indicating double tap power gesture should launch one of many target + * actions. + */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_MULTI_TARGET_MODE = 2; + + /** Indicates camera launch is selected as target action for multi target double tap power. */ @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0; - /** Indicates wallet should be launched on power double tap. */ + /** Indicates wallet launch is selected as target action for multi target double tap power. */ @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1; /** Number of taps required to launch the double tap shortcut (either camera or wallet). */ @@ -228,6 +239,7 @@ public class GestureLauncherService extends SystemService { return mId; } } + public GestureLauncherService(Context context) { this(context, new MetricsLogger(), QuickAccessWalletClient.create(context), new UiEventLoggerImpl()); @@ -289,16 +301,15 @@ public class GestureLauncherService extends SystemService { Settings.Secure.getUriFor( Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE), false, mSettingObserver, mUserId); - } else { - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), - false, mSettingObserver, mUserId); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), - false, mSettingObserver, mUserId); } mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED), false, mSettingObserver, mUserId); mContext.getContentResolver().registerContentObserver( @@ -468,23 +479,27 @@ public class GestureLauncherService extends SystemService { Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0); } - /** Checks if camera should be launched on double press of the power button. */ public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) { - boolean res; - - if (launchWalletOptionOnPowerDoubleTap()) { - res = isDoubleTapPowerGestureSettingEnabled(context, userId) - && getDoubleTapPowerGestureAction(context, userId) - == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; - } else { - // These are legacy settings that will be deprecated once the option to launch both - // wallet and camera has been created. - res = isCameraDoubleTapPowerEnabled(context.getResources()) + if (!launchWalletOptionOnPowerDoubleTap()) { + return isCameraDoubleTapPowerEnabled(context.getResources()) && (Settings.Secure.getIntForUser(context.getContentResolver(), Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0); } - return res; + + final int doubleTapPowerGestureSettingMode = getDoubleTapPowerGestureMode( + context.getResources()); + + return switch (doubleTapPowerGestureSettingMode) { + case DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE -> Settings.Secure.getIntForUser( + context.getContentResolver(), + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0; + case DOUBLE_TAP_POWER_MULTI_TARGET_MODE -> + isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId) + && getDoubleTapPowerGestureAction(context, userId) + == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; + default -> false; + }; } /** Checks if wallet should be launched on double tap of the power button. */ @@ -493,7 +508,9 @@ public class GestureLauncherService extends SystemService { return false; } - return isDoubleTapPowerGestureSettingEnabled(context, userId) + return getDoubleTapPowerGestureMode(context.getResources()) + == DOUBLE_TAP_POWER_MULTI_TARGET_MODE + && isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId) && getDoubleTapPowerGestureAction(context, userId) == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER; } @@ -515,26 +532,40 @@ public class GestureLauncherService extends SystemService { isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0; } - private static int getDoubleTapPowerGestureAction(Context context, int userId) { - return Settings.Secure.getIntForUser( - context.getContentResolver(), - Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, - LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER, - userId); + /** Gets the double tap power gesture mode. */ + private static int getDoubleTapPowerGestureMode(Resources resources) { + return resources.getInteger(R.integer.config_doubleTapPowerGestureMode); } - /** Whether the shortcut to launch app on power double press is enabled. */ - private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) { + /** + * Whether the setting for multi target double tap power gesture is enabled. + * + * <p>Multi target double tap power gesture allows the user to choose one of many target actions + * when double tapping the power button. + * </p> + */ + private static boolean isMultiTargetDoubleTapPowerGestureSettingEnabled(Context context, + int userId) { return Settings.Secure.getIntForUser( context.getContentResolver(), Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED, - isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0, + getDoubleTapPowerGestureMode(context.getResources()) + == DOUBLE_TAP_POWER_MULTI_TARGET_MODE ? 1 : 0, userId) == 1; } - private static boolean isDoubleTapConfigEnabled(Resources resources) { - return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled); + /** Gets the selected target action for the multi target double tap power gesture. + * + * <p>The target actions are defined in {@link Settings.Secure#DOUBLE_TAP_POWER_BUTTON_GESTURE}. + * </p> + */ + private static int getDoubleTapPowerGestureAction(Context context, int userId) { + return Settings.Secure.getIntForUser( + context.getContentResolver(), + Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, + LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER, + userId); } /** @@ -595,7 +626,7 @@ public class GestureLauncherService extends SystemService { || isCameraLiftTriggerEnabled(resources) || isEmergencyGestureEnabled(resources); if (launchWalletOptionOnPowerDoubleTap()) { - res |= isDoubleTapConfigEnabled(resources); + res |= getDoubleTapPowerGestureMode(resources) != DOUBLE_TAP_POWER_DISABLED_MODE; } else { res |= isCameraDoubleTapPowerEnabled(resources); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 41b4cbd9e074..6cca7d16842a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -8950,18 +8950,12 @@ public final class ActiveServices { if (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk) { return true; // In this case, we only check the service's target SDK level. } - final int callingUid; - if (Flags.newFgsRestrictionLogic()) { - // We always consider SYSTEM_UID to target S+, so just enable the restrictions. - if (actualCallingUid == Process.SYSTEM_UID) { - return true; - } - callingUid = actualCallingUid; - } else { - // Legacy logic used mRecentCallingUid. - callingUid = r.mRecentCallingUid; + // We always consider SYSTEM_UID to target S+, so just enable the restrictions. + if (actualCallingUid == Process.SYSTEM_UID) { + return true; } - if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, callingUid)) { + if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, + actualCallingUid)) { return false; // If the caller targets < S, then we still disable the restrictions. } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 3817ba1a28b9..0b7890167c08 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -305,6 +305,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { this.stringName = null; } + @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) { + return mAllowlistDuration.get(allowlistToken); + } + void setAllowBgActivityStarts(IBinder token, int flags) { if (token == null) return; if ((flags & FLAG_ACTIVITY_SENDER) != 0) { @@ -323,6 +327,12 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mAllowBgActivityStartsForActivitySender.remove(token); mAllowBgActivityStartsForBroadcastSender.remove(token); mAllowBgActivityStartsForServiceSender.remove(token); + if (mAllowlistDuration != null) { + mAllowlistDuration.remove(token); + if (mAllowlistDuration.isEmpty()) { + mAllowlistDuration = null; + } + } } public void registerCancelListenerLocked(IResultReceiver receiver) { @@ -703,7 +713,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } - private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( + @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken) diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 92d33c9eae56..ca34a13c55b1 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -278,24 +278,21 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN * Whether to use the new "while-in-use permission" logic for FGS start */ private boolean useNewWiuLogic_forStart() { - return Flags.newFgsRestrictionLogic() // This flag should only be set on V+ - && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid); + return CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid); } /** * Whether to use the new "while-in-use permission" logic for capabilities */ private boolean useNewWiuLogic_forCapabilities() { - return Flags.newFgsRestrictionLogic() // This flag should only be set on V+ - && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid); + return CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid); } /** * Whether to use the new "FGS BG start exemption" logic. */ private boolean useNewBfslLogic() { - return Flags.newFgsRestrictionLogic() // This flag should only be set on V+ - && CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid); + return CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 295e0443371d..8a63f9a24ea3 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -359,7 +359,7 @@ public class AppOpsService extends IAppOpsService.Stub { private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10); private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW); - volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); + volatile @NonNull HistoricalRegistry mHistoricalRegistry; /* * These are app op restrictions imposed per user from various parties. @@ -1039,6 +1039,8 @@ public class AppOpsService extends IAppOpsService.Stub { // will not exist and the nonce will be UNSET. AppOpsManager.invalidateAppOpModeCache(); AppOpsManager.disableAppOpModeCache(); + + mHistoricalRegistry = new HistoricalRegistry(this, context); } public void publish() { diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 4d114b4ad4ac..9dd09cef88f9 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -113,7 +113,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE, - DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount); + DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount); } /** @@ -257,7 +257,8 @@ final class AttributedOp { if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, startTime, - attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1); + attributionFlags, attributionChainId, + DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1); } } @@ -344,8 +345,8 @@ final class AttributedOp { parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), event.getAttributionFlags(), event.getAttributionChainId(), - isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP - : DiscreteRegistry.ACCESS_TYPE_FINISH_OP); + isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP + : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP); if (!isPausing) { mAppOpsService.mInProgressStartOpEventPool.release(event); @@ -453,7 +454,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), - event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1); + event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), true, diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java new file mode 100644 index 000000000000..e4c36cc214e8 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.DefaultDatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteRawStatement; +import android.os.Environment; +import android.util.IntArray; +import android.util.Slog; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +class DiscreteOpsDbHelper extends SQLiteOpenHelper { + private static final String LOG_TAG = "DiscreteOpsDbHelper"; + static final String DATABASE_NAME = "app_op_history.db"; + private static final int DATABASE_VERSION = 1; + private static final boolean DEBUG = false; + + DiscreteOpsDbHelper(@NonNull Context context, @NonNull File databaseFile) { + super(context, databaseFile.getAbsolutePath(), null, DATABASE_VERSION, + new DiscreteOpsDatabaseErrorHandler()); + setOpenParams(getDatabaseOpenParams()); + } + + private static SQLiteDatabase.OpenParams getDatabaseOpenParams() { + return new SQLiteDatabase.OpenParams.Builder() + .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) + .build(); + } + + @NonNull + static File getDatabaseFile() { + return new File(new File(Environment.getDataSystemDirectory(), "appops"), DATABASE_NAME); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + db.execSQL("PRAGMA synchronous = NORMAL"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(DiscreteOpsTable.CREATE_TABLE_SQL); + db.execSQL(DiscreteOpsTable.CREATE_INDEX_SQL); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + + void insertDiscreteOps(@NonNull List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents) { + if (opEvents.isEmpty()) { + return; + } + + SQLiteDatabase db = getWritableDatabase(); + // TODO (b/383157289) what if database is busy and can't start a transaction? will read + // more about it and can be done in a follow up cl. + db.beginTransaction(); + try (SQLiteRawStatement statement = db.createRawStatement( + DiscreteOpsTable.INSERT_TABLE_SQL)) { + for (DiscreteOpsSqlRegistry.DiscreteOp event : opEvents) { + try { + statement.bindInt(DiscreteOpsTable.UID_INDEX, event.getUid()); + bindTextOrNull(statement, DiscreteOpsTable.PACKAGE_NAME_INDEX, + event.getPackageName()); + bindTextOrNull(statement, DiscreteOpsTable.DEVICE_ID_INDEX, + event.getDeviceId()); + statement.bindInt(DiscreteOpsTable.OP_CODE_INDEX, event.getOpCode()); + bindTextOrNull(statement, DiscreteOpsTable.ATTRIBUTION_TAG_INDEX, + event.getAttributionTag()); + statement.bindLong(DiscreteOpsTable.ACCESS_TIME_INDEX, event.getAccessTime()); + statement.bindLong( + DiscreteOpsTable.ACCESS_DURATION_INDEX, event.getDuration()); + statement.bindInt(DiscreteOpsTable.UID_STATE_INDEX, event.getUidState()); + statement.bindInt(DiscreteOpsTable.OP_FLAGS_INDEX, event.getOpFlags()); + statement.bindInt(DiscreteOpsTable.ATTRIBUTION_FLAGS_INDEX, + event.getAttributionFlags()); + statement.bindLong(DiscreteOpsTable.CHAIN_ID_INDEX, event.getChainId()); + statement.step(); + } catch (Exception exception) { + Slog.e(LOG_TAG, "Error inserting the discrete op: " + event, exception); + } finally { + statement.reset(); + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) { + if (text == null) { + statement.bindNull(index); + } else { + statement.bindText(index, text); + } + } + + /** + * This will be used as an offset for inserting new chain id in discrete ops table. + */ + long getLargestAttributionChainId() { + long chainId = 0; + try { + SQLiteDatabase db = getReadableDatabase(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = + db.createRawStatement(DiscreteOpsTable.SELECT_MAX_ATTRIBUTION_CHAIN_ID)) { + if (statement.step()) { + chainId = statement.getColumnLong(0); + if (chainId < 0) { + chainId = 0; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } catch (SQLiteException exception) { + Slog.e(LOG_TAG, "Error reading attribution chain id", exception); + } + return chainId; + } + + void execSQL(@NonNull String sql) { + execSQL(sql, null); + } + + void execSQL(@NonNull String sql, Object[] bindArgs) { + if (DEBUG) { + Slog.i(LOG_TAG, "DB execSQL, sql: " + sql); + } + SQLiteDatabase db = getWritableDatabase(); + if (bindArgs == null) { + db.execSQL(sql); + } else { + db.execSQL(sql, bindArgs); + } + } + + /** + * Returns a list of {@link DiscreteOpsSqlRegistry.DiscreteOp} based on the given filters. + */ + List<DiscreteOpsSqlRegistry.DiscreteOp> getDiscreteOps( + @AppOpsManager.HistoricalOpsRequestFilter int requestFilters, + int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter, + long beginTime, long endTime, int limit, String orderByColumn) { + List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters, + uidFilter, packageNameFilter, + attributionTagFilter, opCodesFilter, opFlagsFilter); + String sql = buildSql(conditions, orderByColumn, limit); + + SQLiteDatabase db = getReadableDatabase(); + List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = db.createRawStatement(sql)) { + int size = conditions.size(); + for (int i = 0; i < size; i++) { + SQLCondition condition = conditions.get(i); + if (DEBUG) { + Slog.i(LOG_TAG, condition + ", binding value = " + condition.mFilterValue); + } + switch (condition.mColumnFilter) { + case PACKAGE_NAME, ATTR_TAG -> statement.bindText(i + 1, + condition.mFilterValue.toString()); + case UID, OP_CODE_EQUAL, OP_FLAGS -> statement.bindInt(i + 1, + Integer.parseInt(condition.mFilterValue.toString())); + case BEGIN_TIME, END_TIME -> statement.bindLong(i + 1, + Long.parseLong(condition.mFilterValue.toString())); + case OP_CODE_IN -> Slog.d(LOG_TAG, "No binding for In operator"); + default -> Slog.w(LOG_TAG, "unknown sql condition " + condition); + } + } + + while (statement.step()) { + int uid = statement.getColumnInt(0); + String packageName = statement.getColumnText(1); + String deviceId = statement.getColumnText(2); + int opCode = statement.getColumnInt(3); + String attributionTag = statement.getColumnText(4); + long accessTime = statement.getColumnLong(5); + long duration = statement.getColumnLong(6); + int uidState = statement.getColumnInt(7); + int opFlags = statement.getColumnInt(8); + int attributionFlags = statement.getColumnInt(9); + long chainId = statement.getColumnLong(10); + DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, attributionTag, deviceId, opCode, + opFlags, attributionFlags, uidState, chainId, accessTime, duration); + results.add(event); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return results; + } + + private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) { + StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA); + if (!conditions.isEmpty()) { + sql.append(" WHERE "); + int size = conditions.size(); + for (int i = 0; i < size; i++) { + sql.append(conditions.get(i).toString()); + if (i < size - 1) { + sql.append(" AND "); + } + } + } + + if (orderByColumn != null) { + sql.append(" ORDER BY ").append(orderByColumn); + } + if (limit > 0) { + sql.append(" LIMIT ").append(limit); + } + if (DEBUG) { + Slog.i(LOG_TAG, "Sql query " + sql); + } + return sql.toString(); + } + + /** + * Creates where conditions for package, uid, attribution tag and app op codes, + * app op codes condition does not support argument binding. + */ + private List<SQLCondition> prepareConditions(long beginTime, long endTime, int requestFilters, + int uid, @Nullable String packageName, @Nullable String attributionTag, + IntArray opCodes, int opFlags) { + final List<SQLCondition> conditions = new ArrayList<>(); + + if (beginTime != -1) { + conditions.add(new SQLCondition(ColumnFilter.BEGIN_TIME, beginTime)); + } + if (endTime != -1) { + conditions.add(new SQLCondition(ColumnFilter.END_TIME, endTime)); + } + if (opFlags != 0) { + conditions.add(new SQLCondition(ColumnFilter.OP_FLAGS, opFlags)); + } + + if (requestFilters != 0) { + if ((requestFilters & AppOpsManager.FILTER_BY_PACKAGE_NAME) != 0) { + conditions.add(new SQLCondition(ColumnFilter.PACKAGE_NAME, packageName)); + } + if ((requestFilters & AppOpsManager.FILTER_BY_UID) != 0) { + conditions.add(new SQLCondition(ColumnFilter.UID, uid)); + + } + if ((requestFilters & AppOpsManager.FILTER_BY_ATTRIBUTION_TAG) != 0) { + conditions.add(new SQLCondition(ColumnFilter.ATTR_TAG, attributionTag)); + } + // filter op codes + if (opCodes != null && opCodes.size() == 1) { + conditions.add(new SQLCondition(ColumnFilter.OP_CODE_EQUAL, opCodes.get(0))); + } else if (opCodes != null && opCodes.size() > 1) { + StringBuilder b = new StringBuilder(); + int size = opCodes.size(); + for (int i = 0; i < size; i++) { + b.append(opCodes.get(i)); + if (i < size - 1) { + b.append(", "); + } + } + conditions.add(new SQLCondition(ColumnFilter.OP_CODE_IN, b.toString())); + } + } + return conditions; + } + + /** + * This class prepares a where clause condition for discrete ops table column. + */ + static final class SQLCondition { + private final ColumnFilter mColumnFilter; + private final Object mFilterValue; + + SQLCondition(ColumnFilter columnFilter, Object filterValue) { + mColumnFilter = columnFilter; + mFilterValue = filterValue; + } + + @Override + public String toString() { + if (mColumnFilter == ColumnFilter.OP_CODE_IN) { + return mColumnFilter + " ( " + mFilterValue + " )"; + } + return mColumnFilter.toString(); + } + } + + /** + * This enum describes the where clause conditions for different columns in discrete ops + * table. + */ + private enum ColumnFilter { + PACKAGE_NAME(DiscreteOpsTable.Columns.PACKAGE_NAME + " = ? "), + UID(DiscreteOpsTable.Columns.UID + " = ? "), + ATTR_TAG(DiscreteOpsTable.Columns.ATTRIBUTION_TAG + " = ? "), + END_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " < ? "), + OP_CODE_EQUAL(DiscreteOpsTable.Columns.OP_CODE + " = ? "), + BEGIN_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " + " + + DiscreteOpsTable.Columns.ACCESS_DURATION + " > ? "), + OP_FLAGS("(" + DiscreteOpsTable.Columns.OP_FLAGS + " & ? ) != 0"), + OP_CODE_IN(DiscreteOpsTable.Columns.OP_CODE + " IN "); + + final String mCondition; + + ColumnFilter(String condition) { + mCondition = condition; + } + + @Override + public String toString() { + return mCondition; + } + } + + static final class DiscreteOpsDatabaseErrorHandler implements DatabaseErrorHandler { + private final DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler = + new DefaultDatabaseErrorHandler(); + + @Override + public void onCorruption(SQLiteDatabase dbObj) { + Slog.e(LOG_TAG, "discrete ops database got corrupted."); + mDefaultDatabaseErrorHandler.onCorruption(dbObj); + } + } + + // USED for testing only + List<DiscreteOpsSqlRegistry.DiscreteOp> getAllDiscreteOps(@NonNull String sql) { + SQLiteDatabase db = getReadableDatabase(); + List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = db.createRawStatement(sql)) { + while (statement.step()) { + int uid = statement.getColumnInt(0); + String packageName = statement.getColumnText(1); + String deviceId = statement.getColumnText(2); + int opCode = statement.getColumnInt(3); + String attributionTag = statement.getColumnText(4); + long accessTime = statement.getColumnLong(5); + long duration = statement.getColumnLong(6); + int uidState = statement.getColumnInt(7); + int opFlags = statement.getColumnInt(8); + int attributionFlags = statement.getColumnInt(9); + long chainId = statement.getColumnLong(10); + DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, attributionTag, deviceId, opCode, + opFlags, attributionFlags, uidState, chainId, accessTime, duration); + results.add(event); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return results; + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java new file mode 100644 index 000000000000..c38ee55b4f42 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for migrating discrete ops from xml to sqlite + */ +public class DiscreteOpsMigrationHelper { + /** + * migrate discrete ops from xml to sqlite. + */ + static void migrateDiscreteOpsToSqlite(DiscreteOpsXmlRegistry xmlRegistry, + DiscreteOpsSqlRegistry sqlRegistry) { + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps(); + List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = getSqlDiscreteOps(xmlOps); + sqlRegistry.migrateXmlData(discreteOps, xmlOps.mChainIdOffset); + xmlRegistry.deleteDiscreteOpsDir(); + } + + /** + * rollback discrete ops from sqlite to xml. + */ + static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry, + DiscreteOpsXmlRegistry xmlRegistry) { + List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps(); + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps); + xmlRegistry.migrateSqliteData(xmlOps); + sqlRegistry.deleteDatabase(); + } + + /** + * Convert sqlite flat rows to hierarchical data. + */ + private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps( + List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = + new DiscreteOpsXmlRegistry.DiscreteOps(0); + if (discreteOps.isEmpty()) { + return xmlOps; + } + + for (DiscreteOpsSqlRegistry.DiscreteOp discreteOp : discreteOps) { + xmlOps.addDiscreteAccess(discreteOp.getOpCode(), discreteOp.getUid(), + discreteOp.getPackageName(), discreteOp.getDeviceId(), + discreteOp.getAttributionTag(), discreteOp.getOpFlags(), + discreteOp.getUidState(), + discreteOp.getAccessTime(), discreteOp.getDuration(), + discreteOp.getAttributionFlags(), (int) discreteOp.getChainId()); + } + return xmlOps; + } + + /** + * Convert xml (hierarchical) data to flat row based data. + */ + private static List<DiscreteOpsSqlRegistry.DiscreteOp> getSqlDiscreteOps( + DiscreteOpsXmlRegistry.DiscreteOps discreteOps) { + List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>(); + + if (discreteOps.isEmpty()) { + return opEvents; + } + + discreteOps.mUids.forEach((uid, discreteUidOps) -> { + discreteUidOps.mPackages.forEach((packageName, packageOps) -> { + packageOps.mPackageOps.forEach((opcode, ops) -> { + ops.mDeviceAttributedOps.forEach((deviceId, deviceOps) -> { + deviceOps.mAttributedOps.forEach((tag, attributedOps) -> { + for (DiscreteOpsXmlRegistry.DiscreteOpEvent attributedOp : + attributedOps) { + DiscreteOpsSqlRegistry.DiscreteOp + opModel = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, tag, + deviceId, opcode, attributedOp.mOpFlag, + attributedOp.mAttributionFlags, + attributedOp.mUidState, attributedOp.mAttributionChainId, + attributedOp.mNoteTime, + attributedOp.mNoteDuration); + opEvents.add(opModel); + } + }); + }); + }); + }); + }); + + return opEvents; + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java new file mode 100644 index 000000000000..88b3f6dce4c2 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; +import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; +import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; +import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; +import static android.app.AppOpsManager.OP_READ_ICC_SMS; +import static android.app.AppOpsManager.OP_READ_SMS; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; +import static android.app.AppOpsManager.OP_SEND_SMS; +import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; +import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; +import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; +import static android.app.AppOpsManager.OP_WRITE_SMS; + +import static java.lang.Long.min; +import static java.lang.Math.max; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.AsyncTask; +import android.os.Build; +import android.permission.flags.Flags; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Date; +import java.util.Set; + +/** + * This class provides interface for xml and sqlite implementation. Implementation manages + * information about recent accesses to ops for permission usage timeline. + * <p> + * The discrete history is kept for limited time (initial default is 24 hours, set in + * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that. + * <p> + * Discrete history is quantized to reduce resources footprint. By default, quantization is set to + * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are + * aligned to the closest quantized time. All durations (except -1, meaning no duration) are + * rounded up to the closest quantized interval. + * <p> + * When data is queried through API, events are deduplicated and for every time quant there can + * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about + * different accesses which happened in specified time quant - across dimensions of + * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension + * it is only possible to know if at least one access happened in the time quant. + * <p> + * INITIALIZATION: We can initialize persistence only after the system is ready + * as we need to check the optional configuration override from the settings + * database which is not initialized at the time the app ops service is created. This class + * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All + * outside calls are going through {@link HistoricalRegistry}. + * + */ +abstract class DiscreteOpsRegistry { + private static final String TAG = DiscreteOpsRegistry.class.getSimpleName(); + + static final boolean DEBUG_LOG = false; + static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; + static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = + "discrete_history_quantization_millis"; + static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; + static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; + static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," + + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + + "," + OP_RESERVED_FOR_TESTING; + static final int[] sDiscreteOpsToLog = + new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, + OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, + OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, + OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, + OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, + }; + + static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); + static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); + // The duration for which the data is kept, default is 7 days and max 30 days enforced. + static long sDiscreteHistoryCutoff; + + static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis(); + // discrete ops are rounded up to quantization time, meaning we record one op per time bucket + // in case of duplicate op events. + static long sDiscreteHistoryQuantization; + + static int[] sDiscreteOps; + static int sDiscreteFlags; + + static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED + | OP_FLAG_TRUSTED_PROXY; + + boolean mDebugMode = false; + + static final int ACCESS_TYPE_NOTE_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; + static final int ACCESS_TYPE_START_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; + static final int ACCESS_TYPE_FINISH_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; + static final int ACCESS_TYPE_PAUSE_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; + static final int ACCESS_TYPE_RESUME_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ACCESS_TYPE_"}, value = { + ACCESS_TYPE_NOTE_OP, + ACCESS_TYPE_START_OP, + ACCESS_TYPE_FINISH_OP, + ACCESS_TYPE_PAUSE_OP, + ACCESS_TYPE_RESUME_OP + }) + @interface AccessType {} + + void systemReady() { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, + AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { + setDiscreteHistoryParameters(p); + }); + setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY)); + } + + abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, + int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, + @DiscreteOpsRegistry.AccessType int accessType); + + /** + * A periodic callback from {@link AppOpsService} to flush the in memory events to disk. + * The shutdown callback is also plugged into it. + * <p> + * This method flushes in memory records to disk, and also clears old records from disk. + */ + abstract void writeAndClearOldAccessHistory(); + + /** Remove all discrete op events. */ + abstract void clearHistory(); + + /** Remove all discrete op events for given UID and package. */ + abstract void clearHistory(int uid, String packageName); + + /** + * Offset access time by given timestamp, new access time would be accessTime - offsetMillis. + */ + abstract void offsetHistory(long offset); + + abstract void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, + Set<String> attributionExemptPkgs); + + abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps); + + void setDebugMode(boolean debugMode) { + this.mDebugMode = debugMode; + } + + static long discretizeTimeStamp(long timeStamp) { + return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + + } + + static long discretizeDuration(long duration) { + return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) + / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + } + + static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { + if (!ArrayUtils.contains(sDiscreteOps, op)) { + return false; + } + if ((flags & (sDiscreteFlags)) == 0) { + return false; + } + return true; + } + + // could this be impl detail of discrete registry, just one test is using the method + // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps(); + + private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { + if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { + sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, + DEFAULT_DISCRETE_HISTORY_CUTOFF); + if (!Build.IS_DEBUGGABLE && !mDebugMode) { + sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF, + sDiscreteHistoryCutoff); + } + } else { + sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF; + } + if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) { + sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION, + DEFAULT_DISCRETE_HISTORY_QUANTIZATION); + if (!Build.IS_DEBUGGABLE && !mDebugMode) { + sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION, + sDiscreteHistoryQuantization); + } + } else { + sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; + } + sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags = + p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; + sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList( + p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList( + DEFAULT_DISCRETE_OPS); + } + + private static int[] parseOpsList(String opsList) { + String[] strArr; + if (opsList.isEmpty()) { + strArr = new String[0]; + } else { + strArr = opsList.split(","); + } + int nOps = strArr.length; + int[] result = new int[nOps]; + try { + for (int i = 0; i < nOps; i++) { + result[i] = Integer.parseInt(strArr[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); + return parseOpsList(DEFAULT_DISCRETE_OPS); + } + return result; + } + + /** + * Whether app op access tacking is enabled and a metric event should be logged. + */ + static boolean shouldLogAccess(int op) { + return Flags.appopAccessTrackingLoggingEnabled() + && ArrayUtils.contains(sDiscreteOpsToLog, op); + } + + String getAttributionTag(String attributionTag, String packageName) { + if (attributionTag == null || packageName == null) { + return attributionTag; + } + int firstChar = 0; + if (attributionTag.startsWith(packageName)) { + firstChar = packageName.length(); + if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) + == '.') { + firstChar++; + } + } + return attributionTag.substring(firstChar); + } + +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java new file mode 100644 index 000000000000..4b3981cd4bc0 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; +import static android.app.AppOpsManager.flagsToString; +import static android.app.AppOpsManager.getUidStateName; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.ServiceThread; + +import java.io.File; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * This class handles sqlite persistence layer for discrete ops. + */ +public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { + private static final String TAG = "DiscreteOpsSqlRegistry"; + + private final Context mContext; + private final DiscreteOpsDbHelper mDiscreteOpsDbHelper; + private final SqliteWriteHandler mSqliteWriteHandler; + private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512); + private static final long THREE_HOURS = Duration.ofHours(3).toMillis(); + private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1; + private static final int DELETE_OLD_OP_EVENTS = 2; + // Attribution chain id is used to identify an attribution source chain, This is + // set for startOp only. PermissionManagerService resets this ID on device restart, so + // we use previously persisted chain id as offset, and add it to chain id received from + // permission manager service. + private long mChainIdOffset; + private final File mDatabaseFile; + + DiscreteOpsSqlRegistry(Context context) { + this(context, DiscreteOpsDbHelper.getDatabaseFile()); + } + + DiscreteOpsSqlRegistry(Context context, File databaseFile) { + ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true); + thread.start(); + mContext = context; + mDatabaseFile = databaseFile; + mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper()); + mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile); + mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId(); + } + + @Override + void recordDiscreteAccess(int uid, String packageName, + @NonNull String deviceId, int op, + @Nullable String attributionTag, int flags, int uidState, + long accessTime, long accessDuration, int attributionFlags, int attributionChainId, + int accessType) { + if (shouldLogAccess(op)) { + FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, + uidState, flags, attributionFlags, + getAttributionTag(attributionTag, packageName), + attributionChainId); + } + + if (!isDiscreteOp(op, flags)) { + return; + } + + long offsetChainId = attributionChainId; + if (attributionChainId != ATTRIBUTION_CHAIN_ID_NONE) { + offsetChainId = attributionChainId + mChainIdOffset; + // PermissionManagerService chain id reached the max value, + // reset offset, it's going to be very rare. + if (attributionChainId == Integer.MAX_VALUE) { + mChainIdOffset = offsetChainId; + } + } + DiscreteOp discreteOpEvent = new DiscreteOp(uid, packageName, attributionTag, deviceId, op, + flags, attributionFlags, uidState, offsetChainId, accessTime, accessDuration); + mDiscreteOpCache.add(discreteOpEvent); + } + + @Override + void writeAndClearOldAccessHistory() { + // Let the sql impl also follow the same disk write frequencies as xml, + // controlled by AppOpsService. + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); + if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) { + if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) { + Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued"); + } + } + } + + @Override + void clearHistory() { + mDiscreteOpCache.clear(); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_TABLE_DATA); + } + + @Override + void clearHistory(int uid, String packageName) { + mDiscreteOpCache.clear(uid, packageName); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_DATA_FOR_UID_PACKAGE, + new Object[]{uid, packageName}); + } + + @Override + void offsetHistory(long offset) { + mDiscreteOpCache.offsetTimestamp(offset); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.OFFSET_ACCESS_TIME, + new Object[]{offset}); + } + + private IntArray getAppOpCodes(@AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter) { + if ((filter & AppOpsManager.FILTER_BY_OP_NAMES) != 0) { + IntArray opCodes = new IntArray(opNamesFilter.length); + for (int i = 0; i < opNamesFilter.length; i++) { + int op; + try { + op = AppOpsManager.strOpToOp(opNamesFilter[i]); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Appop `" + opNamesFilter[i] + "` is not recognized."); + continue; + } + opCodes.add(op); + } + return opCodes; + } + return null; + } + + @Override + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, int filter, int uidFilter, + @Nullable String packageNameFilter, + @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, int opFlagsFilter, + Set<String> attributionExemptPkgs) { + // flush the cache into database before read. + writeAndClearOldAccessHistory(); + boolean assembleChains = attributionExemptPkgs != null; + IntArray opCodes = getAppOpCodes(filter, opNamesFilter); + List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, + packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis, + endTimeMillis, -1, null); + + LongSparseArray<AttributionChain> attributionChains = null; + if (assembleChains) { + attributionChains = createAttributionChains(discreteOps, attributionExemptPkgs); + } + + int nEvents = discreteOps.size(); + for (int j = 0; j < nEvents; j++) { + DiscreteOp event = discreteOps.get(j); + AppOpsManager.OpEventProxyInfo proxy = null; + if (assembleChains && event.mChainId != ATTRIBUTION_CHAIN_ID_NONE) { + AttributionChain chain = attributionChains.get(event.mChainId); + if (chain != null && chain.isComplete() + && chain.isStart(event) + && chain.mLastVisibleEvent != null) { + DiscreteOp proxyEvent = chain.mLastVisibleEvent; + proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid, + proxyEvent.mPackageName, proxyEvent.mAttributionTag); + } + } + result.addDiscreteAccess(event.mOpCode, event.mUid, event.mPackageName, + event.mAttributionTag, event.mUidState, event.mOpFlags, + event.mDiscretizedAccessTime, event.mDiscretizedDuration, proxy); + } + } + + @Override + void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps) { + writeAndClearOldAccessHistory(); + IntArray opCodes = new IntArray(); + if (dumpOp != AppOpsManager.OP_NONE) { + opCodes.add(dumpOp); + } + List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, + packageNameFilter, attributionTagFilter, opCodes, 0, -1, + -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME); + + pw.print(prefix); + pw.print("Largest chain id: "); + pw.print(mDiscreteOpsDbHelper.getLargestAttributionChainId()); + pw.println(); + pw.println("UID|PACKAGE_NAME|DEVICE_ID|OP_NAME|ATTRIBUTION_TAG|UID_STATE|OP_FLAGS|" + + "ATTR_FLAGS|CHAIN_ID|ACCESS_TIME|DURATION"); + int discreteOpsCount = discreteOps.size(); + for (int i = 0; i < discreteOpsCount; i++) { + DiscreteOp event = discreteOps.get(i); + date.setTime(event.mAccessTime); + pw.println(event.mUid + "|" + event.mPackageName + "|" + event.mDeviceId + "|" + + AppOpsManager.opToName(event.mOpCode) + "|" + event.mAttributionTag + "|" + + getUidStateName(event.mUidState) + "|" + + flagsToString(event.mOpFlags) + "|" + event.mAttributionFlags + "|" + + event.mChainId + "|" + + sdf.format(date) + "|" + event.mDuration); + } + pw.println(); + } + + void migrateXmlData(List<DiscreteOp> opEvents, int chainIdOffset) { + mChainIdOffset = chainIdOffset; + mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); + } + + LongSparseArray<AttributionChain> createAttributionChains( + List<DiscreteOp> discreteOps, Set<String> attributionExemptPkgs) { + LongSparseArray<AttributionChain> chains = new LongSparseArray<>(); + final int count = discreteOps.size(); + + for (int i = 0; i < count; i++) { + DiscreteOp opEvent = discreteOps.get(i); + if (opEvent.mChainId == ATTRIBUTION_CHAIN_ID_NONE + || (opEvent.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { + continue; + } + AttributionChain chain = chains.get(opEvent.mChainId); + if (chain == null) { + chain = new AttributionChain(attributionExemptPkgs); + chains.put(opEvent.mChainId, chain); + } + chain.addEvent(opEvent); + } + return chains; + } + + static class AttributionChain { + List<DiscreteOp> mChain = new ArrayList<>(); + Set<String> mExemptPkgs; + DiscreteOp mStartEvent = null; + DiscreteOp mLastVisibleEvent = null; + + AttributionChain(Set<String> exemptPkgs) { + mExemptPkgs = exemptPkgs; + } + + boolean isComplete() { + return !mChain.isEmpty() && getStart() != null && isEnd(mChain.get(mChain.size() - 1)); + } + + DiscreteOp getStart() { + return mChain.isEmpty() || !isStart(mChain.get(0)) ? null : mChain.get(0); + } + + private boolean isEnd(DiscreteOp event) { + return event != null + && (event.mAttributionFlags & ATTRIBUTION_FLAG_ACCESSOR) != 0; + } + + private boolean isStart(DiscreteOp event) { + return event != null + && (event.mAttributionFlags & ATTRIBUTION_FLAG_RECEIVER) != 0; + } + + DiscreteOp getLastVisible() { + // Search all nodes but the first one, which is the start node + for (int i = mChain.size() - 1; i > 0; i--) { + DiscreteOp event = mChain.get(i); + if (!mExemptPkgs.contains(event.mPackageName)) { + return event; + } + } + return null; + } + + void addEvent(DiscreteOp opEvent) { + // check if we have a matching event except duration. + DiscreteOp matchingItem = null; + for (int i = 0; i < mChain.size(); i++) { + DiscreteOp item = mChain.get(i); + if (item.equalsExceptDuration(opEvent)) { + matchingItem = item; + break; + } + } + + if (matchingItem != null) { + // exact match or existing event has longer duration + if (matchingItem.mDuration == opEvent.mDuration + || matchingItem.mDuration > opEvent.mDuration) { + return; + } + mChain.remove(matchingItem); + } + + if (mChain.isEmpty() || isEnd(opEvent)) { + mChain.add(opEvent); + } else if (isStart(opEvent)) { + mChain.add(0, opEvent); + } else { + for (int i = 0; i < mChain.size(); i++) { + DiscreteOp currEvent = mChain.get(i); + if ((!isStart(currEvent) + && currEvent.mAccessTime > opEvent.mAccessTime) + || (i == mChain.size() - 1 && isEnd(currEvent))) { + mChain.add(i, opEvent); + break; + } else if (i == mChain.size() - 1) { + mChain.add(opEvent); + break; + } + } + } + mStartEvent = isComplete() ? getStart() : null; + mLastVisibleEvent = isComplete() ? getLastVisible() : null; + } + } + + /** + * Handler to write asynchronously to sqlite database. + */ + class SqliteWriteHandler extends Handler { + SqliteWriteHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case WRITE_CACHE_EVICTED_OP_EVENTS: + List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj; + mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); + break; + case DELETE_OLD_OP_EVENTS: + long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff; + mDiscreteOpsDbHelper.execSQL( + DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, + new Object[]{cutOffTimeStamp}); + break; + default: + throw new IllegalStateException("Unexpected value: " + msg.what); + } + } + } + + /** + * A write cache for discrete ops. The noteOp, start/finishOp discrete op events are written to + * the cache first. + * <p> + * These events are persisted into sqlite database + * 1) Periodic interval, controlled by {@link AppOpsService} + * 2) When total events in the cache exceeds cache limit. + * 3) During read call we flush the whole cache to sqlite. + * 4) During shutdown. + */ + class DiscreteOpCache { + private final int mCapacity; + private final ArraySet<DiscreteOp> mCache; + + DiscreteOpCache(int capacity) { + mCapacity = capacity; + mCache = new ArraySet<>(); + } + + public void add(DiscreteOp opEvent) { + synchronized (this) { + if (mCache.contains(opEvent)) { + return; + } + mCache.add(opEvent); + if (mCache.size() >= mCapacity) { + if (DEBUG_LOG) { + Slog.i(TAG, "Current discrete ops cache size: " + mCache.size()); + } + List<DiscreteOp> evictedEvents = evict(); + if (DEBUG_LOG) { + Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size()); + } + // if nothing to evict, just write the whole cache to disk + if (evictedEvents.isEmpty()) { + Slog.w(TAG, "No discrete ops event is evicted, write cache to db."); + evictedEvents.addAll(mCache); + mCache.clear(); + } + mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); + } + } + } + + /** + * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. + */ + private List<DiscreteOp> evict() { + synchronized (this) { + List<DiscreteOp> evictedEvents = new ArrayList<>(); + Set<DiscreteOp> snapshot = new ArraySet<>(mCache); + long evictionTimestamp = System.currentTimeMillis() - sDiscreteHistoryQuantization; + evictionTimestamp = discretizeTimeStamp(evictionTimestamp); + for (DiscreteOp opEvent : snapshot) { + if (opEvent.mDiscretizedAccessTime <= evictionTimestamp) { + evictedEvents.add(opEvent); + mCache.remove(opEvent); + } + } + return evictedEvents; + } + } + + /** + * Remove all the entries from cache. + * + * @return return all removed entries. + */ + public List<DiscreteOp> getAllEventsAndClear() { + synchronized (this) { + List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size()); + if (mCache.isEmpty()) { + return cachedOps; + } + cachedOps.addAll(mCache); + mCache.clear(); + return cachedOps; + } + } + + /** + * Remove all entries from the cache. + */ + public void clear() { + synchronized (this) { + mCache.clear(); + } + } + + /** + * Offset access time by given offset milliseconds. + */ + public void offsetTimestamp(long offsetMillis) { + synchronized (this) { + List<DiscreteOp> cachedOps = new ArrayList<>(mCache); + mCache.clear(); + for (DiscreteOp discreteOp : cachedOps) { + add(new DiscreteOp(discreteOp.getUid(), discreteOp.mPackageName, + discreteOp.getAttributionTag(), discreteOp.getDeviceId(), + discreteOp.mOpCode, discreteOp.mOpFlags, + discreteOp.getAttributionFlags(), discreteOp.getUidState(), + discreteOp.getChainId(), discreteOp.mAccessTime - offsetMillis, + discreteOp.getDuration()) + ); + } + } + } + + /** Remove cached events for given UID and package. */ + public void clear(int uid, String packageName) { + synchronized (this) { + Set<DiscreteOp> snapshot = new ArraySet<>(mCache); + for (DiscreteOp currentEvent : snapshot) { + if (Objects.equals(packageName, currentEvent.mPackageName) + && uid == currentEvent.getUid()) { + mCache.remove(currentEvent); + } + } + } + } + } + + /** Immutable discrete op object. */ + static class DiscreteOp { + private final int mUid; + private final String mPackageName; + private final String mAttributionTag; + private final String mDeviceId; + private final int mOpCode; + private final int mOpFlags; + private final int mAttributionFlags; + private final int mUidState; + private final long mChainId; + private final long mAccessTime; + private final long mDuration; + // store discretized timestamp to avoid repeated calculations. + private final long mDiscretizedAccessTime; + private final long mDiscretizedDuration; + + DiscreteOp(int uid, String packageName, String attributionTag, String deviceId, + int opCode, + int mOpFlags, int mAttributionFlags, int uidState, long chainId, long accessTime, + long duration) { + this.mUid = uid; + this.mPackageName = packageName.intern(); + this.mAttributionTag = attributionTag; + this.mDeviceId = deviceId; + this.mOpCode = opCode; + this.mOpFlags = mOpFlags; + this.mAttributionFlags = mAttributionFlags; + this.mUidState = uidState; + this.mChainId = chainId; + this.mAccessTime = accessTime; + this.mDiscretizedAccessTime = discretizeTimeStamp(accessTime); + this.mDuration = duration; + this.mDiscretizedDuration = discretizeDuration(duration); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DiscreteOp that)) return false; + + if (mUid != that.mUid) return false; + if (mOpCode != that.mOpCode) return false; + if (mOpFlags != that.mOpFlags) return false; + if (mAttributionFlags != that.mAttributionFlags) return false; + if (mUidState != that.mUidState) return false; + if (mChainId != that.mChainId) return false; + if (!Objects.equals(mPackageName, that.mPackageName)) { + return false; + } + if (!Objects.equals(mAttributionTag, that.mAttributionTag)) { + return false; + } + if (!Objects.equals(mDeviceId, that.mDeviceId)) { + return false; + } + if (mDiscretizedAccessTime != that.mDiscretizedAccessTime) { + return false; + } + return mDiscretizedDuration == that.mDiscretizedDuration; + } + + @Override + public int hashCode() { + int result = mUid; + result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0); + result = 31 * result + (mAttributionTag != null ? mAttributionTag.hashCode() : 0); + result = 31 * result + (mDeviceId != null ? mDeviceId.hashCode() : 0); + result = 31 * result + mOpCode; + result = 31 * result + mOpFlags; + result = 31 * result + mAttributionFlags; + result = 31 * result + mUidState; + result = 31 * result + Objects.hash(mChainId); + result = 31 * result + Objects.hash(mDiscretizedAccessTime); + result = 31 * result + Objects.hash(mDiscretizedDuration); + return result; + } + + public boolean equalsExceptDuration(DiscreteOp that) { + if (mUid != that.mUid) return false; + if (mOpCode != that.mOpCode) return false; + if (mOpFlags != that.mOpFlags) return false; + if (mAttributionFlags != that.mAttributionFlags) return false; + if (mUidState != that.mUidState) return false; + if (mChainId != that.mChainId) return false; + if (!Objects.equals(mPackageName, that.mPackageName)) { + return false; + } + if (!Objects.equals(mAttributionTag, that.mAttributionTag)) { + return false; + } + if (!Objects.equals(mDeviceId, that.mDeviceId)) { + return false; + } + return mAccessTime == that.mAccessTime; + } + + @Override + public String toString() { + return "DiscreteOp{" + + "uid=" + mUid + + ", packageName='" + mPackageName + '\'' + + ", attributionTag='" + mAttributionTag + '\'' + + ", deviceId='" + mDeviceId + '\'' + + ", opCode=" + AppOpsManager.opToName(mOpCode) + + ", opFlag=" + flagsToString(mOpFlags) + + ", attributionFlag=" + mAttributionFlags + + ", uidState=" + getUidStateName(mUidState) + + ", chainId=" + mChainId + + ", accessTime=" + mAccessTime + + ", duration=" + mDuration + '}'; + } + + public int getUid() { + return mUid; + } + + public String getPackageName() { + return mPackageName; + } + + public String getAttributionTag() { + return mAttributionTag; + } + + public String getDeviceId() { + return mDeviceId; + } + + public int getOpCode() { + return mOpCode; + } + + @AppOpsManager.OpFlags + public int getOpFlags() { + return mOpFlags; + } + + + @AppOpsManager.AttributionFlags + public int getAttributionFlags() { + return mAttributionFlags; + } + + @AppOpsManager.UidState + public int getUidState() { + return mUidState; + } + + public long getChainId() { + return mChainId; + } + + public long getAccessTime() { + return mAccessTime; + } + + public long getDuration() { + return mDuration; + } + } + + // API for tests only, can be removed or changed. + void recordDiscreteAccess(DiscreteOp discreteOpEvent) { + mDiscreteOpCache.add(discreteOpEvent); + } + + // API for tests only, can be removed or changed. + List<DiscreteOp> getCachedDiscreteOps() { + return new ArrayList<>(mDiscreteOpCache.mCache); + } + + // API for tests only, can be removed or changed. + List<DiscreteOp> getAllDiscreteOps() { + List<DiscreteOp> ops = new ArrayList<>(mDiscreteOpCache.mCache); + ops.addAll(mDiscreteOpsDbHelper.getAllDiscreteOps(DiscreteOpsTable.SELECT_TABLE_DATA)); + return ops; + } + + // API for testing and migration + long getLargestAttributionChainId() { + return mDiscreteOpsDbHelper.getLargestAttributionChainId(); + } + + // API for testing and migration + void deleteDatabase() { + mDiscreteOpsDbHelper.close(); + mContext.deleteDatabase(mDatabaseFile.getName()); + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTable.java b/services/core/java/com/android/server/appop/DiscreteOpsTable.java new file mode 100644 index 000000000000..9cb19aa30a15 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsTable.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + + +/** + * SQLite table for storing app op accesses. + */ +final class DiscreteOpsTable { + private static final String TABLE_NAME = "app_op_accesses"; + private static final String INDEX_APP_OP = "app_op_access_index"; + + static final class Columns { + /** Auto increment primary key. */ + static final String ID = "id"; + /** UID of the package accessing private data. */ + static final String UID = "uid"; + /** Package accessing private data. */ + static final String PACKAGE_NAME = "package_name"; + /** The device from which the private data is accessed. */ + static final String DEVICE_ID = "device_id"; + /** Op code representing private data i.e. location, mic etc. */ + static final String OP_CODE = "op_code"; + /** Attribution tag provided when accessing the private data. */ + static final String ATTRIBUTION_TAG = "attribution_tag"; + /** Timestamp when private data is accessed, number of milliseconds that have passed + * since Unix epoch */ + static final String ACCESS_TIME = "access_time"; + /** For how long the private data is accessed. */ + static final String ACCESS_DURATION = "access_duration"; + /** App process state, whether the app is in foreground, background or cached etc. */ + static final String UID_STATE = "uid_state"; + /** App op flags */ + static final String OP_FLAGS = "op_flags"; + /** Attribution flags */ + static final String ATTRIBUTION_FLAGS = "attribution_flags"; + /** Chain id */ + static final String CHAIN_ID = "chain_id"; + } + + static final int UID_INDEX = 1; + static final int PACKAGE_NAME_INDEX = 2; + static final int DEVICE_ID_INDEX = 3; + static final int OP_CODE_INDEX = 4; + static final int ATTRIBUTION_TAG_INDEX = 5; + static final int ACCESS_TIME_INDEX = 6; + static final int ACCESS_DURATION_INDEX = 7; + static final int UID_STATE_INDEX = 8; + static final int OP_FLAGS_INDEX = 9; + static final int ATTRIBUTION_FLAGS_INDEX = 10; + static final int CHAIN_ID_INDEX = 11; + + static final String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + "(" + + Columns.ID + " INTEGER PRIMARY KEY," + + Columns.UID + " INTEGER," + + Columns.PACKAGE_NAME + " TEXT," + + Columns.DEVICE_ID + " TEXT NOT NULL," + + Columns.OP_CODE + " INTEGER," + + Columns.ATTRIBUTION_TAG + " TEXT," + + Columns.ACCESS_TIME + " INTEGER," + + Columns.ACCESS_DURATION + " INTEGER," + + Columns.UID_STATE + " INTEGER," + + Columns.OP_FLAGS + " INTEGER," + + Columns.ATTRIBUTION_FLAGS + " INTEGER," + + Columns.CHAIN_ID + " INTEGER" + + ")"; + + static final String INSERT_TABLE_SQL = "INSERT INTO " + TABLE_NAME + "(" + + Columns.UID + ", " + + Columns.PACKAGE_NAME + ", " + + Columns.DEVICE_ID + ", " + + Columns.OP_CODE + ", " + + Columns.ATTRIBUTION_TAG + ", " + + Columns.ACCESS_TIME + ", " + + Columns.ACCESS_DURATION + ", " + + Columns.UID_STATE + ", " + + Columns.OP_FLAGS + ", " + + Columns.ATTRIBUTION_FLAGS + ", " + + Columns.CHAIN_ID + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + static final String SELECT_MAX_ATTRIBUTION_CHAIN_ID = "SELECT MAX(" + Columns.CHAIN_ID + ")" + + " FROM " + TABLE_NAME; + + static final String SELECT_TABLE_DATA = "SELECT DISTINCT " + + Columns.UID + "," + + Columns.PACKAGE_NAME + "," + + Columns.DEVICE_ID + "," + + Columns.OP_CODE + "," + + Columns.ATTRIBUTION_TAG + "," + + Columns.ACCESS_TIME + "," + + Columns.ACCESS_DURATION + "," + + Columns.UID_STATE + "," + + Columns.OP_FLAGS + "," + + Columns.ATTRIBUTION_FLAGS + "," + + Columns.CHAIN_ID + + " FROM " + TABLE_NAME; + + static final String DELETE_TABLE_DATA = "DELETE FROM " + TABLE_NAME; + + static final String DELETE_TABLE_DATA_BEFORE_ACCESS_TIME = "DELETE FROM " + TABLE_NAME + + " WHERE " + Columns.ACCESS_TIME + " < ?"; + + static final String DELETE_DATA_FOR_UID_PACKAGE = "DELETE FROM " + DiscreteOpsTable.TABLE_NAME + + " WHERE " + Columns.UID + " = ? AND " + Columns.PACKAGE_NAME + " = ?"; + + static final String OFFSET_ACCESS_TIME = "UPDATE " + DiscreteOpsTable.TABLE_NAME + + " SET " + Columns.ACCESS_TIME + " = ACCESS_TIME - ?"; + + // Index on access time, uid and op code + static final String CREATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS " + + INDEX_APP_OP + " ON " + TABLE_NAME + + " (" + Columns.ACCESS_TIME + ", " + Columns.UID + ", " + Columns.OP_CODE + ")"; +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java new file mode 100644 index 000000000000..1523cca86607 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.SystemClock; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; +import java.util.Set; + +/** + * A testing class, which supports both xml and sqlite persistence for discrete ops, the class + * logs warning if there is a mismatch in the behavior. + */ +class DiscreteOpsTestingShim extends DiscreteOpsRegistry { + private static final String LOG_TAG = "DiscreteOpsTestingShim"; + private final DiscreteOpsRegistry mXmlRegistry; + private final DiscreteOpsRegistry mSqlRegistry; + + DiscreteOpsTestingShim(DiscreteOpsRegistry xmlRegistry, + DiscreteOpsRegistry sqlRegistry) { + mXmlRegistry = xmlRegistry; + mSqlRegistry = sqlRegistry; + } + + @Override + void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, + @Nullable String attributionTag, int flags, int uidState, long accessTime, + long accessDuration, int attributionFlags, int attributionChainId, int accessType) { + long start = SystemClock.uptimeMillis(); + mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, + uidState, accessTime, accessDuration, attributionFlags, attributionChainId, + accessType); + long start2 = SystemClock.uptimeMillis(); + mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, + uidState, accessTime, accessDuration, attributionFlags, attributionChainId, + accessType); + long end = SystemClock.uptimeMillis(); + long xmlTimeTaken = start2 - start; + long sqlTimeTaken = end - start2; + Log.i(LOG_TAG, + "recordDiscreteAccess: XML time taken : " + xmlTimeTaken + ", SQL time taken : " + + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken)); + } + + + @Override + void writeAndClearOldAccessHistory() { + mXmlRegistry.writeAndClearOldAccessHistory(); + mSqlRegistry.writeAndClearOldAccessHistory(); + } + + @Override + void clearHistory() { + mXmlRegistry.clearHistory(); + mSqlRegistry.clearHistory(); + } + + @Override + void clearHistory(int uid, String packageName) { + mXmlRegistry.clearHistory(uid, packageName); + mSqlRegistry.clearHistory(uid, packageName); + } + + @Override + void offsetHistory(long offset) { + mXmlRegistry.offsetHistory(offset); + mSqlRegistry.offsetHistory(offset); + } + + @Override + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, int flagsFilter, + Set<String> attributionExemptPkgs) { + AppOpsManager.HistoricalOps result2 = + new AppOpsManager.HistoricalOps(beginTimeMillis, endTimeMillis); + + long start = System.currentTimeMillis(); + mXmlRegistry.addFilteredDiscreteOpsToHistoricalOps(result2, beginTimeMillis, endTimeMillis, + filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, + flagsFilter, attributionExemptPkgs); + long start2 = System.currentTimeMillis(); + mSqlRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, endTimeMillis, + filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, + flagsFilter, attributionExemptPkgs); + long end = System.currentTimeMillis(); + long xmlTimeTaken = start2 - start; + long sqlTimeTaken = end - start2; + try { + assertHistoricalOpsAreEquals(result, result2); + } catch (Exception ex) { + Slog.e(LOG_TAG, "different output when reading discrete ops", ex); + } + Log.i(LOG_TAG, "Read: XML time taken : " + xmlTimeTaken + ", SQL time taken : " + + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken)); + } + + void assertHistoricalOpsAreEquals(AppOpsManager.HistoricalOps sqlResult, + AppOpsManager.HistoricalOps xmlResult) { + assertEquals(sqlResult.getUidCount(), xmlResult.getUidCount()); + int uidCount = sqlResult.getUidCount(); + + for (int i = 0; i < uidCount; i++) { + AppOpsManager.HistoricalUidOps sqlUidOps = sqlResult.getUidOpsAt(i); + AppOpsManager.HistoricalUidOps xmlUidOps = xmlResult.getUidOpsAt(i); + Slog.i(LOG_TAG, "sql uid: " + sqlUidOps.getUid() + ", xml uid: " + xmlUidOps.getUid()); + assertEquals(sqlUidOps.getUid(), xmlUidOps.getUid()); + assertEquals(sqlUidOps.getPackageCount(), xmlUidOps.getPackageCount()); + + int packageCount = sqlUidOps.getPackageCount(); + for (int p = 0; p < packageCount; p++) { + AppOpsManager.HistoricalPackageOps sqlPackageOps = sqlUidOps.getPackageOpsAt(p); + AppOpsManager.HistoricalPackageOps xmlPackageOps = xmlUidOps.getPackageOpsAt(p); + Slog.i(LOG_TAG, "sql package: " + sqlPackageOps.getPackageName() + ", xml package: " + + xmlPackageOps.getPackageName()); + assertEquals(sqlPackageOps.getPackageName(), xmlPackageOps.getPackageName()); + assertEquals(sqlPackageOps.getAttributedOpsCount(), + xmlPackageOps.getAttributedOpsCount()); + + int attrCount = sqlPackageOps.getAttributedOpsCount(); + for (int a = 0; a < attrCount; a++) { + AppOpsManager.AttributedHistoricalOps sqlAttrOps = + sqlPackageOps.getAttributedOpsAt(a); + AppOpsManager.AttributedHistoricalOps xmlAttrOps = + xmlPackageOps.getAttributedOpsAt(a); + Slog.i(LOG_TAG, "sql tag: " + sqlAttrOps.getTag() + ", xml tag: " + + xmlAttrOps.getTag()); + assertEquals(sqlAttrOps.getTag(), xmlAttrOps.getTag()); + assertEquals(sqlAttrOps.getOpCount(), xmlAttrOps.getOpCount()); + + int opCount = sqlAttrOps.getOpCount(); + for (int o = 0; o < opCount; o++) { + AppOpsManager.HistoricalOp sqlHistoricalOp = sqlAttrOps.getOpAt(o); + AppOpsManager.HistoricalOp xmlHistoricalOp = xmlAttrOps.getOpAt(o); + Slog.i(LOG_TAG, "sql op: " + sqlHistoricalOp.getOpName() + ", xml op: " + + xmlHistoricalOp.getOpName()); + assertEquals(sqlHistoricalOp.getOpName(), xmlHistoricalOp.getOpName()); + assertEquals(sqlHistoricalOp.getDiscreteAccessCount(), + xmlHistoricalOp.getDiscreteAccessCount()); + + int accessCount = sqlHistoricalOp.getDiscreteAccessCount(); + for (int x = 0; x < accessCount; x++) { + AppOpsManager.AttributedOpEntry sqlOpEntry = + sqlHistoricalOp.getDiscreteAccessAt(x); + AppOpsManager.AttributedOpEntry xmlOpEntry = + xmlHistoricalOp.getDiscreteAccessAt(x); + Slog.i(LOG_TAG, "sql keys: " + sqlOpEntry.collectKeys() + ", xml keys: " + + xmlOpEntry.collectKeys()); + assertEquals(sqlOpEntry.collectKeys(), xmlOpEntry.collectKeys()); + assertEquals(sqlOpEntry.isRunning(), xmlOpEntry.isRunning()); + ArraySet<Long> keys = sqlOpEntry.collectKeys(); + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + final int flags = extractFlagsFromKey(key); + assertEquals(sqlOpEntry.getLastDuration(flags), + xmlOpEntry.getLastDuration(flags)); + assertEquals(sqlOpEntry.getLastProxyInfo(flags), + xmlOpEntry.getLastProxyInfo(flags)); + assertEquals(sqlOpEntry.getLastAccessTime(flags), + xmlOpEntry.getLastAccessTime(flags)); + } + } + } + } + } + } + } + + // code duplicated for assertions + private static final int FLAGS_MASK = 0xFFFFFFFF; + + public static int extractFlagsFromKey(@AppOpsManager.DataBucketKey long key) { + return (int) (key & FLAGS_MASK); + } + + private void assertEquals(Object actual, Object expected) { + if (!Objects.equals(actual, expected)) { + throw new IllegalStateException("Actual (" + actual + ") is not equal to expected (" + + expected + ")"); + } + } + + @Override + void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps) { + mXmlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp, + sdf, date, prefix, nDiscreteOps); + pw.println("--------------------------------------------------------"); + pw.println("--------------------------------------------------------"); + mSqlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp, + sdf, date, prefix, nDiscreteOps); + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java index 7f161f618618..a6e3fc7cc66a 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java @@ -24,48 +24,20 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; -import static android.app.AppOpsManager.OP_CAMERA; -import static android.app.AppOpsManager.OP_COARSE_LOCATION; -import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; -import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; -import static android.app.AppOpsManager.OP_FLAG_SELF; -import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; -import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.app.AppOpsManager.OP_NONE; -import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; -import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; -import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; -import static android.app.AppOpsManager.OP_READ_ICC_SMS; -import static android.app.AppOpsManager.OP_READ_SMS; -import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; -import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; -import static android.app.AppOpsManager.OP_RECORD_AUDIO; -import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; -import static android.app.AppOpsManager.OP_SEND_SMS; -import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; -import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; -import static android.app.AppOpsManager.OP_WRITE_SMS; import static android.app.AppOpsManager.flagsToString; import static android.app.AppOpsManager.getUidStateName; import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; -import static java.lang.Long.min; import static java.lang.Math.max; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.os.AsyncTask; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.permission.flags.Flags; -import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; @@ -84,10 +56,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; -import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -99,100 +68,30 @@ import java.util.Objects; import java.util.Set; /** - * This class manages information about recent accesses to ops for permission usage timeline. - * - * The discrete history is kept for limited time (initial default is 24 hours, set in - * {@link DiscreteRegistry#sDiscreteHistoryCutoff) and discarded after that. - * - * Discrete history is quantized to reduce resources footprint. By default quantization is set to - * one minute in {@link DiscreteRegistry#sDiscreteHistoryQuantization}. All access times are aligned - * to the closest quantized time. All durations (except -1, meaning no duration) are rounded up to - * the closest quantized interval. - * - * When data is queried through API, events are deduplicated and for every time quant there can - * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about - * different accesses which happened in specified time quant - across dimensions of - * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension - * it is only possible to know if at least one access happened in the time quant. + * Xml persistence implementation for discrete ops. * + * <p> * Every time state is saved (default is 30 minutes), memory state is dumped to a * new file and memory state is cleared. Files older than time limit are deleted * during the process. - * + * <p> * When request comes in, files are read and requested information is collected * and delivered. Information is cached in memory until the next state save (up to 30 minutes), to * avoid reading disk if more API calls come in a quick succession. - * + * <p> * THREADING AND LOCKING: - * For in-memory transactions this class relies on {@link DiscreteRegistry#mInMemoryLock}. It is - * assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, - * {@link HistoricalRegistry}, and {@link DiscreteRegistry}. - * {@link DiscreteRegistry#recordDiscreteAccess(int, String, int, String, int, int, long, long)} - * must only be called while holding this lock. - * {@link DiscreteRegistry#mOnDiskLock} is used when disk transactions are performed. - * It is very important to release {@link DiscreteRegistry#mInMemoryLock} as soon as possible, as - * no AppOps related transactions across the system can be performed while it is held. + * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}. + * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, + * {@link HistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }. + * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock. + * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed. + * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as + * possible, as no AppOps related transactions across the system can be performed while it is held. * - * INITIALIZATION: We can initialize persistence only after the system is ready - * as we need to check the optional configuration override from the settings - * database which is not initialized at the time the app ops service is created. This class - * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All - * outside calls are going through {@link HistoricalRegistry}, where - * {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done. */ - -final class DiscreteRegistry { +class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry { static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl"; - private static final String TAG = DiscreteRegistry.class.getSimpleName(); - - private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; - private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = - "discrete_history_quantization_millis"; - private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; - private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; - private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION - + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," - + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," - + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO - + "," + OP_RESERVED_FOR_TESTING; - private static final int[] sDiscreteOpsToLog = - new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, - OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, - OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, - OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, - OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, - OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, - }; - private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); - private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); - private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = - Duration.ofMinutes(1).toMillis(); - - static final int ACCESS_TYPE_NOTE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; - static final int ACCESS_TYPE_START_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; - static final int ACCESS_TYPE_FINISH_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; - static final int ACCESS_TYPE_PAUSE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; - static final int ACCESS_TYPE_RESUME_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"ACCESS_TYPE_"}, value = { - ACCESS_TYPE_NOTE_OP, - ACCESS_TYPE_START_OP, - ACCESS_TYPE_FINISH_OP, - ACCESS_TYPE_PAUSE_OP, - ACCESS_TYPE_RESUME_OP - }) - public @interface AccessType {} - - private static long sDiscreteHistoryCutoff; - private static long sDiscreteHistoryQuantization; - private static int[] sDiscreteOps; - private static int sDiscreteFlags; + private static final String TAG = DiscreteOpsXmlRegistry.class.getSimpleName(); private static final String TAG_HISTORY = "h"; private static final String ATTR_VERSION = "v"; @@ -221,9 +120,6 @@ final class DiscreteRegistry { private static final String ATTR_ATTRIBUTION_FLAGS = "af"; private static final String ATTR_CHAIN_ID = "ci"; - private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED - | OP_FLAG_TRUSTED_PROXY; - // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -239,14 +135,12 @@ final class DiscreteRegistry { @GuardedBy("mOnDiskLock") private DiscreteOps mCachedOps = null; - private boolean mDebugMode = false; - - DiscreteRegistry(Object inMemoryLock) { - this(inMemoryLock, new File(new File(Environment.getDataSystemDirectory(), "appops"), - "discrete")); + DiscreteOpsXmlRegistry(Object inMemoryLock) { + this(inMemoryLock, getDiscreteOpsDir()); } - DiscreteRegistry(Object inMemoryLock, File discreteAccessDir) { + // constructor for tests. + DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir) { mInMemoryLock = inMemoryLock; synchronized (mOnDiskLock) { mDiscreteAccessDir = discreteAccessDir; @@ -258,40 +152,8 @@ final class DiscreteRegistry { } } - void systemReady() { - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, - AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { - setDiscreteHistoryParameters(p); - }); - setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY)); - } - - private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { - if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { - sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, - DEFAULT_DISCRETE_HISTORY_CUTOFF); - if (!Build.IS_DEBUGGABLE && !mDebugMode) { - sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF, - sDiscreteHistoryCutoff); - } - } else { - sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF; - } - if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) { - sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION, - DEFAULT_DISCRETE_HISTORY_QUANTIZATION); - if (!Build.IS_DEBUGGABLE && !mDebugMode) { - sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION, - sDiscreteHistoryQuantization); - } - } else { - sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; - } - sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags = - p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; - sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList( - p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList( - DEFAULT_DISCRETE_OPS); + static File getDiscreteOpsDir() { + return new File(new File(Environment.getDataSystemDirectory(), "appops"), "discrete"); } void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @@ -300,17 +162,9 @@ final class DiscreteRegistry { @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, @AccessType int accessType) { if (shouldLogAccess(op)) { - int firstChar = 0; - if (attributionTag != null && attributionTag.startsWith(packageName)) { - firstChar = packageName.length(); - if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) - == '.') { - firstChar++; - } - } FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, uidState, flags, attributionFlags, - attributionTag == null ? null : attributionTag.substring(firstChar), + getAttributionTag(attributionTag, packageName), attributionChainId); } @@ -331,7 +185,7 @@ final class DiscreteRegistry { } } - void writeAndClearAccessHistory() { + void writeAndClearOldAccessHistory() { synchronized (mOnDiskLock) { if (mDiscreteAccessDir == null) { Slog.d(TAG, "State not saved - persistence not initialized."); @@ -350,6 +204,22 @@ final class DiscreteRegistry { } } + void migrateSqliteData(DiscreteOps sqliteOps) { + synchronized (mOnDiskLock) { + if (mDiscreteAccessDir == null) { + Slog.d(TAG, "State not saved - persistence not initialized."); + return; + } + synchronized (mInMemoryLock) { + mDiscreteOps.mLargestChainId = sqliteOps.mLargestChainId; + mDiscreteOps.mChainIdOffset = sqliteOps.mChainIdOffset; + } + if (!sqliteOps.isEmpty()) { + persistDiscreteOpsLocked(sqliteOps); + } + } + } + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @@ -369,7 +239,7 @@ final class DiscreteRegistry { discreteOps.applyToHistoricalOps(result, attributionChains); } - private int readLargestChainIdFromDiskLocked() { + int readLargestChainIdFromDiskLocked() { final File[] files = mDiscreteAccessDir.listFiles(); if (files != null && files.length > 0) { File latestFile = null; @@ -497,6 +367,13 @@ final class DiscreteRegistry { } } + void deleteDiscreteOpsDir() { + synchronized (mOnDiskLock) { + mCachedOps = null; + FileUtils.deleteContentsAndDir(mDiscreteAccessDir); + } + } + void clearHistory(int uid, String packageName) { synchronized (mOnDiskLock) { DiscreteOps discreteOps; @@ -1506,26 +1383,6 @@ final class DiscreteRegistry { } } - private static int[] parseOpsList(String opsList) { - String[] strArr; - if (opsList.isEmpty()) { - strArr = new String[0]; - } else { - strArr = opsList.split(","); - } - int nOps = strArr.length; - int[] result = new int[nOps]; - try { - for (int i = 0; i < nOps; i++) { - result[i] = Integer.parseInt(strArr[i]); - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); - return parseOpsList(DEFAULT_DISCRETE_OPS); - } - return result; - } - private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a, List<DiscreteOpEvent> b) { int nA = a.size(); @@ -1570,34 +1427,4 @@ final class DiscreteRegistry { } return result; } - - private static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { - if (!ArrayUtils.contains(sDiscreteOps, op)) { - return false; - } - if ((flags & (sDiscreteFlags)) == 0) { - return false; - } - return true; - } - - private static boolean shouldLogAccess(int op) { - return Flags.appopAccessTrackingLoggingEnabled() - && ArrayUtils.contains(sDiscreteOpsToLog, op); - } - - private static long discretizeTimeStamp(long timeStamp) { - return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - - } - - private static long discretizeDuration(long duration) { - return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) - / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - } - - void setDebugMode(boolean debugMode) { - this.mDebugMode = debugMode; - } } - diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 5e67f26ba1f6..ba391d0a9995 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -35,6 +35,7 @@ import android.app.AppOpsManager.OpFlags; import android.app.AppOpsManager.OpHistoryFlags; import android.app.AppOpsManager.UidState; import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; @@ -45,6 +46,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteCallback; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArraySet; import android.util.LongSparseArray; @@ -135,7 +137,7 @@ final class HistoricalRegistry { private static final String PARAMETER_DELIMITER = ","; private static final String PARAMETER_ASSIGNMENT = "="; - private volatile @NonNull DiscreteRegistry mDiscreteRegistry; + private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry; @GuardedBy("mLock") private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); @@ -196,13 +198,30 @@ final class HistoricalRegistry { @GuardedBy("mOnDiskLock") private Persistence mPersistence; - HistoricalRegistry(@NonNull Object lock) { + private final Context mContext; + + HistoricalRegistry(@NonNull Object lock, Context context) { mInMemoryLock = lock; - mDiscreteRegistry = new DiscreteRegistry(lock); + mContext = context; + if (Flags.enableSqliteAppopsAccesses()) { + mDiscreteRegistry = new DiscreteOpsSqlRegistry(context); + if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) { + DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry; + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); + } + } else { + mDiscreteRegistry = new DiscreteOpsXmlRegistry(context); + if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context); + DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry; + DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); + } + } } HistoricalRegistry(@NonNull HistoricalRegistry other) { - this(other.mInMemoryLock); + this(other.mInMemoryLock, other.mContext); mMode = other.mMode; mBaseSnapshotInterval = other.mBaseSnapshotInterval; mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier; @@ -475,7 +494,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteRegistry.AccessType int accessType, int accessCount) { + @DiscreteOpsRegistry.AccessType int accessType, int accessCount) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -512,7 +531,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long eventStartTime, long increment, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteRegistry.AccessType int accessType) { + @DiscreteOpsRegistry.AccessType int accessType) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -648,7 +667,7 @@ final class HistoricalRegistry { } void writeAndClearDiscreteHistory() { - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); } void clearAllHistory() { @@ -743,7 +762,7 @@ final class HistoricalRegistry { } persistPendingHistory(pendingWrites); } - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); } private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index e89f43bd7196..20c33275b8f1 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -876,7 +876,28 @@ final class CompatConfig { } @Nullable + @android.ravenwood.annotation.RavenwoodReplace( + blockedBy = PackageManager.class, + reason = "PackageManager.getApplicationInfo() isn't supported yet") private Long getVersionCodeOrNull(String packageName) { + return getVersionCodeOrNullImpl(packageName); + } + + @SuppressWarnings("unused") + @Nullable + private Long getVersionCodeOrNull$ravenwood(String packageName) { + try { + // It's possible that the context is mocked, try the real method first + return getVersionCodeOrNullImpl(packageName); + } catch (Throwable e) { + // For now, Ravenwood doesn't support the concept of "app updates", so let's + // just use a fixed version code for all packages. + return 1L; + } + } + + @Nullable + private Long getVersionCodeOrNullImpl(String packageName) { try { ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo( packageName, MATCH_ANY_USER); diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 5d9db65fe2b2..d89db8d5581b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -312,6 +312,13 @@ public final class DeviceStateManagerService extends SystemService { mProcessObserver); } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + mDeviceStatePolicy.getDeviceStateProvider().onSystemReady(); + } + } + @VisibleForTesting Handler getHandler() { return mHandler; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index 8d07609cef30..8a8ebc2ffc21 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -91,6 +91,11 @@ public interface DeviceStateProvider extends Dumpable { @interface SupportedStatesUpdatedReason {} /** + * Called when the system boot phase advances to PHASE_SYSTEM_SERVICES_READY. + */ + default void onSystemReady() {}; + + /** * Registers a listener for changes in provider state. * <p> * It is <b>required</b> that diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index c384b5434bce..9349ea54e4ee 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -1030,10 +1030,20 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { } @ServiceThreadOnly + void addAndStartAction(final HdmiCecFeatureAction action, final boolean remove) { + assertRunOnServiceThread(); + if (hasAction(action.getClass()) && remove) { + // If the action is currently running, remove it and restart it. + Slog.i(TAG, action.getClass().getName() + " is in progress. Restarting."); + removeAction(action.getClass()); + } + addAndStartAction(action); + } + + @ServiceThreadOnly void startNewAvbAudioStatusAction(int targetAddress) { assertRunOnServiceThread(); - removeAction(AbsoluteVolumeAudioStatusAction.class); - addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress)); + addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress), true); } @ServiceThreadOnly diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 1e90ab279d32..510e4f5e1868 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -317,11 +317,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) { - if (hasAction(SystemAudioInitiationActionFromAvr.class)) { - Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting."); - removeAction(SystemAudioInitiationActionFromAvr.class); - } - addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); + addAndStartAction(new SystemAudioInitiationActionFromAvr(this), true); } } @@ -457,6 +453,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { HdmiLogger.debug("AVR device is not directly connected with TV"); return Constants.ABORT_NOT_IN_CORRECT_MODE; } else { + // Action has been removed if it existed, do not attempt to remove again before start. addAndStartAction(new ArcInitiationActionFromAvr(this)); return Constants.HANDLED; } @@ -477,11 +474,9 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) { IHdmiControlCallback callback = getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0); - removeAction(ArcTerminationActionFromAvr.class); - addAndStartAction(new ArcTerminationActionFromAvr(this, callback)); + addAndStartAction(new ArcTerminationActionFromAvr(this, callback), true); } else { - removeAction(ArcTerminationActionFromAvr.class); - addAndStartAction(new ArcTerminationActionFromAvr(this)); + addAndStartAction(new ArcTerminationActionFromAvr(this), true); } return Constants.HANDLED; } @@ -1036,11 +1031,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { void onSystemAudioControlFeatureSupportChanged(boolean enabled) { setSystemAudioControlFeatureEnabled(enabled); if (enabled) { - if (hasAction(SystemAudioInitiationActionFromAvr.class)) { - Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting."); - removeAction(SystemAudioInitiationActionFromAvr.class); - } - addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); + addAndStartAction(new SystemAudioInitiationActionFromAvr(this), true); } } @@ -1221,8 +1212,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { removeAction(ArcTerminationActionFromAvr.class); if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true) && isDirectConnectToTv() && !isArcEnabled()) { - removeAction(ArcInitiationActionFromAvr.class); - addAndStartAction(new ArcInitiationActionFromAvr(this)); + addAndStartAction(new ArcInitiationActionFromAvr(this), true); } } @@ -1367,10 +1357,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { if (mService.isDeviceDiscoveryHandledByPlayback()) { return; } - if (hasAction(DeviceDiscoveryAction.class)) { - Slog.i(TAG, "Device Discovery Action is in progress. Restarting."); - removeAction(DeviceDiscoveryAction.class); - } DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, new DeviceDiscoveryCallback() { @Override @@ -1380,7 +1366,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } } }); - addAndStartAction(action); + addAndStartAction(action, true); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 0b667fc10880..86abbc403e50 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -21,7 +21,6 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.hardware.display.DeviceProductInfo; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -32,7 +31,6 @@ import android.os.PowerManager; import android.os.SystemProperties; import android.sysprop.HdmiProperties; import android.util.Slog; -import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.LocalePicker; @@ -151,10 +149,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private void launchDeviceDiscovery() { assertRunOnServiceThread(); clearDeviceInfoList(); - if (hasAction(DeviceDiscoveryAction.class)) { - Slog.i(TAG, "Device Discovery Action is in progress. Restarting."); - removeAction(DeviceDiscoveryAction.class); - } DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, new DeviceDiscoveryAction.DeviceDiscoveryCallback() { @Override @@ -163,25 +157,21 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { mService.getHdmiCecNetwork().addCecDevice(info); } - // Since we removed all devices when it starts and device discovery action - // does not poll local devices, we should put device info of local device - // manually here. + // Since we removed all devices when it starts and device discovery + // action does not poll local devices, we should put device info of + // local device manually here. for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) { mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); } - List<HotplugDetectionAction> hotplugActions = - getActions(HotplugDetectionAction.class); - if (hotplugActions.isEmpty()) { + if (!hasAction(HotplugDetectionAction.class)) { addAndStartAction( - new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this)); + new HotplugDetectionAction( + HdmiCecLocalDevicePlayback.this)); } if (mService.isHdmiControlEnhancedBehaviorFlagEnabled()) { - List<PowerStatusMonitorActionFromPlayback> - powerStatusMonitorActionsFromPlayback = - getActions(PowerStatusMonitorActionFromPlayback.class); - if (powerStatusMonitorActionsFromPlayback.isEmpty()) { + if (!hasAction(PowerStatusMonitorActionFromPlayback.class)) { addAndStartAction( new PowerStatusMonitorActionFromPlayback( HdmiCecLocalDevicePlayback.this)); @@ -189,7 +179,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } } }); - addAndStartAction(action); + addAndStartAction(action, true); } @Override @@ -235,8 +225,16 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - removeAction(DeviceSelectActionFromPlayback.class); - addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback)); + List<DeviceSelectActionFromPlayback> actions = getActions( + DeviceSelectActionFromPlayback.class); + if (!actions.isEmpty()) { + DeviceSelectActionFromPlayback action = actions.get(0); + if (action.getTargetAddress() == targetDevice.getLogicalAddress()) { + return; + } + } + addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback), + true); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 424102cbdd89..3d6d34bf9911 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -220,10 +220,6 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { List<HdmiCecMessage> bufferedActiveSource = mDelayedMessageBuffer .getBufferedMessagesWithOpcode(Constants.MESSAGE_ACTIVE_SOURCE); if (bufferedActiveSource.isEmpty()) { - if (hasAction(RequestActiveSourceAction.class)) { - Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting."); - removeAction(RequestActiveSourceAction.class); - } addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { @@ -231,7 +227,7 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchRoutingControl(routingForBootup); } } - })); + }), true); } else { addCecDeviceForBufferedActiveSource(bufferedActiveSource.get(0)); } @@ -328,8 +324,15 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } - removeAction(DeviceSelectActionFromTv.class); - addAndStartAction(new DeviceSelectActionFromTv(this, targetDevice, callback)); + List<DeviceSelectActionFromTv> actions = getActions(DeviceSelectActionFromTv.class); + if (!actions.isEmpty()) { + DeviceSelectActionFromTv action = actions.get(0); + if (action.getTargetAddress() == targetDevice.getLogicalAddress()) { + return; + } + } + addAndStartAction(new DeviceSelectActionFromTv(this, targetDevice, callback), + true); } @ServiceThreadOnly @@ -475,9 +478,8 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiCecMessageBuilder.buildRoutingChange( getDeviceInfo().getLogicalAddress(), oldPath, newPath); mService.sendCecCommand(routingChange); - removeAction(RoutingControlAction.class); addAndStartAction( - new RoutingControlAction(this, newPath, callback)); + new RoutingControlAction(this, newPath, callback), true); } @ServiceThreadOnly @@ -801,16 +803,12 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mSelectRequestBuffer.process(); resetSelectRequestBuffer(); - List<HotplugDetectionAction> hotplugActions - = getActions(HotplugDetectionAction.class); - if (hotplugActions.isEmpty()) { + if (!hasAction(HotplugDetectionAction.class)) { addAndStartAction( new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); } - List<PowerStatusMonitorAction> powerStatusActions - = getActions(PowerStatusMonitorAction.class); - if (powerStatusActions.isEmpty()) { + if (!hasAction(PowerStatusMonitorAction.class)) { addAndStartAction( new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 35ef18b144e7..bd8b67b9d626 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -17,7 +17,6 @@ package com.android.server.hdmi; import static android.media.tv.flags.Flags.hdmiControlEnhancedBehavior; - import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED; @@ -107,7 +106,6 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.KeyEvent; -import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1218,9 +1216,6 @@ public class HdmiControlService extends SystemService { audioSystem.terminateSystemAudioMode(); } if (isArcEnabled) { - if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) { - audioSystem.removeAction(ArcTerminationActionFromAvr.class); - } audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem, new IHdmiControlCallback.Stub() { @Override @@ -1228,7 +1223,7 @@ public class HdmiControlService extends SystemService { mAddressAllocated = false; initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); } - })); + }), true); } } if (!isArcEnabled) { diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java index 9a3cde156300..d05ded5367d0 100644 --- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java +++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java @@ -68,6 +68,8 @@ public class PowerStatusMonitorActionFromPlayback extends HdmiCecFeatureAction { private boolean handleReportPowerStatusFromTv(HdmiCecMessage cmd) { int powerStatus = cmd.getParams()[0] & 0xFF; + mState = STATE_WAIT_FOR_NEXT_MONITORING; + addTimer(mState, MONITORING_INTERVAL_MS); if (powerStatus == POWER_STATUS_STANDBY) { Slog.d(TAG, "TV reported it turned off, going to sleep."); source().getService().standby(); diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index febf24edc294..e25ea4b43827 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -74,6 +74,8 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.System.getUriFor( Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED), (reason) -> updateMouseAccelerationEnabled()), + Map.entry(Settings.System.getUriFor(Settings.System.MOUSE_SCROLLING_SPEED), + (reason) -> updateMouseScrollingSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), (reason) -> updateTouchpadPointerSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), @@ -199,6 +201,11 @@ class InputSettingsObserver extends ContentObserver { InputSettings.isMousePointerAccelerationEnabled(mContext)); } + private void updateMouseScrollingSpeed() { + mNative.setMouseScrollingSpeed( + constrainPointerSpeedValue(InputSettings.getMouseScrollingSpeed(mContext))); + } + private void updateTouchpadPointerSpeed() { mNative.setTouchpadPointerSpeed( constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext))); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 7dbde64a6412..d426e8292f14 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -136,6 +136,8 @@ interface NativeInputManagerService { void setMouseScrollingAccelerationEnabled(boolean enabled); + void setMouseScrollingSpeed(int speed); + void setMouseSwapPrimaryButtonEnabled(boolean enabled); void setMouseAccelerationEnabled(boolean enabled); @@ -428,6 +430,9 @@ interface NativeInputManagerService { public native void setMouseScrollingAccelerationEnabled(boolean enabled); @Override + public native void setMouseScrollingSpeed(int speed); + + @Override public native void setMouseSwapPrimaryButtonEnabled(boolean enabled); @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index 7698a87f80c9..740c4f195852 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; /** * A class that manages registration/unregistration of clients and manages messages to/from clients. @@ -312,15 +313,9 @@ import java.util.concurrent.ConcurrentHashMap; @Override public void onCloseEndpointSession(int sessionId, byte reason) { - boolean callbackInvoked = false; - for (ContextHubEndpointBroker broker : mEndpointMap.values()) { - if (broker.hasSessionId(sessionId)) { - broker.onCloseEndpointSession(sessionId, reason); - callbackInvoked = true; - break; - } - } - + boolean callbackInvoked = + invokeCallbackForMatchingSession( + sessionId, (broker) -> broker.onCloseEndpointSession(sessionId, reason)); if (!callbackInvoked) { Log.w(TAG, "onCloseEndpointSession: unknown session ID " + sessionId); } @@ -328,15 +323,9 @@ import java.util.concurrent.ConcurrentHashMap; @Override public void onEndpointSessionOpenComplete(int sessionId) { - boolean callbackInvoked = false; - for (ContextHubEndpointBroker broker : mEndpointMap.values()) { - if (broker.hasSessionId(sessionId)) { - broker.onEndpointSessionOpenComplete(sessionId); - callbackInvoked = true; - break; - } - } - + boolean callbackInvoked = + invokeCallbackForMatchingSession( + sessionId, (broker) -> broker.onEndpointSessionOpenComplete(sessionId)); if (!callbackInvoked) { Log.w(TAG, "onEndpointSessionOpenComplete: unknown session ID " + sessionId); } @@ -344,15 +333,9 @@ import java.util.concurrent.ConcurrentHashMap; @Override public void onMessageReceived(int sessionId, HubMessage message) { - boolean callbackInvoked = false; - for (ContextHubEndpointBroker broker : mEndpointMap.values()) { - if (broker.hasSessionId(sessionId)) { - broker.onMessageReceived(sessionId, message); - callbackInvoked = true; - break; - } - } - + boolean callbackInvoked = + invokeCallbackForMatchingSession( + sessionId, (broker) -> broker.onMessageReceived(sessionId, message)); if (!callbackInvoked) { Log.w(TAG, "onMessageReceived: unknown session ID " + sessionId); } @@ -360,18 +343,36 @@ import java.util.concurrent.ConcurrentHashMap; @Override public void onMessageDeliveryStatusReceived(int sessionId, int sequenceNumber, byte errorCode) { - boolean callbackInvoked = false; + boolean callbackInvoked = + invokeCallbackForMatchingSession( + sessionId, + (broker) -> + broker.onMessageDeliveryStatusReceived( + sessionId, sequenceNumber, errorCode)); + if (!callbackInvoked) { + Log.w(TAG, "onMessageDeliveryStatusReceived: unknown session ID " + sessionId); + } + } + + /** + * Invokes a callback for a session with matching ID. + * + * @param callback The callback to execute + * @return true if a callback was executed + */ + private boolean invokeCallbackForMatchingSession( + int sessionId, Consumer<ContextHubEndpointBroker> callback) { for (ContextHubEndpointBroker broker : mEndpointMap.values()) { if (broker.hasSessionId(sessionId)) { - broker.onMessageDeliveryStatusReceived(sessionId, sequenceNumber, errorCode); - callbackInvoked = true; - break; + try { + callback.accept(broker); + } catch (RuntimeException e) { + Log.e(TAG, "Exception while invoking callback", e); + } + return true; } } - - if (!callbackInvoked) { - Log.w(TAG, "onMessageDeliveryStatusReceived: unknown session ID " + sessionId); - } + return false; } /** Unregister the hub (called during init() failure). Silence errors. */ diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 54ed5a9711b3..2615a76ac279 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -756,9 +756,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public List<HubInfo> getHubs() throws RemoteException { super.getHubs_enforcePermission(); - if (mHubInfoRegistry == null) { - return Collections.emptyList(); - } + checkHubDiscoveryPreconditions(); return mHubInfoRegistry.getHubs(); } @@ -766,9 +764,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public List<HubEndpointInfo> findEndpoints(long endpointId) { super.findEndpoints_enforcePermission(); - if (mHubInfoRegistry == null) { - return Collections.emptyList(); - } + checkEndpointDiscoveryPreconditions(); return mHubInfoRegistry.findEndpoints(endpointId); } @@ -776,9 +772,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public List<HubEndpointInfo> findEndpointsWithService(String serviceDescriptor) { super.findEndpointsWithService_enforcePermission(); - if (mHubInfoRegistry == null) { - return Collections.emptyList(); - } + checkEndpointDiscoveryPreconditions(); return mHubInfoRegistry.findEndpointsWithService(serviceDescriptor); } @@ -834,6 +828,13 @@ public class ContextHubService extends IContextHubService.Stub { } } + private void checkHubDiscoveryPreconditions() { + if (mHubInfoRegistry == null) { + Log.e(TAG, "Hub registry failed to initialize"); + throw new UnsupportedOperationException("Hub discovery is not supported"); + } + } + /** * Creates an internal load transaction callback to be used for old API clients * diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 3f915757f137..286238e7888c 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -451,6 +451,7 @@ public class LockSettingsService extends ILockSettings.Stub { * @param profileUserId profile user Id * @param profileUserPassword profile original password (when it has separated lock). */ + @GuardedBy("mSpManager") private void tieProfileLockIfNecessary(int profileUserId, LockscreenCredential profileUserPassword) { // Only for profiles that shares credential with parent @@ -909,14 +910,8 @@ public class LockSettingsService extends ILockSettings.Stub { // Hide notification first, as tie profile lock takes time hideEncryptionNotification(new UserHandle(userId)); - if (android.app.admin.flags.Flags.fixRaceConditionInTieProfileLock()) { - synchronized (mSpManager) { - tieProfileLockIfNecessary(userId, LockscreenCredential.createNone()); - } - } else { - if (isCredentialSharableWithParent(userId)) { - tieProfileLockIfNecessary(userId, LockscreenCredential.createNone()); - } + synchronized (mSpManager) { + tieProfileLockIfNecessary(userId, LockscreenCredential.createNone()); } } }); @@ -1380,11 +1375,7 @@ public class LockSettingsService extends ILockSettings.Stub { mStorage.removeChildProfileLock(userId); removeKeystoreProfileKey(userId); } else { - if (android.app.admin.flags.Flags.fixRaceConditionInTieProfileLock()) { - synchronized (mSpManager) { - tieProfileLockIfNecessary(userId, profileUserPassword); - } - } else { + synchronized (mSpManager) { tieProfileLockIfNecessary(userId, profileUserPassword); } } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 34bb4155c943..3babc0ae065c 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -18,6 +18,7 @@ package com.android.server.media.quality; import android.content.ContentValues; import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.media.quality.AmbientBacklightSettings; @@ -35,8 +36,13 @@ import android.media.quality.SoundProfileHandle; import android.os.Binder; import android.os.Bundle; import android.os.PersistableBundle; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; import com.android.server.SystemService; @@ -45,9 +51,11 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -64,10 +72,13 @@ public class MediaQualityService extends SystemService { private final MediaQualityDbHelper mMediaQualityDbHelper; private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; + private final PackageManager mPackageManager; + private final SparseArray<UserState> mUserStates = new SparseArray<>(); public MediaQualityService(Context context) { super(context); mContext = context; + mPackageManager = mContext.getPackageManager(); mPictureProfileTempIdMap = new BiMap<>(); mSoundProfileTempIdMap = new BiMap<>(); mMediaQualityDbHelper = new MediaQualityDbHelper(mContext); @@ -85,12 +96,20 @@ public class MediaQualityService extends SystemService { @Override public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) { + if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty() + && !incomingPackageEqualsCallingUidPackage(pp.getPackageName())) + && !hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = getContentValues(null, pp.getProfileType(), pp.getName(), - pp.getPackageName(), + pp.getPackageName() == null || pp.getPackageName().isEmpty() + ? getPackageOfCallingUid() : pp.getPackageName(), pp.getInputId(), pp.getParameters()); @@ -104,9 +123,13 @@ public class MediaQualityService extends SystemService { @Override public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { - Long intId = mPictureProfileTempIdMap.getKey(id); + Long dbId = mPictureProfileTempIdMap.getKey(id); + if (!hasPermissionToUpdatePictureProfile(dbId, pp)) { + notifyError(id, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } - ContentValues values = getContentValues(intId, + ContentValues values = getContentValues(dbId, pp.getProfileType(), pp.getName(), pp.getPackageName(), @@ -118,27 +141,51 @@ public class MediaQualityService extends SystemService { null, values); } + private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) { + PictureProfile fromDb = getPictureProfile(dbId); + return fromDb.getProfileType() == toUpdate.getProfileType() + && fromDb.getPackageName().equals(toUpdate.getPackageName()) + && fromDb.getName().equals(toUpdate.getName()) + && fromDb.getName().equals(getPackageOfCallingUid()); + } + @Override public void removePictureProfile(String id, UserHandle user) { - Long intId = mPictureProfileTempIdMap.getKey(id); - if (intId != null) { + Long dbId = mPictureProfileTempIdMap.getKey(id); + + if (!hasPermissionToRemovePictureProfile(dbId)) { + notifyError(id, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + + if (dbId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArgs = {Long.toString(intId)}; - db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection, + String[] selectionArgs = {Long.toString(dbId)}; + int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection, selectionArgs); - mPictureProfileTempIdMap.remove(intId); + if (result == 0) { + notifyError(id, PictureProfile.ERROR_INVALID_ARGUMENT, + Binder.getCallingUid(), Binder.getCallingPid()); + } + mPictureProfileTempIdMap.remove(dbId); } } + private boolean hasPermissionToRemovePictureProfile(Long dbId) { + PictureProfile fromDb = getPictureProfile(dbId); + return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid()); + } + @Override public PictureProfile getPictureProfile(int type, String name, Bundle options, UserHandle user) { boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " - + BaseParameters.PARAMETER_NAME + " = ?"; - String[] selectionArguments = {Integer.toString(type), name}; + + BaseParameters.PARAMETER_NAME + " = ? AND " + + BaseParameters.PARAMETER_PACKAGE + " = ?"; + String[] selectionArguments = {Integer.toString(type), name, getPackageOfCallingUid()}; try ( Cursor cursor = getCursorAfterQuerying( @@ -156,13 +203,42 @@ public class MediaQualityService extends SystemService { return null; } cursor.moveToFirst(); - return getPictureProfileWithTempIdFromCursor(cursor); + return convertCursorToPictureProfileWithTempId(cursor); + } + } + + private PictureProfile getPictureProfile(Long dbId) { + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArguments = {Long.toString(dbId)}; + + try ( + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, + getMediaProfileColumns(false), selection, selectionArguments) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%d" + + " in %s. Should only ever be 0 or 1.", count, dbId, + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME)); + return null; + } + cursor.moveToFirst(); + return convertCursorToPictureProfileWithTempId(cursor); } } @Override public List<PictureProfile> getPictureProfilesByPackage( String packageName, Bundle options, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -172,23 +248,31 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) { - String[] packageNames = mContext.getPackageManager().getPackagesForUid( - Binder.getCallingUid()); - if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { - return getPictureProfilesByPackage(packageNames[0], options, user); + public List<PictureProfile> getAvailablePictureProfiles( + Bundle options, UserHandle user) { + String packageName = getPackageOfCallingUid(); + if (packageName != null) { + return getPictureProfilesByPackage(packageName, options, user); } return new ArrayList<>(); } @Override public boolean setDefaultPictureProfile(String profileId, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(profileId, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override public List<String> getPictureProfilePackageNames(UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } String [] column = {BaseParameters.PARAMETER_PACKAGE}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); @@ -210,12 +294,19 @@ public class MediaQualityService extends SystemService { @Override public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) { + if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty() + && !incomingPackageEqualsCallingUidPackage(sp.getPackageName())) + && !hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + return null; + } SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = getContentValues(null, sp.getProfileType(), sp.getName(), - sp.getPackageName(), + sp.getPackageName() == null || sp.getPackageName().isEmpty() + ? getPackageOfCallingUid() : sp.getPackageName(), sp.getInputId(), sp.getParameters()); @@ -229,9 +320,14 @@ public class MediaQualityService extends SystemService { @Override public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) { - Long intId = mSoundProfileTempIdMap.getKey(id); + Long dbId = mSoundProfileTempIdMap.getKey(id); + + if (!hasPermissionToUpdateSoundProfile(dbId, sp)) { + //TODO: error handling + return; + } - ContentValues values = getContentValues(intId, + ContentValues values = getContentValues(dbId, sp.getProfileType(), sp.getName(), sp.getPackageName(), @@ -242,27 +338,49 @@ public class MediaQualityService extends SystemService { db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values); } + private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) { + SoundProfile fromDb = getSoundProfile(dbId); + return fromDb.getProfileType() == sp.getProfileType() + && fromDb.getPackageName().equals(sp.getPackageName()) + && fromDb.getName().equals(sp.getName()) + && fromDb.getName().equals(getPackageOfCallingUid()); + } + @Override public void removeSoundProfile(String id, UserHandle user) { Long intId = mSoundProfileTempIdMap.getKey(id); + if (!hasPermissionToRemoveSoundProfile(intId)) { + //TODO: error handling + return; + } + if (intId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; String[] selectionArgs = {Long.toString(intId)}; - db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, + int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, selectionArgs); + if (result == 0) { + //TODO: error handling + } mSoundProfileTempIdMap.remove(intId); } } + private boolean hasPermissionToRemoveSoundProfile(Long dbId) { + SoundProfile fromDb = getSoundProfile(dbId); + return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid()); + } + @Override public SoundProfile getSoundProfile(int type, String id, Bundle options, UserHandle user) { boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " - + BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArguments = {String.valueOf(type), id}; + + BaseParameters.PARAMETER_ID + " = ? AND " + + BaseParameters.PARAMETER_PACKAGE + " = ?"; + String[] selectionArguments = {String.valueOf(type), id, getPackageOfCallingUid()}; try ( Cursor cursor = getCursorAfterQuerying( @@ -280,13 +398,42 @@ public class MediaQualityService extends SystemService { return null; } cursor.moveToFirst(); - return getSoundProfileWithTempIdFromCursor(cursor); + return convertCursorToSoundProfileWithTempId(cursor); + } + } + + private SoundProfile getSoundProfile(Long dbId) { + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArguments = {Long.toString(dbId)}; + + try ( + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, + getMediaProfileColumns(false), selection, selectionArguments) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s " + + "in %s. Should only ever be 0 or 1.", count, dbId, + mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME)); + return null; + } + cursor.moveToFirst(); + return convertCursorToSoundProfileWithTempId(cursor); } } @Override public List<SoundProfile> getSoundProfilesByPackage( String packageName, Bundle options, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } + boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -296,24 +443,30 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getAvailableSoundProfiles( - Bundle options, UserHandle user) { - String[] packageNames = mContext.getPackageManager().getPackagesForUid( - Binder.getCallingUid()); - if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { - return getSoundProfilesByPackage(packageNames[0], options, user); + public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) { + String packageName = getPackageOfCallingUid(); + if (packageName != null) { + return getSoundProfilesByPackage(packageName, options, user); } return new ArrayList<>(); } @Override public boolean setDefaultSoundProfile(String profileId, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return false; + } // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override public List<String> getSoundProfilePackageNames(UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } String [] column = {BaseParameters.PARAMETER_NAME}; List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column, null, null); @@ -323,6 +476,37 @@ public class MediaQualityService extends SystemService { .collect(Collectors.toList()); } + private String getPackageOfCallingUid() { + String[] packageNames = mPackageManager.getPackagesForUid( + Binder.getCallingUid()); + if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { + return packageNames[0]; + } + return null; + } + + private boolean incomingPackageEqualsCallingUidPackage(String incomingPackage) { + return incomingPackage.equalsIgnoreCase(getPackageOfCallingUid()); + } + + private boolean hasGlobalPictureQualityServicePermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + + private boolean hasGlobalSoundQualityServicePermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .MANAGE_GLOBAL_SOUND_QUALITY_SERVICE, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + + private boolean hasReadColorZonesPermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .READ_COLOR_ZONES, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + private void populateTempIdMap(BiMap<Long, String> map, Long id) { if (id != null && map.getValue(id) == null) { String uuid; @@ -430,7 +614,7 @@ public class MediaQualityService extends SystemService { return columns.toArray(new String[0]); } - private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) { + private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) { return new PictureProfile( getTempId(mPictureProfileTempIdMap, cursor), getType(cursor), @@ -442,7 +626,7 @@ public class MediaQualityService extends SystemService { ); } - private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) { + private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) { return new SoundProfile( getTempId(mSoundProfileTempIdMap, cursor), getType(cursor), @@ -502,7 +686,7 @@ public class MediaQualityService extends SystemService { ) { List<PictureProfile> pictureProfiles = new ArrayList<>(); while (cursor.moveToNext()) { - pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor)); + pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor)); } return pictureProfiles; } @@ -517,30 +701,64 @@ public class MediaQualityService extends SystemService { ) { List<SoundProfile> soundProfiles = new ArrayList<>(); while (cursor.moveToNext()) { - soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor)); + soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor)); } return soundProfiles; } } + private void notifyError(String profileId, int errorCode, int uid, int pid) { + UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); + int n = userState.mCallbacks.beginBroadcast(); + + for (int i = 0; i < n; ++i) { + try { + IPictureProfileCallback callback = userState.mCallbacks.getBroadcastItem(i); + Pair<Integer, Integer> pidUid = userState.mCallbackPidUidMap.get(callback); + + if (pidUid.first == pid && pidUid.second == uid) { + userState.mCallbacks.getBroadcastItem(i).onError(profileId, errorCode); + } + } catch (RemoteException e) { + Slog.e(TAG, "failed to report added input to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + @Override public void registerPictureProfileCallback(final IPictureProfileCallback callback) { + int callingPid = Binder.getCallingPid(); + int callingUid = Binder.getCallingUid(); + + UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid()); + userState.mCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid)); } + @Override public void registerSoundProfileCallback(final ISoundProfileCallback callback) { } @Override public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override public void setAmbientBacklightSettings( AmbientBacklightSettings settings, UserHandle user) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override @@ -551,20 +769,34 @@ public class MediaQualityService extends SystemService { @Override public List<String> getPictureProfileAllowList(UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } return new ArrayList<>(); } @Override public void setPictureProfileAllowList(List<String> packages, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override public List<String> getSoundProfileAllowList(UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } return new ArrayList<>(); } @Override public void setSoundProfileAllowList(List<String> packages, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -574,6 +806,9 @@ public class MediaQualityService extends SystemService { @Override public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -583,6 +818,9 @@ public class MediaQualityService extends SystemService { @Override public void setSuperResolutionEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -592,6 +830,9 @@ public class MediaQualityService extends SystemService { @Override public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -604,4 +845,38 @@ public class MediaQualityService extends SystemService { return false; } } + + private class MediaQualityManagerCallbackList extends + RemoteCallbackList<IPictureProfileCallback> { + @Override + public void onCallbackDied(IPictureProfileCallback callback) { + //todo + } + } + + private final class UserState { + // A list of callbacks. + private final MediaQualityManagerCallbackList mCallbacks = + new MediaQualityManagerCallbackList(); + + private final Map<IPictureProfileCallback, Pair<Integer, Integer>> mCallbackPidUidMap = + new HashMap<>(); + + private UserState(Context context, int userId) { + + } + } + + private UserState getOrCreateUserStateLocked(int userId) { + UserState userState = getUserStateLocked(userId); + if (userState == null) { + userState = new UserState(mContext, userId); + mUserStates.put(userId, userState); + } + return userState; + } + + private UserState getUserStateLocked(int userId) { + return mUserStates.get(userId); + } } diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index 0b40d64e3a09..3f2c2228e453 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -325,7 +325,7 @@ public class ConditionProviders extends ManagedServices { for (int i = 0; i < N; i++) { final Condition c = conditions[i]; if (mCallback != null) { - mCallback.onConditionChanged(c.id, c); + mCallback.onConditionChanged(c.id, c, info.uid); } } } @@ -515,7 +515,7 @@ public class ConditionProviders extends ManagedServices { public interface Callback { void onServiceAdded(ComponentName component); - void onConditionChanged(Uri id, Condition condition); + void onConditionChanged(Uri id, Condition condition, int callerUid); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f50e8aa7eb7b..9567c818fa18 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5903,8 +5903,9 @@ public class NotificationManagerService extends SystemService { // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. @Override public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException { - enforcePolicyAccess(Binder.getCallingUid(), "getZenRules"); - return mZenModeHelper.getZenRules(getCallingZenUser()); + int callingUid = Binder.getCallingUid(); + enforcePolicyAccess(callingUid, "getZenRules"); + return mZenModeHelper.getZenRules(getCallingZenUser(), callingUid); } @Override @@ -5912,15 +5913,17 @@ public class NotificationManagerService extends SystemService { if (!android.app.Flags.modesApi()) { throw new IllegalStateException("getAutomaticZenRules called with flag off!"); } - enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules"); - return mZenModeHelper.getAutomaticZenRules(getCallingZenUser()); + int callingUid = Binder.getCallingUid(); + enforcePolicyAccess(callingUid, "getAutomaticZenRules"); + return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid); } @Override public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException { Objects.requireNonNull(id, "Id is null"); - enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule"); - return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id); + int callingUid = Binder.getCallingUid(); + enforcePolicyAccess(callingUid, "getAutomaticZenRule"); + return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id, callingUid); } @Override @@ -6065,8 +6068,9 @@ public class NotificationManagerService extends SystemService { @Condition.State public int getAutomaticZenRuleState(@NonNull String id) { Objects.requireNonNull(id, "id is null"); - enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState"); - return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id); + int callingUid = Binder.getCallingUid(); + enforcePolicyAccess(callingUid, "getAutomaticZenRuleState"); + return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id, callingUid); } @Override diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 52d0c41614d5..d44baeb58a28 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -113,15 +113,18 @@ public class ZenModeConditions implements ConditionProviders.Callback { } @Override - public void onConditionChanged(Uri id, Condition condition) { + public void onConditionChanged(Uri id, Condition condition, int callingUid) { if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition); ZenModeConfig config = mHelper.getConfig(); if (config == null) return; - final int callingUid = Binder.getCallingUid(); + if (!Flags.fixCallingUidFromCps()) { + // Old behavior: overwrite with known-bad callingUid (always system_server). + callingUid = Binder.getCallingUid(); + } // This change is known to be for UserHandle.CURRENT because ConditionProviders for // background users are not bound. - mHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, condition, + mHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, id, condition, callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM : ZenModeConfig.ORIGIN_APP, callingUid); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index b571d62c0cba..0a63f3fb36d0 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -413,13 +413,13 @@ public class ZenModeHelper { } // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined. - public List<ZenRule> getZenRules(UserHandle user) { + public List<ZenRule> getZenRules(UserHandle user, int callingUid) { List<ZenRule> rules = new ArrayList<>(); synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) return rules; for (ZenRule rule : config.automaticRules.values()) { - if (canManageAutomaticZenRule(rule)) { + if (canManageAutomaticZenRule(rule, callingUid)) { rules.add(rule); } } @@ -432,8 +432,8 @@ public class ZenModeHelper { * (which means the owned rules for a regular app, and every rule for system callers) together * with their ids. */ - Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user) { - List<ZenRule> ruleList = getZenRules(user); + Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) { + List<ZenRule> ruleList = getZenRules(user, callingUid); HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size()); for (ZenRule rule : ruleList) { rules.put(rule.id, zenRuleToAutomaticZenRule(rule)); @@ -441,7 +441,7 @@ public class ZenModeHelper { return rules; } - public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id) { + public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id, int callingUid) { ZenRule rule; synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); @@ -449,7 +449,7 @@ public class ZenModeHelper { rule = config.automaticRules.get(id); } if (rule == null) return null; - if (canManageAutomaticZenRule(rule)) { + if (canManageAutomaticZenRule(rule, callingUid)) { return zenRuleToAutomaticZenRule(rule); } return null; @@ -591,7 +591,7 @@ public class ZenModeHelper { + " reason=" + reason); } ZenModeConfig.ZenRule oldRule = config.automaticRules.get(ruleId); - if (oldRule == null || !canManageAutomaticZenRule(oldRule)) { + if (oldRule == null || !canManageAutomaticZenRule(oldRule, callingUid)) { throw new SecurityException( "Cannot update rules not owned by your condition provider"); } @@ -859,7 +859,7 @@ public class ZenModeHelper { newConfig = config.copy(); ZenRule ruleToRemove = newConfig.automaticRules.get(id); if (ruleToRemove == null) return false; - if (canManageAutomaticZenRule(ruleToRemove)) { + if (canManageAutomaticZenRule(ruleToRemove, callingUid)) { newConfig.automaticRules.remove(id); maybePreserveRemovedRule(newConfig, ruleToRemove, origin); if (ruleToRemove.getPkg() != null @@ -893,7 +893,8 @@ public class ZenModeHelper { newConfig = config.copy(); for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) { ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i)); - if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) { + if (Objects.equals(rule.getPkg(), packageName) + && canManageAutomaticZenRule(rule, callingUid)) { newConfig.automaticRules.removeAt(i); maybePreserveRemovedRule(newConfig, rule, origin); } @@ -938,14 +939,14 @@ public class ZenModeHelper { } @Condition.State - int getAutomaticZenRuleState(UserHandle user, String id) { + int getAutomaticZenRuleState(UserHandle user, String id, int callingUid) { synchronized (mConfigLock) { ZenModeConfig config = getConfigLocked(user); if (config == null) { return Condition.STATE_UNKNOWN; } ZenRule rule = config.automaticRules.get(id); - if (rule == null || !canManageAutomaticZenRule(rule)) { + if (rule == null || !canManageAutomaticZenRule(rule, callingUid)) { return Condition.STATE_UNKNOWN; } if (Flags.modesApi() && Flags.modesUi()) { @@ -968,7 +969,7 @@ public class ZenModeHelper { newConfig = config.copy(); ZenRule rule = newConfig.automaticRules.get(id); if (Flags.modesApi()) { - if (rule != null && canManageAutomaticZenRule(rule)) { + if (rule != null && canManageAutomaticZenRule(rule, callingUid)) { setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule), condition, origin, callingUid); } @@ -980,8 +981,8 @@ public class ZenModeHelper { } } - void setAutomaticZenRuleState(UserHandle user, Uri ruleDefinition, Condition condition, - @ConfigOrigin int origin, int callingUid) { + void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition, + Condition condition, @ConfigOrigin int origin, int callingUid) { checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { @@ -992,7 +993,7 @@ public class ZenModeHelper { List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition); if (Flags.modesApi()) { for (int i = matchingRules.size() - 1; i >= 0; i--) { - if (!canManageAutomaticZenRule(matchingRules.get(i))) { + if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) { matchingRules.remove(i); } } @@ -1125,15 +1126,21 @@ public class ZenModeHelper { return count; } - public boolean canManageAutomaticZenRule(ZenRule rule) { - final int callingUid = Binder.getCallingUid(); + public boolean canManageAutomaticZenRule(ZenRule rule, int callingUid) { + if (!com.android.server.notification.Flags.fixCallingUidFromCps()) { + // Old behavior: ignore supplied callingUid and instead obtain it here. Will be + // incorrect if not currently handling a Binder call. + callingUid = Binder.getCallingUid(); + } + if (callingUid == 0 || callingUid == Process.SYSTEM_UID) { + // Checked specifically, because checkCallingPermission() will fail. return true; } else if (mContext.checkCallingPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { return true; } else { - String[] packages = mPm.getPackagesForUid(Binder.getCallingUid()); + String[] packages = mPm.getPackagesForUid(callingUid); if (packages != null) { final int packageCount = packages.length; for (int i = 0; i < packageCount; i++) { @@ -2902,8 +2909,8 @@ public class ZenModeHelper { } /** - * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} overloads makes - * sense. + * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} or + * {@link #setAutomaticZenRuleStateFromConditionProvider} makes sense. */ private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) { if (!Flags.modesApi()) { diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index f15c23e110a4..2b4d71e85dc0 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -196,4 +196,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "fix_calling_uid_from_cps" + namespace: "systemui" + description: "Correctly checks zen rule ownership when a CPS notifies with a Condition" + bug: "379722187" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index 1b22154c10f6..d33c860343c5 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -28,6 +28,7 @@ import android.os.IBinder; import android.os.IIdmap2; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemService; import android.text.TextUtils; @@ -40,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; /** * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds @@ -66,7 +66,7 @@ class IdmapDaemon { private static IdmapDaemon sInstance; private volatile IIdmap2 mService; - private final AtomicInteger mOpenedCount = new AtomicInteger(); + private int mOpenedCount = 0; private final Object mIdmapToken = new Object(); /** @@ -74,15 +74,20 @@ class IdmapDaemon { * finalized, the idmap service will be stopped after a period of time unless another connection * to the service is open. **/ - private class Connection implements AutoCloseable { + private final class Connection implements AutoCloseable { @Nullable private final IIdmap2 mIdmap2; private boolean mOpened = true; - private Connection(IIdmap2 idmap2) { + private Connection() { + mIdmap2 = null; + mOpened = false; + } + + private Connection(@NonNull IIdmap2 idmap2) { + mIdmap2 = idmap2; synchronized (mIdmapToken) { - mOpenedCount.incrementAndGet(); - mIdmap2 = idmap2; + ++mOpenedCount; } } @@ -94,20 +99,22 @@ class IdmapDaemon { } mOpened = false; - if (mOpenedCount.decrementAndGet() != 0) { + if (--mOpenedCount != 0) { // Only post the callback to stop the service if the service does not have an // open connection. return; } + final var service = mService; FgThread.getHandler().postDelayed(() -> { synchronized (mIdmapToken) { - // Only stop the service if the service does not have an open connection. - if (mService == null || mOpenedCount.get() != 0) { + // Only stop the service if it's the one we were scheduled for and + // it does not have an open connection. + if (mService != service || mOpenedCount != 0) { return; } - stopIdmapService(); + stopIdmapServiceLocked(); mService = null; } }, mIdmapToken, SERVICE_TIMEOUT_MS); @@ -175,6 +182,8 @@ class IdmapDaemon { } boolean idmapExists(String overlayPath, int userId) { + // The only way to verify an idmap is to read its state on disk. + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try (Connection c = connect()) { final IIdmap2 idmap2 = c.getIdmap2(); if (idmap2 == null) { @@ -187,6 +196,8 @@ class IdmapDaemon { } catch (Exception e) { Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); return false; + } finally { + StrictMode.setThreadPolicy(oldPolicy); } } @@ -242,14 +253,16 @@ class IdmapDaemon { } catch (Exception e) { Slog.wtf(TAG, "failed to get all fabricated overlays", e); } finally { - try { - if (c.getIdmap2() != null && iteratorId != -1) { - c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId); + if (c != null) { + try { + if (c.getIdmap2() != null && iteratorId != -1) { + c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId); + } + } catch (RemoteException e) { + // ignore } - } catch (RemoteException e) { - // ignore + c.close(); } - c.close(); } return allInfos; } @@ -271,9 +284,11 @@ class IdmapDaemon { } @Nullable - private IBinder getIdmapService() throws TimeoutException, RemoteException { + private IBinder getIdmapServiceLocked() throws TimeoutException, RemoteException { try { - SystemService.start(IDMAP_DAEMON); + if (!SystemService.isRunning(IDMAP_DAEMON)) { + SystemService.start(IDMAP_DAEMON); + } } catch (RuntimeException e) { Slog.wtf(TAG, "Failed to enable idmap2 daemon", e); if (e.getMessage().contains("failed to set system property")) { @@ -306,9 +321,11 @@ class IdmapDaemon { walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS)); } - private static void stopIdmapService() { + private static void stopIdmapServiceLocked() { try { - SystemService.stop(IDMAP_DAEMON); + if (SystemService.isRunning(IDMAP_DAEMON)) { + SystemService.stop(IDMAP_DAEMON); + } } catch (RuntimeException e) { // If the idmap daemon cannot be disabled for some reason, it is okay // since we already finished invoking idmap. @@ -326,9 +343,9 @@ class IdmapDaemon { return new Connection(mService); } - IBinder binder = getIdmapService(); + IBinder binder = getIdmapServiceLocked(); if (binder == null) { - return new Connection(null); + return new Connection(); } mService = IIdmap2.Stub.asInterface(binder); diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index cc5c88b77293..d806770e5c91 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -19,7 +19,6 @@ package com.android.server.om; import android.annotation.NonNull; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; -import android.content.res.Flags; import android.net.Uri; import android.os.Process; import android.text.TextUtils; @@ -163,15 +162,11 @@ public class OverlayActorEnforcer { return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } - // Framework doesn't have <overlayable> declaration by design, and we still want to be able - // to enable its overlays from the packages with the permission. - if (targetOverlayable == null - && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals( - "android"))) { + if (targetOverlayable == null) { return ActorState.MISSING_OVERLAYABLE; } - final String actor = targetOverlayable == null ? null : targetOverlayable.actor; + String actor = targetOverlayable.actor; if (TextUtils.isEmpty(actor)) { // If there's no actor defined, fallback to the legacy permission check try { diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java index fdceabe74dd8..18de9952ed19 100644 --- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java +++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java @@ -26,15 +26,13 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.CollectionUtils; import com.android.server.SystemConfig; import com.android.server.pm.pkg.AndroidPackage; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -121,20 +119,16 @@ public class OverlayReferenceMapper { return actorPair.first; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) { + public Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg) { String target = pkg.getOverlayTarget(); if (TextUtils.isEmpty(target)) { - return Collections.emptyMap(); + return null; } String overlayable = pkg.getOverlayTargetOverlayableName(); - Map<String, Set<String>> targetToOverlayables = new HashMap<>(); - Set<String> overlayables = new HashSet<>(); - overlayables.add(overlayable); - targetToOverlayables.put(target, overlayables); - return targetToOverlayables; + return Pair.create(target, overlayable); } }; } @@ -174,7 +168,7 @@ public class OverlayReferenceMapper { } // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks - if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) { + if (mProvider.getTargetToOverlayables(pkg) != null) { addOverlay(pkg, otherPkgs, changed); } @@ -245,20 +239,17 @@ public class OverlayReferenceMapper { String target = targetPkg.getPackageName(); removeTarget(target, changedPackages); - Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); - for (String overlayable : overlayablesToActors.keySet()) { - String actor = overlayablesToActors.get(overlayable); + final Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); + for (final var entry : overlayablesToActors.entrySet()) { + final String overlayable = entry.getKey(); + final String actor = entry.getValue(); addTargetToMap(actor, target, changedPackages); for (AndroidPackage overlayPkg : otherPkgs.values()) { - Map<String, Set<String>> targetToOverlayables = + var targetToOverlayables = mProvider.getTargetToOverlayables(overlayPkg); - Set<String> overlayables = targetToOverlayables.get(target); - if (CollectionUtils.isEmpty(overlayables)) { - continue; - } - - if (overlayables.contains(overlayable)) { + if (targetToOverlayables != null && targetToOverlayables.first.equals(target) + && Objects.equals(targetToOverlayables.second, overlayable)) { String overlay = overlayPkg.getPackageName(); addOverlayToMap(actor, target, overlay, changedPackages); } @@ -310,25 +301,22 @@ public class OverlayReferenceMapper { String overlay = overlayPkg.getPackageName(); removeOverlay(overlay, changedPackages); - Map<String, Set<String>> targetToOverlayables = + Pair<String, String> targetToOverlayables = mProvider.getTargetToOverlayables(overlayPkg); - for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) { - String target = entry.getKey(); - Set<String> overlayables = entry.getValue(); + if (targetToOverlayables != null) { + String target = targetToOverlayables.first; AndroidPackage targetPkg = otherPkgs.get(target); if (targetPkg == null) { - continue; + return; } - String targetPkgName = targetPkg.getPackageName(); Map<String, String> overlayableToActor = targetPkg.getOverlayables(); - for (String overlayable : overlayables) { - String actor = overlayableToActor.get(overlayable); - if (TextUtils.isEmpty(actor)) { - continue; - } - addOverlayToMap(actor, targetPkgName, overlay, changedPackages); + String overlayable = targetToOverlayables.second; + String actor = overlayableToActor.get(overlayable); + if (TextUtils.isEmpty(actor)) { + return; } + addOverlayToMap(actor, targetPkgName, overlay, changedPackages); } } } @@ -430,11 +418,11 @@ public class OverlayReferenceMapper { String getActorPkg(@NonNull String actor); /** - * Mock response of multiple overlay tags. + * Mock response of overlay tags. * * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests */ - @NonNull - Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg); + @Nullable + Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg); } } diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java index 3aefc5a64926..473ed6136e9a 100644 --- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java +++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java @@ -23,6 +23,7 @@ import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.security.FileIntegrity; import libcore.io.IoUtils; @@ -121,6 +122,11 @@ final class ResilientAtomicFile implements Closeable { } public void finishWrite(FileOutputStream str) throws IOException { + finishWrite(str, true /* doFsVerity */); + } + + @VisibleForTesting + public void finishWrite(FileOutputStream str, final boolean doFsVerity) throws IOException { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } @@ -145,13 +151,15 @@ final class ResilientAtomicFile implements Closeable { finalizeOutStream(reserveOutStream); } - // Protect both main and reserve using fs-verity. - try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); - ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { - FileIntegrity.setUpFsVerity(mainPfd); - FileIntegrity.setUpFsVerity(copyPfd); - } catch (IOException e) { - Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); + if (doFsVerity) { + // Protect both main and reserve using fs-verity. + try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); + ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { + FileIntegrity.setUpFsVerity(mainPfd); + FileIntegrity.setUpFsVerity(copyPfd); + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); + } } } catch (IOException e) { Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 44789e4c4de2..027da4986ce6 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -179,7 +179,7 @@ abstract class ShortcutPackageItem { itemOut.endDocument(); os.flush(); - file.finishWrite(os); + mShortcutUser.mService.injectFinishWrite(file, os); } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(os); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 2785da5cbdbd..373c1ed3c386 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1008,7 +1008,7 @@ public class ShortcutService extends IShortcutService.Stub { out.endDocument(); // Close. - file.finishWrite(outs); + injectFinishWrite(file, outs); } catch (IOException e) { Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(outs); @@ -1096,7 +1096,7 @@ public class ShortcutService extends IShortcutService.Stub { saveUserInternalLocked(userId, os, /* forBackup= */ false); } - file.finishWrite(os); + injectFinishWrite(file, os); // Remove all dangling bitmap files. cleanupDanglingBitmapDirectoriesLocked(userId); @@ -5067,6 +5067,12 @@ public class ShortcutService extends IShortcutService.Stub { return Build.FINGERPRINT; } + // Injection point. + void injectFinishWrite(@NonNull final ResilientAtomicFile file, + @NonNull final FileOutputStream os) throws IOException { + file.finishWrite(os); + } + final void wtf(String message) { wtf(message, /* exception= */ null); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 672eb4caf798..9d840d0c0d35 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1681,8 +1681,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { // handle overflow if (attributionChainId < 0) { - attributionChainId = 0; sAttributionChainIds.set(0); + attributionChainId = sAttributionChainIds.incrementAndGet(); } return attributionChainId; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5ab59657d4ce..516213b32354 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -562,8 +562,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mPowerKeyHandled; volatile boolean mBackKeyHandled; volatile boolean mEndCallKeyHandled; - volatile boolean mCameraGestureTriggered; - volatile boolean mCameraGestureTriggeredDuringGoingToSleep; + volatile boolean mPowerButtonLaunchGestureTriggered; + volatile boolean mPowerButtonLaunchGestureTriggeredDuringGoingToSleep; /** * {@code true} if the device is entering a low-power state; {@code false otherwise}. @@ -5893,7 +5893,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mGestureLauncherService == null) { return false; } - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggered = false; final MutableBoolean outLaunched = new MutableBoolean(false); final boolean intercept = mGestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched); @@ -5903,9 +5903,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // detector from processing the power key later on. return intercept; } - mCameraGestureTriggered = true; + mPowerButtonLaunchGestureTriggered = true; if (mRequestedOrSleepingDefaultDisplay) { - mCameraGestureTriggeredDuringGoingToSleep = true; + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = true; // Wake device up early to prevent display doing redundant turning off/on stuff. mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture(); } @@ -6282,13 +6282,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mKeyguardDelegate != null) { mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, - mCameraGestureTriggeredDuringGoingToSleep); + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep); } if (mDisplayFoldController != null) { mDisplayFoldController.finishedGoingToSleep(); } - mCameraGestureTriggeredDuringGoingToSleep = false; - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = false; + mPowerButtonLaunchGestureTriggered = false; } // Called on the PowerManager's Notifier thread. @@ -6319,10 +6319,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered); + mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mPowerButtonLaunchGestureTriggered); } - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggered = false; } // Called on the PowerManager's Notifier thread. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index da8b01ac86fb..587447b8af26 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -198,7 +198,7 @@ public class KeyguardServiceDelegate { if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) { mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN, - false /* cameraGestureTriggered */); + false /* powerButtonLaunchGestureTriggered */); } if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) { mKeyguardService.onFinishedWakingUp(); @@ -319,10 +319,10 @@ public class KeyguardServiceDelegate { } public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) { if (mKeyguardService != null) { if (DEBUG) Log.v(TAG, "onStartedWakingUp()"); - mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mKeyguardService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING; } @@ -383,9 +383,11 @@ public class KeyguardServiceDelegate { } public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, + boolean powerButtonLaunchGestureTriggered) { if (mKeyguardService != null) { - mKeyguardService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); + mKeyguardService.onFinishedGoingToSleep(pmSleepReason, + powerButtonLaunchGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP; } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index cd789eaed1b3..f2342e0d5688 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -113,9 +113,10 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, + boolean powerButtonLaunchGestureTriggered) { try { - mService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); + mService.onFinishedGoingToSleep(pmSleepReason, powerButtonLaunchGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } @@ -123,9 +124,9 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) { try { - mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } 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 1726f0da9cbe..a6f2a3757dcb 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -121,6 +121,8 @@ public final class HintManagerService extends SystemService { @VisibleForTesting final long mHintSessionPreferredRate; @VisibleForTesting static final int MAX_GRAPHICS_PIPELINE_THREADS_COUNT = 5; + private static final int DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT = 5; + private static final int DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS = 50; // Multi-level map storing all active AppHintSessions. // First level is keyed by the UID of the client process creating the session. @@ -206,12 +208,17 @@ public final class HintManagerService extends SystemService { "persist.hms.check_headroom_affinity"; private static final String PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS = "persist.hms.check_headroom_proc_stat_min_millis"; + private static final String PROPERTY_CPU_HEADROOM_TID_MAX_CNT = + "persist.hms.cpu_headroom_tid_max_cnt"; private Boolean mFMQUsesIntegratedEventFlag = false; private final Object mCpuHeadroomLock = new Object(); @VisibleForTesting final float mJiffyMillis; + private final boolean mCheckHeadroomTid; + private final boolean mCheckHeadroomAffinity; private final int mCheckHeadroomProcStatMinMillis; + private final int mCpuHeadroomMaxTidCnt; @GuardedBy("mCpuHeadroomLock") private long mLastCpuUserModeTimeCheckedMillis = 0; @GuardedBy("mCpuHeadroomLock") @@ -339,13 +346,23 @@ public final class HintManagerService extends SystemService { mUidToLastUserModeJiffies = new ArrayMap<>(); long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK); mJiffyMillis = 1000.0f / jiffyHz; + mCheckHeadroomTid = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true); + mCheckHeadroomAffinity = SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, + true); mCheckHeadroomProcStatMinMillis = SystemProperties.getInt( - PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, 50); + PROPERTY_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS, + DEFAULT_CHECK_HEADROOM_PROC_STAT_MIN_MILLIS); + mCpuHeadroomMaxTidCnt = Math.min(SystemProperties.getInt( + PROPERTY_CPU_HEADROOM_TID_MAX_CNT, DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT), + mSupportInfo.headroom.cpuMaxTidCount); } else { mCpuHeadroomCache = null; mUidToLastUserModeJiffies = null; mJiffyMillis = 0.0f; + mCheckHeadroomTid = true; + mCheckHeadroomAffinity = true; mCheckHeadroomProcStatMinMillis = 0; + mCpuHeadroomMaxTidCnt = 0; } if (mSupportInfo.headroom.isGpuSupported) { mGpuHeadroomCache = new HeadroomCache<>(2, mSupportInfo.headroom.gpuMinIntervalMillis); @@ -1577,8 +1594,7 @@ public final class HintManagerService extends SystemService { if (params.usesDeviceHeadroom) { halParams.tids = new int[]{}; } else if (params.tids != null && params.tids.length > 0) { - if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && SystemProperties.getBoolean( - PROPERTY_CHECK_HEADROOM_TID, true)) { + if (UserHandle.getAppId(uid) != Process.SYSTEM_UID && mCheckHeadroomTid) { final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid()); for (int tid : params.tids) { if (Process.getThreadGroupLeader(tid) != tgid) { @@ -1588,8 +1604,8 @@ public final class HintManagerService extends SystemService { } } } - if (cpuHeadroomAffinityCheck() && params.tids.length > 1 - && SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_AFFINITY, true)) { + if (cpuHeadroomAffinityCheck() && mCheckHeadroomAffinity + && params.tids.length > 1) { checkThreadAffinityForTids(params.tids); } halParams.tids = params.tids; @@ -1709,15 +1725,22 @@ public final class HintManagerService extends SystemService { throw new IllegalArgumentException( "Unknown CPU headroom calculation type " + (int) params.calculationType); } - if (params.calculationWindowMillis < 50 || params.calculationWindowMillis > 10000) { + if (params.calculationWindowMillis < mSupportInfo.headroom.cpuMinCalculationWindowMillis + || params.calculationWindowMillis + > mSupportInfo.headroom.cpuMaxCalculationWindowMillis) { throw new IllegalArgumentException( - "Invalid CPU headroom calculation window, expected [50, 10000] but got " + "Invalid CPU headroom calculation window, expected [" + + mSupportInfo.headroom.cpuMinCalculationWindowMillis + + ", " + + mSupportInfo.headroom.cpuMaxCalculationWindowMillis + + "] but got " + params.calculationWindowMillis); } if (!params.usesDeviceHeadroom) { - if (params.tids != null && params.tids.length > 5) { + if (params.tids != null && params.tids.length > mCpuHeadroomMaxTidCnt) { throw new IllegalArgumentException( - "More than 5 TIDs requested: " + params.tids.length); + "More than " + mCpuHeadroomMaxTidCnt + " TIDs requested: " + + params.tids.length); } } } @@ -1772,9 +1795,13 @@ public final class HintManagerService extends SystemService { throw new IllegalArgumentException( "Unknown GPU headroom calculation type " + (int) params.calculationType); } - if (params.calculationWindowMillis < 50 || params.calculationWindowMillis > 10000) { + if (params.calculationWindowMillis < mSupportInfo.headroom.gpuMinCalculationWindowMillis + || params.calculationWindowMillis + > mSupportInfo.headroom.gpuMaxCalculationWindowMillis) { throw new IllegalArgumentException( - "Invalid GPU headroom calculation window, expected [50, 10000] but got " + "Invalid GPU headroom calculation window, expected [" + + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", " + + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "] but got " + params.calculationWindowMillis); } } @@ -1807,9 +1834,15 @@ public final class HintManagerService extends SystemService { @Override public IHintManager.HintManagerClientData registerClient(@NonNull IHintManager.IHintManagerClient clientBinder) { + return getClientData(); + } + + @Override + public IHintManager.HintManagerClientData getClientData() { IHintManager.HintManagerClientData out = new IHintManager.HintManagerClientData(); out.preferredRateNanos = mHintSessionPreferredRate; out.maxGraphicsPipelineThreads = getMaxGraphicsPipelineThreadsCount(); + out.maxCpuHeadroomThreads = DEFAULT_MAX_CPU_HEADROOM_THREADS_COUNT; out.powerHalVersion = mPowerHalVersion; out.supportInfo = mSupportInfo; return out; @@ -1838,23 +1871,40 @@ public final class HintManagerService extends SystemService { } } } - pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis); - pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis); - try { - CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); - params.usesDeviceHeadroom = true; - CpuHeadroomResult ret = getCpuHeadroom(params); - pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); - } catch (Exception e) { - Slog.d(TAG, "Failed to dump CPU headroom", e); - pw.println("CPU headroom: N/A"); + pw.println("CPU Headroom Supported: " + mSupportInfo.headroom.isCpuSupported); + if (mSupportInfo.headroom.isCpuSupported) { + pw.println("CPU Headroom Interval: " + mSupportInfo.headroom.cpuMinIntervalMillis); + pw.println("CPU Headroom TID Max Count: " + mCpuHeadroomMaxTidCnt); + pw.println("CPU Headroom TID Max Count From HAL: " + + mSupportInfo.headroom.cpuMaxTidCount); + pw.println("CPU Headroom Calculation Window Range: [" + + mSupportInfo.headroom.cpuMinCalculationWindowMillis + ", " + + mSupportInfo.headroom.cpuMaxCalculationWindowMillis + "]"); + try { + CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal(); + params.usesDeviceHeadroom = true; + CpuHeadroomResult ret = getCpuHeadroom(params); + pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); + } catch (Exception e) { + Slog.d(TAG, "Failed to dump CPU headroom", e); + pw.println("CPU headroom: N/A"); + } } - try { - GpuHeadroomResult ret = getGpuHeadroom(new GpuHeadroomParamsInternal()); - pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); - } catch (Exception e) { - Slog.d(TAG, "Failed to dump GPU headroom", e); - pw.println("GPU headroom: N/A"); + pw.println("GPU Headroom Supported: " + mSupportInfo.headroom.isGpuSupported); + if (mSupportInfo.headroom.isGpuSupported) { + pw.println("GPU Headroom Interval: " + mSupportInfo.headroom.gpuMinIntervalMillis); + pw.println("GPU Headroom Calculation Window Range: [" + + mSupportInfo.headroom.gpuMinCalculationWindowMillis + ", " + + mSupportInfo.headroom.gpuMaxCalculationWindowMillis + "]"); + try { + GpuHeadroomParamsInternal params = new GpuHeadroomParamsInternal(); + params.calculationWindowMillis = mDefaultGpuHeadroomCalculationWindowMillis; + GpuHeadroomResult ret = getGpuHeadroom(params); + pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom())); + } catch (Exception e) { + Slog.d(TAG, "Failed to dump GPU headroom", e); + pw.println("GPU headroom: N/A"); + } } } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index caaf5a2b16d0..9206cce12cd6 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -2205,6 +2205,11 @@ public class BatteryStatsImpl extends BatteryStats { getWakelockDurationRetriever() { return mWakelockDurationRetriever; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return BatteryStatsImpl.this.networkStatsDelta(stats, oldStats); + } } private final PowerStatsCollectorInjector mPowerStatsCollectorInjector = @@ -12392,83 +12397,13 @@ public class BatteryStatsImpl extends BatteryStats { return networkStatsManager.getWifiUidStats(); } - static class NetworkStatsDelta { - int mUid; - int mSet; - long mRxBytes; - long mRxPackets; - long mTxBytes; - long mTxPackets; - - public int getUid() { - return mUid; - } - - - public int getSet() { - return mSet; - } - - public long getRxBytes() { - return mRxBytes; - } - - public long getRxPackets() { - return mRxPackets; - } - - public long getTxBytes() { - return mTxBytes; - } - - public long getTxPackets() { - return mTxPackets; - } - - @Override - public String toString() { - return "NetworkStatsDelta{mUid=" + mUid + ", mSet=" + mSet + ", mRxBytes=" + mRxBytes - + ", mRxPackets=" + mRxPackets + ", mTxBytes=" + mTxBytes + ", mTxPackets=" - + mTxPackets + '}'; - } - } - - static List<NetworkStatsDelta> computeDelta(NetworkStats currentStats, - NetworkStats lastStats) { - List<NetworkStatsDelta> deltaList = new ArrayList<>(); - for (NetworkStats.Entry entry : currentStats) { - NetworkStatsDelta delta = new NetworkStatsDelta(); - delta.mUid = entry.getUid(); - delta.mSet = entry.getSet(); - NetworkStats.Entry lastEntry = null; - if (lastStats != null) { - for (NetworkStats.Entry e : lastStats) { - if (e.getUid() == entry.getUid() && e.getSet() == entry.getSet() - && e.getTag() == entry.getTag() - && e.getMetered() == entry.getMetered() - && e.getRoaming() == entry.getRoaming() - && e.getDefaultNetwork() == entry.getDefaultNetwork() - /*&& Objects.equals(e.getIface(), entry.getIface())*/) { - lastEntry = e; - break; - } - } - } - if (lastEntry != null) { - delta.mRxBytes = Math.max(0, entry.getRxBytes() - lastEntry.getRxBytes()); - delta.mRxPackets = Math.max(0, entry.getRxPackets() - lastEntry.getRxPackets()); - delta.mTxBytes = Math.max(0, entry.getTxBytes() - lastEntry.getTxBytes()); - delta.mTxPackets = Math.max(0, entry.getTxPackets() - lastEntry.getTxPackets()); - } else { - delta.mRxBytes = entry.getRxBytes(); - delta.mRxPackets = entry.getRxPackets(); - delta.mTxBytes = entry.getTxBytes(); - delta.mTxPackets = entry.getTxPackets(); - } - deltaList.add(delta); + @VisibleForTesting + protected NetworkStats networkStatsDelta(@NonNull NetworkStats stats, + @Nullable NetworkStats oldStats) { + if (oldStats == null) { + return stats; } - - return deltaList; + return stats.subtract(oldStats); } /** @@ -12486,12 +12421,12 @@ public class BatteryStatsImpl extends BatteryStats { } } + NetworkStats delta; // Grab a separate lock to acquire the network stats, which may do I/O. - List<NetworkStatsDelta> delta; synchronized (mWifiNetworkLock) { final NetworkStats latestStats = readWifiNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = computeDelta(latestStats, mLastWifiNetworkStats); + delta = networkStatsDelta(latestStats, mLastWifiNetworkStats); mLastWifiNetworkStats = latestStats; } else { delta = null; @@ -12501,15 +12436,15 @@ public class BatteryStatsImpl extends BatteryStats { } private void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo wifiActivityEnergyInfo, - List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) { + NetworkStats networkStatsDelta, long elapsedRealtimeMs, long uptimeMs) { // Do not populate consumed energy, because energy attribution is done by // WifiPowerStatsProcessor. - updateWifiBatteryStats(wifiActivityEnergyInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE, + updateWifiBatteryStats(wifiActivityEnergyInfo, networkStatsDelta, POWER_DATA_UNAVAILABLE, elapsedRealtimeMs, uptimeMs); } private void updateWifiBatteryStats(WifiActivityEnergyInfo info, - List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs, + NetworkStats delta, long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { @@ -12535,7 +12470,7 @@ public class BatteryStatsImpl extends BatteryStats { long totalTxPackets = 0; long totalRxPackets = 0; if (delta != null) { - for (NetworkStatsDelta entry : delta) { + for (NetworkStats.Entry entry : delta) { if (DEBUG_ENERGY) { Slog.d(TAG, "Wifi uid " + entry.getUid() + ": delta rx=" + entry.getRxBytes() @@ -12879,11 +12814,11 @@ public class BatteryStatsImpl extends BatteryStats { mLastModemActivityInfo = activityInfo; // Grab a separate lock to acquire the network stats, which may do I/O. - List<NetworkStatsDelta> delta = null; + NetworkStats delta = null; synchronized (mModemNetworkLock) { final NetworkStats latestStats = readMobileNetworkStatsLocked(networkStatsManager); if (latestStats != null) { - delta = computeDelta(latestStats, mLastModemNetworkStats); + delta = networkStatsDelta(latestStats, mLastModemNetworkStats); mLastModemNetworkStats = latestStats; } } @@ -12892,15 +12827,15 @@ public class BatteryStatsImpl extends BatteryStats { } private void onMobileRadioPowerStatsRetrieved(ModemActivityInfo modemActivityInfo, - List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) { + NetworkStats networkStatsDelta, long elapsedRealtimeMs, long uptimeMs) { // Do not populate consumed energy, because energy attribution is done by // MobileRadioPowerStatsProcessor. - updateCellularBatteryStats(modemActivityInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE, + updateCellularBatteryStats(modemActivityInfo, networkStatsDelta, POWER_DATA_UNAVAILABLE, elapsedRealtimeMs, uptimeMs); } private void updateCellularBatteryStats(@Nullable ModemActivityInfo deltaInfo, - @Nullable List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs, + @Nullable NetworkStats delta, long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { // Add modem tx power to history. addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs); @@ -13003,7 +12938,7 @@ public class BatteryStatsImpl extends BatteryStats { long totalRxPackets = 0; long totalTxPackets = 0; if (delta != null) { - for (NetworkStatsDelta entry : delta) { + for (NetworkStats.Entry entry : delta) { if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { continue; } @@ -13044,7 +12979,7 @@ public class BatteryStatsImpl extends BatteryStats { // Now distribute proportional blame to the apps that did networking. long totalPackets = totalRxPackets + totalTxPackets; if (totalPackets > 0) { - for (NetworkStatsDelta entry : delta) { + for (NetworkStats.Entry entry : delta) { if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { continue; } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java index cbd6fab2a9f7..f971e2e882c3 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -38,7 +38,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.MobileRadioPowerStatsLayout; import java.util.Arrays; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.LongSupplier; @@ -71,7 +70,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { interface Observer { void onMobileRadioPowerStatsRetrieved( @Nullable ModemActivityInfo modemActivityDelta, - @Nullable List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas, + @Nullable NetworkStats networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs); } @@ -86,6 +85,8 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { TelephonyManager getTelephonyManager(); LongSupplier getCallDurationSupplier(); LongSupplier getPhoneSignalScanDurationSupplier(); + + NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats); } private final Injector mInjector; @@ -190,7 +191,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { mPowerStats.uidStats.clear(); ModemActivityInfo modemActivityDelta = collectModemActivityInfo(); - List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats(); + NetworkStats networkStatsDeltas = collectNetworkStats(); mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout); @@ -288,17 +289,15 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { return deltaInfo; } - private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() { + private NetworkStats collectNetworkStats() { NetworkStats networkStats = mNetworkStatsSupplier.get(); if (networkStats == null) { return null; } - List<BatteryStatsImpl.NetworkStatsDelta> delta = - BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats); + NetworkStats delta = mInjector.networkStatsDelta(networkStats, mLastNetworkStats); mLastNetworkStats = networkStats; - for (int i = delta.size() - 1; i >= 0; i--) { - BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i); + for (NetworkStats.Entry uidDelta : delta) { long rxBytes = uidDelta.getRxBytes(); long txBytes = uidDelta.getTxBytes(); long rxPackets = uidDelta.getRxPackets(); diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java index 1fdeac9816d0..5440bcf1d124 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java @@ -31,7 +31,6 @@ import com.android.internal.os.PowerStats; import com.android.server.power.stats.format.WifiPowerStatsLayout; import java.util.Arrays; -import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -43,7 +42,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { interface Observer { void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo info, - List<BatteryStatsImpl.NetworkStatsDelta> delta, long elapsedRealtimeMs, + NetworkStats delta, long elapsedRealtimeMs, long uptimeMs); } @@ -66,6 +65,8 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { Supplier<NetworkStats> getWifiNetworkStatsSupplier(); WifiManager getWifiManager(); WifiStatsRetriever getWifiStatsRetriever(); + + NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats); } private final Injector mInjector; @@ -161,7 +162,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { } else { collectWifiActivityStats(); } - List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats(); + NetworkStats networkStatsDeltas = collectNetworkStats(); collectWifiScanTime(); mConsumedEnergyHelper.collectConsumedEnergy(mPowerStats, mLayout); @@ -227,17 +228,15 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { mPowerStats.durationMs = duration; } - private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() { + private NetworkStats collectNetworkStats() { NetworkStats networkStats = mNetworkStatsSupplier.get(); if (networkStats == null) { return null; } - List<BatteryStatsImpl.NetworkStatsDelta> delta = - BatteryStatsImpl.computeDelta(networkStats, mLastNetworkStats); + NetworkStats delta = mInjector.networkStatsDelta(networkStats, mLastNetworkStats); mLastNetworkStats = networkStats; - for (int i = delta.size() - 1; i >= 0; i--) { - BatteryStatsImpl.NetworkStatsDelta uidDelta = delta.get(i); + for (NetworkStats.Entry uidDelta : delta) { long rxBytes = uidDelta.getRxBytes(); long txBytes = uidDelta.getTxBytes(); long rxPackets = uidDelta.getRxPackets(); diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java index a75d110e3cd1..17739712d65a 100644 --- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java +++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java @@ -88,6 +88,5 @@ public class ResourcesManagerShellCommand extends ShellCommand { out.println(" Print this help text."); out.println(" dump <PROCESS>"); out.println(" Dump the Resources objects in use as well as the history of Resources"); - } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5dbdeff672e7..29f1f93a844f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -367,7 +367,6 @@ import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TransferPipe; import com.android.internal.policy.AttributeCache; -import com.android.internal.policy.PhoneWindow; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; @@ -2027,8 +2026,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); mStyleFillsParent = mOccludesParent; mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); - mOptOutEdgeToEdge = PhoneWindow.isOptingOutEdgeToEdgeEnforcement( - aInfo.applicationInfo, false /* local */, ent.array); + mOptOutEdgeToEdge = ent.array.getBoolean( + R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false); } else { mStyleFillsParent = mOccludesParent = true; mNoDisplay = false; @@ -3210,7 +3209,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A true /* forActivity */)) { return false; } - if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) { + if (mAppCompatController.getResizeOverrides().allowRestrictedResizability()) { return false; } // If the user preference respects aspect ratio, then it becomes non-resizable. @@ -3241,8 +3240,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The caller will check both application and activity level property. return true; } - return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(), - appInfo.packageName); + return !AppCompatResizeOverrides.allowRestrictedResizability( + wms.mContext.getPackageManager(), appInfo.packageName); } boolean isResizeable() { @@ -8436,8 +8435,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ @ActivityInfo.SizeChangesSupportMode private int supportsSizeChanges() { - if (mAppCompatController.getAppCompatResizeOverrides() - .shouldOverrideForceNonResizeApp()) { + final AppCompatResizeOverrides resizeOverrides = mAppCompatController.getResizeOverrides(); + if (resizeOverrides.shouldOverrideForceNonResizeApp()) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -8445,8 +8444,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return SIZE_CHANGES_SUPPORTED_METADATA; } - if (mAppCompatController.getAppCompatResizeOverrides() - .shouldOverrideForceResizeApp()) { + if (resizeOverrides.shouldOverrideForceResizeApp()) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -10222,7 +10220,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAppCompatController.getAppCompatOrientationOverrides() .shouldIgnoreOrientationRequestLoop()); proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP, - mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); + mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp()); proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS, mAppCompatController.getAppCompatAspectRatioOverrides() .shouldEnableUserAspectRatioSettings()); diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 4433d64f0d00..0967078deac3 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -15,23 +15,17 @@ */ package com.android.server.wm; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY; - import android.annotation.NonNull; import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; import java.io.PrintWriter; -import java.util.function.BooleanSupplier; /** * Allows the interaction with all the app compat policies and configurations */ class AppCompatController { - - @NonNull - private final ActivityRecord mActivityRecord; @NonNull private final TransparentPolicy mTransparentPolicy; @NonNull @@ -50,56 +44,28 @@ class AppCompatController { private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy; - @NonNull - final BooleanSupplier mAllowRestrictedResizability; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { - mActivityRecord = activityRecord; final PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, activityRecord.packageName); mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); mTransparentPolicy = new TransparentPolicy(activityRecord, wmService.mAppCompatConfiguration); - mAppCompatOverrides = new AppCompatOverrides(activityRecord, + mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager, wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery); mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); - mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord, + mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord, wmService.mAppCompatConfiguration); - mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord, + mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord, wmService.mAppCompatConfiguration); mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord, mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration); - mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord, + mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord, mAppCompatOverrides); - mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> { - // Application level. - if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) { - return true; - } - // Activity level. - try { - return packageManager.getPropertyAsUser( - PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, - mActivityRecord.mActivityComponent.getPackageName(), - mActivityRecord.mActivityComponent.getClassName(), - mActivityRecord.mUserId).getBoolean(); - } catch (PackageManager.NameNotFoundException e) { - return false; - } - }); - } - - static boolean allowRestrictedResizability(PackageManager pm, String packageName) { - try { - return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName) - .getBoolean(); - } catch (PackageManager.NameNotFoundException e) { - return false; - } } @NonNull @@ -138,8 +104,8 @@ class AppCompatController { } @NonNull - AppCompatResizeOverrides getAppCompatResizeOverrides() { - return mAppCompatOverrides.getAppCompatResizeOverrides(); + AppCompatResizeOverrides getResizeOverrides() { + return mAppCompatOverrides.getResizeOverrides(); } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index 2f03105846bd..58b37becc373 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; @@ -34,13 +35,14 @@ public class AppCompatOverrides { @NonNull private final AppCompatFocusOverrides mAppCompatFocusOverrides; @NonNull - private final AppCompatResizeOverrides mAppCompatResizeOverrides; + private final AppCompatResizeOverrides mResizeOverrides; @NonNull private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; @NonNull private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; AppCompatOverrides(@NonNull ActivityRecord activityRecord, + @NonNull PackageManager packageManager, @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) { @@ -55,7 +57,8 @@ public class AppCompatOverrides { mAppCompatReachabilityOverrides); mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration, optPropBuilder); - mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder); + mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager, + optPropBuilder); mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord, appCompatConfiguration); } @@ -81,8 +84,8 @@ public class AppCompatOverrides { } @NonNull - AppCompatResizeOverrides getAppCompatResizeOverrides() { - return mAppCompatResizeOverrides; + AppCompatResizeOverrides getResizeOverrides() { + return mResizeOverrides; } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java index 60c18254eca7..fa53153dd143 100644 --- a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java @@ -19,13 +19,17 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY; import static com.android.server.wm.AppCompatUtils.isChangeEnabled; import android.annotation.NonNull; +import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; +import java.util.function.BooleanSupplier; + /** * Encapsulate app compat logic about resizability. */ @@ -37,11 +41,40 @@ class AppCompatResizeOverrides { @NonNull private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp; + @NonNull + private final BooleanSupplier mAllowRestrictedResizability; + AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord, + @NonNull PackageManager packageManager, @NonNull OptPropFactory optPropBuilder) { mActivityRecord = activityRecord; mAllowForceResizeOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> { + // Application level. + if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) { + return true; + } + // Activity level. + try { + return packageManager.getPropertyAsUser( + PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, + mActivityRecord.mActivityComponent.getPackageName(), + mActivityRecord.mActivityComponent.getClassName(), + mActivityRecord.mUserId).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + }); + } + + static boolean allowRestrictedResizability(PackageManager pm, String packageName) { + try { + return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } } /** @@ -75,4 +108,9 @@ class AppCompatResizeOverrides { return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP)); } + + /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY */ + boolean allowRestrictedResizability() { + return mAllowRestrictedResizability.getAsBoolean(); + } } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 98ed6f76b2f9..54ae80cfe98a 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -103,6 +103,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // again, so that the control with leash can be eventually dispatched if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending && mControlTarget != null) { + ProtoLog.d(WM_DEBUG_IME, + "onPostLayout: IME control ready to be dispatched, ws=%s", ws); mGivenInsetsReady = true; ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); @@ -118,6 +120,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED); mStatsToken = null; } else if (wasServerVisible && !isServerVisible()) { + ProtoLog.d(WM_DEBUG_IME, "onPostLayout: setImeShowing(false) was: %s, ws=%s", + isImeShowing(), ws); setImeShowing(false); } } @@ -621,6 +625,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // request (cancelling the initial show) or hide request (aborting the initial show). logIsScheduledAndReadyToShowIme(!visible /* aborted */); } + ProtoLog.d(WM_DEBUG_IME, "receiveImeStatsToken: visible=%s", visible); if (visible) { ImeTracker.forLogging().onCancelled( mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java index ae6e72464555..e3ffe716271c 100644 --- a/services/core/java/com/android/server/wm/InputConfigAdapter.java +++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java @@ -76,9 +76,6 @@ class InputConfigAdapter { LayoutParams.FLAG_NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE, false /* inverted */), new FlagMapping( - LayoutParams.FLAG_SPLIT_TOUCH, - InputConfig.PREVENT_SPLITTING, true /* inverted */), - new FlagMapping( LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */), new FlagMapping( diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 65cf4ee733dd..379e312e58c0 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -346,6 +346,7 @@ public: void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); void setMouseReverseVerticalScrollingEnabled(bool enabled); void setMouseScrollingAccelerationEnabled(bool enabled); + void setMouseScrollingSpeed(int32_t speed); void setMouseSwapPrimaryButtonEnabled(bool enabled); void setMouseAccelerationEnabled(bool enabled); void setTouchpadPointerSpeed(int32_t speed); @@ -500,6 +501,9 @@ private: // True if mouse scrolling acceleration is enabled. bool mouseScrollingAccelerationEnabled{true}; + // The mouse scrolling speed, as a number from -7 (slowest) to 7 (fastest). + int32_t mouseScrollingSpeed{0}; + // True if mouse vertical scrolling is reversed. bool mouseReverseVerticalScrollingEnabled{false}; @@ -843,6 +847,9 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon mLocked.mouseScrollingAccelerationEnabled ? android::os::IInputConstants::DEFAULT_MOUSE_WHEEL_ACCELERATION : 1; + outConfig->wheelVelocityControlParameters.scale = mLocked.mouseScrollingAccelerationEnabled + ? 1 + : exp2f(mLocked.mouseScrollingSpeed * POINTER_SPEED_EXPONENT); outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled; outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest; @@ -1451,6 +1458,21 @@ void NativeInputManager::setMouseScrollingAccelerationEnabled(bool enabled) { InputReaderConfiguration::Change::POINTER_SPEED); } +void NativeInputManager::setMouseScrollingSpeed(int32_t speed) { + { // acquire lock + std::scoped_lock _l(mLock); + + if (mLocked.mouseScrollingSpeed == speed) { + return; + } + + mLocked.mouseScrollingSpeed = speed; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::POINTER_SPEED); +} + void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) { { // acquire lock std::scoped_lock _l(mLock); @@ -3243,6 +3265,11 @@ static void nativeSetMouseScrollingAccelerationEnabled(JNIEnv* env, jobject nati im->setMouseScrollingAccelerationEnabled(enabled); } +static void nativeSetMouseScrollingSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setMouseScrollingSpeed(speed); +} + static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj, bool enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -3319,6 +3346,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetMouseReverseVerticalScrollingEnabled}, {"setMouseScrollingAccelerationEnabled", "(Z)V", (void*)nativeSetMouseScrollingAccelerationEnabled}, + {"setMouseScrollingSpeed", "(I)V", (void*)nativeSetMouseScrollingSpeed}, {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled}, {"setMouseAccelerationEnabled", "(Z)V", (void*)nativeSetMouseAccelerationEnabled}, {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed}, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9ab9a8f44ed9..c5d42ad9f081 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1787,9 +1787,11 @@ public final class SystemServer implements Dumpable { SignedConfigService.registerUpdateReceiver(mSystemContext); t.traceEnd(); - t.traceBegin("AppIntegrityService"); - mSystemServiceManager.startService(AppIntegrityManagerService.class); - t.traceEnd(); + if (!android.server.Flags.removeAppIntegrityManagerService()) { + t.traceBegin("AppIntegrityService"); + mSystemServiceManager.startService(AppIntegrityManagerService.class); + t.traceEnd(); + } t.traceBegin("StartLogcatManager"); mSystemServiceManager.startService(LogcatManagerService.class); diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig index 0d222fb4409e..4d021ec2c0d3 100644 --- a/services/java/com/android/server/flags.aconfig +++ b/services/java/com/android/server/flags.aconfig @@ -51,4 +51,11 @@ flag { description: "Remove GameManagerService from Wear" bug: "340929737" is_fixed_read_only: true +} + +flag { + name: "remove_app_integrity_manager_service" + namespace: "package_manager_service" + description: "Remove AppIntegrityManagerService" + bug: "364200023" }
\ No newline at end of file diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp index 6c4158e60ebb..8e0eb6b14432 100644 --- a/services/robotests/Android.bp +++ b/services/robotests/Android.bp @@ -63,7 +63,6 @@ android_robolectric_test { instrumentation_for: "FrameworksServicesLib", - upstream: true, strict_mode: false, } diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp index 3ace3fb11506..95b38e56f276 100644 --- a/services/robotests/backup/Android.bp +++ b/services/robotests/backup/Android.bp @@ -66,7 +66,6 @@ android_robolectric_test { instrumentation_for: "BackupFrameworksServicesLib", - upstream: true, strict_mode: false, diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp index e6ff5068368f..da58aa1f6c66 100644 --- a/services/tests/InputMethodSystemServerTests/Android.bp +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -86,6 +86,7 @@ android_ravenwood_test { "src/com/android/server/inputmethod/**/ClientControllerTest.java", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } android_test { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index 7277fd79fdd5..66aaa562b873 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -45,6 +45,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -78,10 +79,7 @@ import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; @Presubmit @RunWith(JUnit4.class) @@ -885,18 +883,15 @@ public class AppsFilterImplTest { return null; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables( + public Pair<String, String> getTargetToOverlayables( @NonNull AndroidPackage pkg) { if (overlay.getPackageName().equals(pkg.getPackageName())) { - Map<String, Set<String>> map = new ArrayMap<>(); - Set<String> set = new ArraySet<>(); - set.add(overlay.getOverlayTargetOverlayableName()); - map.put(overlay.getOverlayTarget(), set); - return map; + return Pair.create(overlay.getOverlayTarget(), + overlay.getOverlayTargetOverlayableName()); } - return Collections.emptyMap(); + return null; } }, mMockHandler); @@ -977,18 +972,15 @@ public class AppsFilterImplTest { return null; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables( + public Pair<String, String> getTargetToOverlayables( @NonNull AndroidPackage pkg) { if (overlay.getPackageName().equals(pkg.getPackageName())) { - Map<String, Set<String>> map = new ArrayMap<>(); - Set<String> set = new ArraySet<>(); - set.add(overlay.getOverlayTargetOverlayableName()); - map.put(overlay.getOverlayTarget(), set); - return map; + return Pair.create(overlay.getOverlayTarget(), + overlay.getOverlayTargetOverlayableName()); } - return Collections.emptyMap(); + return null; } }, mMockHandler); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 9e96800ca2e9..4a09802fc822 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -258,7 +258,6 @@ public class MockingOomAdjusterTests { mService.mOomAdjuster = mService.mProcessStateController.getOomAdjuster(); mService.mOomAdjuster.mAdjSeq = 10000; mService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE); - mSetFlagsRule.enableFlags(Flags.FLAG_NEW_FGS_RESTRICTION_LOGIC); mUiTierSize = mService.mConstants.TIERED_CACHED_ADJ_UI_TIER_SIZE; mFirstNonUiCachedAdj = sFirstUiCachedAdj + mUiTierSize; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index 89b48bad2358..27eada013642 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -27,9 +29,11 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED; +import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -39,9 +43,11 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; +import android.os.Binder; import android.os.Looper; import android.os.UserHandle; @@ -179,6 +185,34 @@ public class PendingIntentControllerTest { } } + @Test + public void testClearAllowBgActivityStartsClearsToken() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER); + assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token), + pir.getBackgroundStartPrivilegesForActivitySender(token)); + pir.clearAllowBgActivityStarts(token); + assertEquals(BackgroundStartPrivileges.NONE, + pir.getBackgroundStartPrivilegesForActivitySender(token)); + } + + @Test + public void testClearAllowBgActivityStartsClearsDuration() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowlistDurationLocked(token, 1000, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, + "NotificationManagerService"); + PendingIntentRecord.TempAllowListDuration allowlistDurationLocked = + pir.getAllowlistDurationLocked(token); + assertEquals(1000, allowlistDurationLocked.duration); + pir.clearAllowBgActivityStarts(token); + PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear = + pir.getAllowlistDurationLocked(token); + assertNull(allowlistDurationLockedAfterClear); + } + private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java index 00b911bc1ae7..cd3683ba0ca8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java @@ -158,6 +158,11 @@ public class MobileRadioPowerStatsCollectorTest { public LongSupplier getPhoneSignalScanDurationSupplier() { return mScanDurationSupplier; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } }; @Before diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 4b6fcc39dcef..8a081f8e16cc 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -283,6 +283,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { protected void updateBatteryPropertiesLocked() { } + @Override + protected NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } + public static class DummyExternalStatsSync implements ExternalStatsSync { public int flags = 0; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/NetworkStatsTestUtils.java b/services/tests/powerstatstests/src/com/android/server/power/stats/NetworkStatsTestUtils.java new file mode 100644 index 000000000000..21be6546b011 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/NetworkStatsTestUtils.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.power.stats; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkStats; +import android.platform.test.ravenwood.RavenwoodRule; + +import java.util.ArrayList; +import java.util.List; + +public class NetworkStatsTestUtils { + /** + * Equivalent to NetworkStats.subtract, reimplementing the method for Ravenwood tests. + */ + @NonNull + public static NetworkStats networkStatsDelta(@NonNull NetworkStats currentStats, + @Nullable NetworkStats lastStats) { + if (!RavenwoodRule.isOnRavenwood()) { + if (lastStats == null) { + return currentStats; + } + return currentStats.subtract(lastStats); + } + + List<NetworkStats.Entry> entries = new ArrayList<>(); + for (NetworkStats.Entry entry : currentStats) { + NetworkStats.Entry lastEntry = null; + int uid = entry.getUid(); + if (lastStats != null) { + for (NetworkStats.Entry e : lastStats) { + if (e.getUid() == uid && e.getSet() == entry.getSet() + && e.getTag() == entry.getTag() + && e.getMetered() == entry.getMetered() + && e.getRoaming() == entry.getRoaming() + && e.getDefaultNetwork() == entry.getDefaultNetwork() + /*&& Objects.equals(e.getIface(), entry.getIface())*/) { + lastEntry = e; + break; + } + } + } + long rxBytes, rxPackets, txBytes, txPackets; + if (lastEntry != null) { + rxBytes = Math.max(0, entry.getRxBytes() - lastEntry.getRxBytes()); + rxPackets = Math.max(0, entry.getRxPackets() - lastEntry.getRxPackets()); + txBytes = Math.max(0, entry.getTxBytes() - lastEntry.getTxBytes()); + txPackets = Math.max(0, entry.getTxPackets() - lastEntry.getTxPackets()); + } else { + rxBytes = entry.getRxBytes(); + rxPackets = entry.getRxPackets(); + txBytes = entry.getTxBytes(); + txPackets = entry.getTxPackets(); + } + + NetworkStats.Entry uidEntry = mock(NetworkStats.Entry.class); + when(uidEntry.getUid()).thenReturn(uid); + when(uidEntry.getRxBytes()).thenReturn(rxBytes); + when(uidEntry.getRxPackets()).thenReturn(rxPackets); + when(uidEntry.getTxBytes()).thenReturn(txBytes); + when(uidEntry.getTxPackets()).thenReturn(txPackets); + + entries.add(uidEntry); + } + NetworkStats delta = mock(NetworkStats.class); + when(delta.iterator()).thenAnswer(inv -> entries.iterator()); + return delta; + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java index 8b5e6ee9cf89..a26b2c955380 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java @@ -168,6 +168,11 @@ public class WifiPowerStatsCollectorTest { public WifiManager getWifiManager() { return mWifiManager; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } }; @Before diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java index 4ed44a0563e3..6acd36885b1e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/MobileRadioPowerStatsProcessorTest.java @@ -56,6 +56,7 @@ import com.android.internal.os.Clock; import com.android.internal.os.PowerStats; import com.android.server.power.stats.BatteryUsageStatsRule; import com.android.server.power.stats.MobileRadioPowerStatsCollector; +import com.android.server.power.stats.NetworkStatsTestUtils; import com.android.server.power.stats.PowerStatsCollector; import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.format.MobileRadioPowerStatsLayout; @@ -152,6 +153,11 @@ public class MobileRadioPowerStatsProcessorTest { public LongSupplier getPhoneSignalScanDurationSupplier() { return mScanDurationSupplier; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } }; @Before diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java index 535f2da603b8..a20274fb5ded 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PhoneCallPowerStatsProcessorTest.java @@ -43,6 +43,7 @@ import android.telephony.TelephonyManager; import com.android.internal.os.Clock; import com.android.server.power.stats.BatteryUsageStatsRule; import com.android.server.power.stats.MobileRadioPowerStatsCollector; +import com.android.server.power.stats.NetworkStatsTestUtils; import com.android.server.power.stats.PowerStatsCollector; import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.format.PowerStatsLayout; @@ -135,6 +136,11 @@ public class PhoneCallPowerStatsProcessorTest { public LongSupplier getPhoneSignalScanDurationSupplier() { return mScanDurationSupplier; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } }; @Before diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java index 1e097692f55e..bd92a84fc815 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/WifiPowerStatsProcessorTest.java @@ -56,6 +56,7 @@ import com.android.internal.os.Clock; import com.android.internal.os.PowerProfile; import com.android.server.power.stats.BatteryUsageStatsRule; import com.android.server.power.stats.MockBatteryStatsImpl; +import com.android.server.power.stats.NetworkStatsTestUtils; import com.android.server.power.stats.PowerStatsCollector; import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.WifiPowerStatsCollector; @@ -178,6 +179,11 @@ public class WifiPowerStatsProcessorTest { public WifiStatsRetriever getWifiStatsRetriever() { return mWifiStatsRetriever; } + + @Override + public NetworkStats networkStatsDelta(NetworkStats stats, NetworkStats oldStats) { + return NetworkStatsTestUtils.networkStatsDelta(stats, oldStats); + } }; @Before diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 82efae45e1a4..92c6db5b7b96 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -21,6 +21,9 @@ import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_VIA_SYS import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap; import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_DISABLED_MODE; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_MULTI_TARGET_MODE; import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER; import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS; @@ -163,7 +166,7 @@ public class GestureLauncherServiceTest { new GestureLauncherService( mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger); - withDoubleTapPowerGestureEnableSettingValue(true); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); } @@ -215,68 +218,117 @@ public class GestureLauncherServiceTest { } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingDisabled() { + withDoubleTapPowerModeConfigValue( + DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( - mContext, FAKE_USER_ID)); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(0); - assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( - mContext, FAKE_USER_ID)); - } + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingDisabled() { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(1); - } + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - } + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingEnabled() { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(0); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingDisabled() { + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingEnabled() { + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(0); + assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE); + withCameraDoubleTapPowerDisableSettingValue(0); + + assertTrue( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingDisabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( @@ -287,8 +339,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertTrue( @@ -299,11 +351,11 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); - assertTrue( + assertFalse( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @@ -311,8 +363,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(false); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( @@ -323,8 +375,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse( @@ -449,13 +501,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -498,13 +544,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -549,9 +589,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1031,9 +1069,7 @@ public class GestureLauncherServiceTest { public void testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() { // Enable camera double tap gesture - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + enableCameraGesture(); // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); @@ -1220,10 +1256,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_longpress() { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); - withUserSetupCompleteValue(true); + enableCameraGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1400,13 +1433,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1449,9 +1476,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1495,9 +1520,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1630,9 +1653,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + enableCameraGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1823,12 +1844,13 @@ public class GestureLauncherServiceTest { .thenReturn(enableConfigValue); } - private void withDoubleTapPowerEnabledConfigValue(boolean enable) { - when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled)) - .thenReturn(enable); + private void withDoubleTapPowerModeConfigValue( + int modeConfigValue) { + when(mResources.getInteger(com.android.internal.R.integer.config_doubleTapPowerGestureMode)) + .thenReturn(modeConfigValue); } - private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) { + private void withMultiTargetDoubleTapPowerGestureEnableSettingValue(boolean enable) { Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED, @@ -1910,8 +1932,8 @@ public class GestureLauncherServiceTest { private void enableWalletGesture() { withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); - withDoubleTapPowerGestureEnableSettingValue(true); - withDoubleTapPowerEnabledConfigValue(true); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); mGestureLauncherService.updateWalletDoubleTapPowerEnabled(); withUserSetupCompleteValue(true); @@ -1926,8 +1948,9 @@ public class GestureLauncherServiceTest { private void enableCameraGesture() { if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue( + DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); } else { withCameraDoubleTapPowerEnableConfigValue(true); @@ -1937,6 +1960,18 @@ public class GestureLauncherServiceTest { withUserSetupCompleteValue(true); } + private void disableDoubleTapPowerGesture() { + if (launchWalletOptionOnPowerDoubleTap()) { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + } else { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(1); + } + mGestureLauncherService.updateWalletDoubleTapPowerEnabled(); + withUserSetupCompleteValue(true); + } + private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues( long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) { KeyEvent keyEvent = diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java new file mode 100644 index 000000000000..84713079c9d3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.app.AppOpsManager; +import android.content.Context; +import android.os.Process; +import android.util.ArraySet; +import android.util.Log; +import android.util.LongSparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.appop.DiscreteOpsSqlRegistry.DiscreteOp; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class DiscreteAppOpSqlPersistenceTest { + private static final String DATABASE_NAME = "test_app_ops.db"; + private DiscreteOpsSqlRegistry mDiscreteRegistry; + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + @Before + public void setUp() { + mDiscreteRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + mDiscreteRegistry.systemReady(); + } + + @After + public void cleanUp() { + mContext.deleteDatabase(DATABASE_NAME); + } + + @Test + public void discreteOpEventIsRecorded() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getCachedDiscreteOps(); + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void discreteOpEventIsPersistedToDisk() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + assertThat(mDiscreteRegistry.getCachedDiscreteOps()).isEmpty(); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void discreteOpEventInSameMinuteIsNotRecorded() { + long oneMinuteMillis = Duration.ofMinutes(1).toMillis(); + // round timestamp at minute level and add 5 seconds + long accessTime = System.currentTimeMillis() / oneMinuteMillis * oneMinuteMillis + 5000; + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).setAccessTime(accessTime).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + // create duplicate event in same minute, with added 30 seconds + DiscreteOp opEvent2 = + new DiscreteOpBuilder(mContext).setAccessTime(accessTime + 30000).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void multipleDiscreteOpEventAreRecorded() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setPackageName( + "test.package").build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(discreteOps).contains(opEvent); + assertThat(discreteOps).contains(opEvent2); + assertThat(discreteOps.size()).isEqualTo(2); + } + + @Test + public void clearDiscreteOps() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setUid(12345).setPackageName( + "abc").build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + mDiscreteRegistry.clearHistory(); + assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty(); + } + + @Test + public void clearDiscreteOpsForPackage() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(new DiscreteOpBuilder(mContext).build()); + mDiscreteRegistry.clearHistory(Process.myUid(), mContext.getPackageName()); + + assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty(); + } + + @Test + public void offsetDiscreteOps() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + long event2AccessTime = System.currentTimeMillis() - 300000; + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setAccessTime( + event2AccessTime).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + long offset = Duration.ofMinutes(2).toMillis(); + + mDiscreteRegistry.offsetHistory(offset); + + // adjust input for assertion + DiscreteOp e1 = new DiscreteOpBuilder(opEvent) + .setAccessTime(opEvent.getAccessTime() - offset).build(); + DiscreteOp e2 = new DiscreteOpBuilder(opEvent2) + .setAccessTime(event2AccessTime - offset).build(); + + List<DiscreteOp> results = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(results.size()).isEqualTo(2); + assertThat(results).contains(e1); + assertThat(results).contains(e2); + } + + @Test + public void completeAttributionChain() { + long chainId = 100; + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .setChainId(chainId) + .setAttributionFlags(ATTRIBUTION_FLAG_RECEIVER | ATTRIBUTION_FLAG_TRUSTED) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setChainId(chainId) + .setAttributionFlags(ATTRIBUTION_FLAG_ACCESSOR | ATTRIBUTION_FLAG_TRUSTED) + .build(); + List<DiscreteOp> events = new ArrayList<>(); + events.add(event1); + events.add(event2); + + LongSparseArray<DiscreteOpsSqlRegistry.AttributionChain> chains = + mDiscreteRegistry.createAttributionChains(events, new ArraySet<>()); + + assertThat(chains.size()).isGreaterThan(0); + DiscreteOpsSqlRegistry.AttributionChain chain = chains.get(chainId); + assertThat(chain).isNotNull(); + assertThat(chain.isComplete()).isTrue(); + assertThat(chain.getStart()).isEqualTo(event1); + assertThat(chain.getLastVisible()).isEqualTo(event2); + } + + @Test + public void addToHistoricalOps() { + long beginTimeMillis = System.currentTimeMillis(); + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setUid(123457) + .build(); + mDiscreteRegistry.recordDiscreteAccess(event1); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(event2); + + long endTimeMillis = System.currentTimeMillis() + 500; + AppOpsManager.HistoricalOps results = new AppOpsManager.HistoricalOps(beginTimeMillis, + endTimeMillis); + + mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(results, beginTimeMillis, + endTimeMillis, 0, 0, null, null, null, 0, new ArraySet<>()); + Log.i("Manjeet", "TEST read " + results); + assertWithMessage("results shouldn't be empty").that(results.isEmpty()).isFalse(); + } + + @Test + public void dump() { + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .setAccessTime(1732221340628L) + .setUid(12345) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setAccessTime(1732227340628L) + .setUid(123457) + .build(); + mDiscreteRegistry.recordDiscreteAccess(event1); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(event2); + } + + /** This clears in-memory cache and push records into the database. */ + private void flushDiscreteOpsToDatabase() { + mDiscreteRegistry.writeAndClearOldAccessHistory(); + } + + /** + * Creates default op event for CAMERA app op with current time as access time + * and 1 minute duration + */ + private static class DiscreteOpBuilder { + private int mUid; + private String mPackageName; + private String mAttributionTag; + private String mDeviceId; + private int mOpCode; + private int mOpFlags; + private int mAttributionFlags; + private int mUidState; + private long mChainId; + private long mAccessTime; + private long mDuration; + + DiscreteOpBuilder(Context context) { + mUid = Process.myUid(); + mPackageName = context.getPackageName(); + mAttributionTag = null; + mDeviceId = String.valueOf(context.getDeviceId()); + mOpCode = AppOpsManager.OP_CAMERA; + mOpFlags = AppOpsManager.OP_FLAG_SELF; + mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR; + mUidState = UID_STATE_FOREGROUND; + mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; + mAccessTime = System.currentTimeMillis(); + mDuration = Duration.ofMinutes(1).toMillis(); + } + + DiscreteOpBuilder(DiscreteOp discreteOp) { + this.mUid = discreteOp.getUid(); + this.mPackageName = discreteOp.getPackageName(); + this.mAttributionTag = discreteOp.getAttributionTag(); + this.mDeviceId = discreteOp.getDeviceId(); + this.mOpCode = discreteOp.getOpCode(); + this.mOpFlags = discreteOp.getOpFlags(); + this.mAttributionFlags = discreteOp.getAttributionFlags(); + this.mUidState = discreteOp.getUidState(); + this.mChainId = discreteOp.getChainId(); + this.mAccessTime = discreteOp.getAccessTime(); + this.mDuration = discreteOp.getDuration(); + } + + public DiscreteOpBuilder setUid(int uid) { + this.mUid = uid; + return this; + } + + public DiscreteOpBuilder setPackageName(String packageName) { + this.mPackageName = packageName; + return this; + } + + public DiscreteOpBuilder setAttributionFlags(int attributionFlags) { + this.mAttributionFlags = attributionFlags; + return this; + } + + public DiscreteOpBuilder setChainId(long chainId) { + this.mChainId = chainId; + return this; + } + + public DiscreteOpBuilder setAccessTime(long accessTime) { + this.mAccessTime = accessTime; + return this; + } + + public DiscreteOp build() { + return new DiscreteOp(mUid, mPackageName, mAttributionTag, mDeviceId, mOpCode, mOpFlags, + mAttributionFlags, mUidState, mChainId, mAccessTime, mDuration); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java index 2ff0c6288ece..ae973be17904 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java @@ -47,9 +47,12 @@ import org.junit.runner.RunWith; import java.io.File; import java.util.List; +/** + * Test xml persistence implementation for discrete ops. + */ @RunWith(AndroidJUnit4.class) -public class DiscreteAppOpPersistenceTest { - private DiscreteRegistry mDiscreteRegistry; +public class DiscreteAppOpXmlPersistenceTest { + private DiscreteOpsXmlRegistry mDiscreteRegistry; private final Object mLock = new Object(); private File mMockDataDirectory; private final Context mContext = @@ -61,13 +64,13 @@ public class DiscreteAppOpPersistenceTest { @Before public void setUp() { mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); - mDiscreteRegistry = new DiscreteRegistry(mLock, mMockDataDirectory); + mDiscreteRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); mDiscreteRegistry.systemReady(); } @After public void cleanUp() { - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); FileUtils.deleteContents(mMockDataDirectory); } @@ -87,14 +90,14 @@ public class DiscreteAppOpPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteRegistry.ACCESS_TYPE_FINISH_OP); + DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP); // Verify in-memory object is correct fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); // Write to disk and clear the in-memory object - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); // Verify the storage file is created and then verify its content is correct File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory); @@ -119,12 +122,12 @@ public class DiscreteAppOpPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteRegistry.ACCESS_TYPE_START_OP); + DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP); fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory); assertThat(files.length).isEqualTo(1); @@ -136,30 +139,31 @@ public class DiscreteAppOpPersistenceTest { int expectedOp, String expectedDeviceId, String expectedAttrTag, long expectedAccessTime, long expectedAccessDuration, int expectedUidState, int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) { - DiscreteRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + DiscreteOpsXmlRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps(); assertThat(discreteOps.isEmpty()).isFalse(); assertThat(discreteOps.mUids.size()).isEqualTo(1); - DiscreteRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid); + DiscreteOpsXmlRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid); assertThat(discreteUidOps.mPackages.size()).isEqualTo(1); - DiscreteRegistry.DiscretePackageOps discretePackageOps = + DiscreteOpsXmlRegistry.DiscretePackageOps discretePackageOps = discreteUidOps.mPackages.get(expectedPackageName); assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1); - DiscreteRegistry.DiscreteOp discreteOp = discretePackageOps.mPackageOps.get(expectedOp); + DiscreteOpsXmlRegistry.DiscreteOp discreteOp = + discretePackageOps.mPackageOps.get(expectedOp); assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1); - DiscreteRegistry.DiscreteDeviceOp discreteDeviceOp = + DiscreteOpsXmlRegistry.DiscreteDeviceOp discreteDeviceOp = discreteOp.mDeviceAttributedOps.get(expectedDeviceId); assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1); - List<DiscreteRegistry.DiscreteOpEvent> discreteOpEvents = + List<DiscreteOpsXmlRegistry.DiscreteOpEvent> discreteOpEvents = discreteDeviceOp.mAttributedOps.get(expectedAttrTag); assertThat(discreteOpEvents.size()).isEqualTo(1); - DiscreteRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0); + DiscreteOpsXmlRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0); assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime); assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration); assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState); diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java new file mode 100644 index 000000000000..21cc3bac3938 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AppOpsManager; +import android.companion.virtual.VirtualDeviceManager; +import android.content.Context; +import android.os.FileUtils; +import android.os.Process; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class DiscreteOpsMigrationAndRollbackTest { + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private static final String DATABASE_NAME = "test_app_ops.db"; + private static final int RECORD_COUNT = 500; + private final File mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); + final Object mLock = new Object(); + + @After + @Before + public void clean() { + mContext.deleteDatabase(DATABASE_NAME); + FileUtils.deleteContents(mMockDataDirectory); + } + + @Test + public void migrateFromXmlToSqlite() { + // write records to xml registry + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); + xmlRegistry.systemReady(); + for (int i = 1; i <= RECORD_COUNT; i++) { + DiscreteOpsSqlRegistry.DiscreteOp opEvent = + new DiscreteOpBuilder(mContext) + .setChainId(i) + .setUid(10000 + i) // make all records unique + .build(); + xmlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(), + opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), + opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), + opEvent.getDuration(), opEvent.getAttributionFlags(), + (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + } + xmlRegistry.writeAndClearOldAccessHistory(); + assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT); + assertThat(xmlRegistry.getAllDiscreteOps().mUids.size()).isEqualTo(RECORD_COUNT); + + // migration to sql registry + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + sqlRegistry.systemReady(); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); + List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps(); + + assertThat(xmlRegistry.getAllDiscreteOps().mUids).isEmpty(); + assertThat(sqlOps.size()).isEqualTo(RECORD_COUNT); + assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); + } + + @Test + public void migrateFromSqliteToXml() { + // write to sql registry + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + sqlRegistry.systemReady(); + for (int i = 1; i <= RECORD_COUNT; i++) { + DiscreteOpsSqlRegistry.DiscreteOp opEvent = + new DiscreteOpBuilder(mContext) + .setChainId(i) + .setUid(RECORD_COUNT + i) // make all records unique + .build(); + sqlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(), + opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), + opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), + opEvent.getDuration(), opEvent.getAttributionFlags(), + (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + } + sqlRegistry.writeAndClearOldAccessHistory(); + assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT); + assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); + + // migration to xml registry + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); + xmlRegistry.systemReady(); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps(); + + assertThat(sqlRegistry.getAllDiscreteOps()).isEmpty(); + assertThat(xmlOps.mLargestChainId).isEqualTo(RECORD_COUNT); + assertThat(xmlOps.mUids.size()).isEqualTo(RECORD_COUNT); + } + + private static class DiscreteOpBuilder { + private int mUid; + private String mPackageName; + private String mAttributionTag; + private String mDeviceId; + private int mOpCode; + private int mOpFlags; + private int mAttributionFlags; + private int mUidState; + private int mChainId; + private long mAccessTime; + private long mDuration; + + DiscreteOpBuilder(Context context) { + mUid = Process.myUid(); + mPackageName = context.getPackageName(); + mAttributionTag = null; + mDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + mOpCode = AppOpsManager.OP_CAMERA; + mOpFlags = AppOpsManager.OP_FLAG_SELF; + mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR; + mUidState = UID_STATE_FOREGROUND; + mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; + mAccessTime = System.currentTimeMillis(); + mDuration = Duration.ofMinutes(1).toMillis(); + } + + public DiscreteOpBuilder setUid(int uid) { + this.mUid = uid; + return this; + } + + public DiscreteOpBuilder setChainId(int chainId) { + this.mChainId = chainId; + return this; + } + + public DiscreteOpsSqlRegistry.DiscreteOp build() { + return new DiscreteOpsSqlRegistry.DiscreteOp(mUid, mPackageName, mAttributionTag, + mDeviceId, + mOpCode, mOpFlags, mAttributionFlags, mUidState, mChainId, mAccessTime, + mDuration); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 02441648a589..4f551119b42e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -887,6 +887,21 @@ public class HdmiCecLocalDeviceAudioSystemTest { systemAudioModeRequest_fromAudioSystem); } + @Test + public void addAndStartAction_remove() throws Exception { + // utilize callback test to test if addAndStartAction(action, remove) + TestCallback callback = new TestCallback(); + + mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); + mHdmiCecLocalDeviceAudioSystem.addAndStartAction( + new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback), + true); + + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem.getActions( + ArcTerminationActionFromAvr.class).size()).isEqualTo(1); + } + private static class TestCallback extends IHdmiControlCallback.Stub { private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index 1352adef783f..ad6e467efeef 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -76,12 +76,10 @@ class OverlayReferenceMapperTests { val overlay1 = mockOverlay(1) mapper = mapper( overlayToTargetToOverlayables = mapOf( - overlay0.packageName to mapOf( - target.packageName to target.overlayables.keys - ), - overlay1.packageName to mapOf( - target.packageName to target.overlayables.keys - ) + overlay0.packageName to android.util.Pair(target.packageName, + target.overlayables.keys.first()), + overlay1.packageName to android.util.Pair(target.packageName, + target.overlayables.keys.first()) ) ) val existing = mapper.addInOrder(overlay0, overlay1) { @@ -134,42 +132,38 @@ class OverlayReferenceMapperTests { } @Test - fun overlayWithMultipleTargets() { - val target0 = mockTarget(0) - val target1 = mockTarget(1) + fun overlayWithoutTarget() { val overlay = mockOverlay() - mapper = mapper( - overlayToTargetToOverlayables = mapOf( - overlay.packageName to mapOf( - target0.packageName to target0.overlayables.keys, - target1.packageName to target1.overlayables.keys - ) - ) - ) - mapper.addInOrder(target0, target1, overlay) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) - } - assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay)) - mapper.remove(target0) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + mapper.addInOrder(overlay) { + assertThat(it).isEmpty() } - assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay)) - mapper.remove(target1) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + // An overlay can only have visibility exposed through its target + assertEmpty() + mapper.remove(overlay) { + assertThat(it).isEmpty() } assertEmpty() } @Test - fun overlayWithoutTarget() { + fun targetWithNullOverlayable() { + val target = mockTarget() val overlay = mockOverlay() - mapper.addInOrder(overlay) { + mapper = mapper( + overlayToTargetToOverlayables = mapOf( + overlay.packageName to android.util.Pair(target.packageName, null) + ) + ) + val existing = mapper.addInOrder(overlay) { assertThat(it).isEmpty() } - // An overlay can only have visibility exposed through its target assertEmpty() - mapper.remove(overlay) { - assertThat(it).isEmpty() + mapper.addInOrder(target, existing = existing) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } + assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) + mapper.remove(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) } assertEmpty() } @@ -219,17 +213,15 @@ class OverlayReferenceMapperTests { namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run { mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME)) }, - overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf( - mockOverlay().packageName to mapOf( - mockTarget().run { packageName to overlayables.keys } - ) - ) + overlayToTargetToOverlayables: Map<String, android.util.Pair<String, String>> = mapOf( + mockOverlay().packageName to mockTarget().run { android.util.Pair(packageName!!, + overlayables.keys.first()) }) ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider { override fun getActorPkg(actor: String) = OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first override fun getTargetToOverlayables(pkg: AndroidPackage) = - overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap() + overlayToTargetToOverlayables[pkg.packageName] }) private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> { diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 4e030d499c25..3ef360a752f6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -112,6 +112,7 @@ import org.mockito.stubbing.Answer; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; @@ -556,6 +557,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectFinishWrite(@NonNull ResilientAtomicFile file, + @NonNull FileOutputStream os) throws IOException { + file.finishWrite(os, false /* doFsVerity */); + } + + @Override void wtf(String message, Throwable th) { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index c01283a236c4..0d86d4c3fa28 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -159,7 +159,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for the first launch path, no settings file available. */ - public void FirstInitialize() { + public void testFirstInitialize() { assertResetTimes(START_TIME, START_TIME + INTERVAL); } @@ -167,7 +167,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * Test for {@link ShortcutService#getLastResetTimeLocked()} and * {@link ShortcutService#getNextResetTimeLocked()}. */ - public void UpdateAndGetNextResetTimeLocked() { + public void testUpdateAndGetNextResetTimeLocked() { assertResetTimes(START_TIME, START_TIME + INTERVAL); // Advance clock. @@ -196,7 +196,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for the restoration from saved file. */ - public void InitializeFromSavedFile() { + public void testInitializeFromSavedFile() { mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50; assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); @@ -220,7 +220,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Add various broken cases. } - public void LoadConfig() { + public void testLoadConfig() { mService.updateConfigurationLocked( ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123," + ConfigConstants.KEY_MAX_SHORTCUTS + "=4," @@ -261,22 +261,22 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for app side APIs === /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */ - public void GetMaxDynamicShortcutCount() { + public void testGetMaxDynamicShortcutCount() { assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity()); } /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */ - public void GetRemainingCallCount() { + public void testGetRemainingCallCount() { assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount()); } - public void GetIconMaxDimensions() { + public void testGetIconMaxDimensions() { assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth()); assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight()); } /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */ - public void GetRateLimitResetTime() { + public void testGetRateLimitResetTime() { assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50; @@ -284,7 +284,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime()); } - public void SetDynamicShortcuts() { + public void testSetDynamicShortcuts() { setCaller(CALLING_PACKAGE_1, USER_10); final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); @@ -354,7 +354,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void AddDynamicShortcuts() { + public void testAddDynamicShortcuts() { setCaller(CALLING_PACKAGE_1, USER_10); final ShortcutInfo si1 = makeShortcut("shortcut1"); @@ -402,7 +402,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PushDynamicShortcut() { + public void testPushDynamicShortcut() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); @@ -544,7 +544,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10)); } - public void PushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() throws InterruptedException { mService.updateConfigurationLocked( ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); @@ -576,6 +576,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { Mockito.reset(mMockUsageStatsManagerInternal); for (int i = 2; i <= 10; i++) { final ShortcutInfo si = makeShortcut("s" + i); + setCaller(CALLING_PACKAGE_2, USER_10); mManager.pushDynamicShortcut(si); } verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( @@ -595,7 +596,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_2), any(), eq(USER_10)); } - public void UnlimitedCalls() { + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_10); final ShortcutInfo si1 = makeShortcut("shortcut1"); @@ -626,7 +627,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(3, mManager.getRemainingCallCount()); } - public void PublishWithNoActivity() { + public void testPublishWithNoActivity() { // If activity is not explicitly set, use the default one. mRunningUsers.put(USER_11, true); @@ -732,7 +733,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PublishWithNoActivity_noMainActivityInPackage() { + public void testPublishWithNoActivity_noMainActivityInPackage() { mRunningUsers.put(USER_11, true); runWithCaller(CALLING_PACKAGE_2, USER_11, () -> { @@ -751,7 +752,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void DeleteDynamicShortcuts() { + public void testDeleteDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -792,7 +793,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(2, mManager.getRemainingCallCount()); } - public void DeleteAllDynamicShortcuts() { + public void testDeleteAllDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -821,7 +822,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(1, mManager.getRemainingCallCount()); } - public void Icons() throws IOException { + public void testIcons() throws IOException { final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512); @@ -1035,7 +1036,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { */ } - public void CleanupDanglingBitmaps() throws Exception { + public void testCleanupDanglingBitmaps() throws Exception { assertBitmapDirectories(USER_10, EMPTY_STRINGS); assertBitmapDirectories(USER_11, EMPTY_STRINGS); @@ -1204,7 +1205,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { maxSize)); } - public void ShrinkBitmap() { + public void testShrinkBitmap() { checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32); checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511); checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512); @@ -1227,7 +1228,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { return out.getFile(); } - public void OpenIconFileForWrite() throws IOException { + public void testOpenIconFileForWrite() throws IOException { mInjectedCurrentTimeMillis = 1000; final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1); @@ -1301,7 +1302,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(p11_1_3.getName().contains("_")); } - public void UpdateShortcuts() { + public void testUpdateShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), @@ -1432,7 +1433,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void UpdateShortcuts_icons() { + public void testUpdateShortcuts_icons() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1") @@ -1526,7 +1527,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ShortcutManagerGetShortcuts_shortcutTypes() { + public void testShortcutManagerGetShortcuts_shortcutTypes() { // Create 3 manifest and 3 dynamic shortcuts addManifestShortcutResource( @@ -1617,7 +1618,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2"); } - public void CachedShortcuts() { + public void testCachedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), @@ -1701,7 +1702,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); } - public void CachedShortcuts_accessShortcutsPermission() { + public void testCachedShortcuts_accessShortcutsPermission() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), @@ -1743,7 +1744,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3"); } - public void CachedShortcuts_canPassShortcutLimit() { + public void testCachedShortcuts_canPassShortcutLimit() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4"); @@ -1781,7 +1782,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for launcher side APIs === - public void GetShortcuts() { + public void testGetShortcuts() { // Set up shortcuts. @@ -1998,7 +1999,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s1", "s3"); } - public void GetShortcuts_shortcutKinds() throws Exception { + public void testGetShortcuts_shortcutKinds() throws Exception { // Create 3 manifest and 3 dynamic shortcuts addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -2109,7 +2110,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void GetShortcuts_resolveStrings() throws Exception { + public void testGetShortcuts_resolveStrings() throws Exception { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { ShortcutInfo si = new ShortcutInfo.Builder(mClientContext) .setId("id") @@ -2157,7 +2158,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void GetShortcuts_personsFlag() { + public void testGetShortcuts_personsFlag() { ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id") .setShortLabel("label") .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) @@ -2205,7 +2206,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } // TODO resource - public void GetShortcutInfo() { + public void testGetShortcutInfo() { // Create shortcuts. setCaller(CALLING_PACKAGE_1); final ShortcutInfo s1_1 = makeShortcut( @@ -2280,7 +2281,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals("ABC", findById(list, "s1").getTitle()); } - public void PinShortcutAndGetPinnedShortcuts() { + public void testPinShortcutAndGetPinnedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); @@ -2361,7 +2362,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * This is similar to the above test, except it used "disable" instead of "remove". It also * does "enable". */ - public void DisableAndEnableShortcuts() { + public void testDisableAndEnableShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); @@ -2486,7 +2487,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void DisableShortcuts_thenRepublish() { + public void testDisableShortcuts_thenRepublish() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); @@ -2556,7 +2557,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_multi() { + public void testPinShortcutAndGetPinnedShortcuts_multi() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -2832,7 +2833,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_assistant() { + public void testPinShortcutAndGetPinnedShortcuts_assistant() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -2888,7 +2889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() { + public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -3477,7 +3478,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void StartShortcut() { + public void testStartShortcut() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcut( @@ -3612,7 +3613,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check extra, etc } - public void LauncherCallback() throws Throwable { + public void testLauncherCallback() throws Throwable { // Disable throttling for this test. mService.updateConfigurationLocked( ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999," @@ -3778,7 +3779,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .isEmpty(); } - public void LauncherCallback_crossProfile() throws Throwable { + public void testLauncherCallback_crossProfile() throws Throwable { prepareCrossProfileDataSet(); final Handler h = new Handler(Looper.getMainLooper()); @@ -3901,7 +3902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for persisting === - public void SaveAndLoadUser_empty() { + public void testSaveAndLoadUser_empty() { assertTrue(mManager.setDynamicShortcuts(list())); Log.i(TAG, "Saved state"); @@ -3918,7 +3919,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Try save and load, also stop/start the user. */ - public void SaveAndLoadUser() { + public void testSaveAndLoadUser() { // First, create some shortcuts and save. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); @@ -4059,7 +4060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } - public void LoadCorruptedShortcuts() throws Exception { + public void testLoadCorruptedShortcuts() throws Exception { initService(); addPackage("com.android.chrome", 0, 0); @@ -4073,7 +4074,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false)); } - public void SaveCorruptAndLoadUser() throws Exception { + public void testSaveCorruptAndLoadUser() throws Exception { // First, create some shortcuts and save. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); @@ -4229,7 +4230,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } - public void CleanupPackage() { + public void testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s0_1")))); @@ -4506,7 +4507,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.saveDirtyInfo(); } - public void CleanupPackage_republishManifests() { + public void testCleanupPackage_republishManifests() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_2); @@ -4574,7 +4575,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandleGonePackage_crossProfile() { + public void testHandleGonePackage_crossProfile() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -4846,7 +4847,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(expected, spi.canRestoreTo(mService, pi, true)); } - public void CanRestoreTo() { + public void testCanRestoreTo() { addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2"); addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1"); @@ -4909,7 +4910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1"); } - public void HandlePackageDelete() { + public void testHandlePackageDelete() { checkHandlePackageDeleteInner((userId, packageName) -> { uninstallPackage(userId, packageName); mService.mPackageMonitor.onReceive(getTestContext(), @@ -4917,7 +4918,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageDisable() { + public void testHandlePackageDisable() { checkHandlePackageDeleteInner((userId, packageName) -> { disablePackage(userId, packageName); mService.mPackageMonitor.onReceive(getTestContext(), @@ -5049,7 +5050,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } /** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */ - public void HandlePackageClearData() { + public void testHandlePackageClearData() { final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_32x32)); setCaller(CALLING_PACKAGE_1, USER_10); @@ -5125,7 +5126,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_11)); } - public void HandlePackageClearData_manifestRepublished() { + public void testHandlePackageClearData_manifestRepublished() { mRunningUsers.put(USER_11, true); @@ -5167,7 +5168,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate() throws Throwable { + public void testHandlePackageUpdate() throws Throwable { // Set up shortcuts and launchers. final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -5341,7 +5342,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test the case where an updated app has resource IDs changed. */ - public void HandlePackageUpdate_resIdChanged() throws Exception { + public void testHandlePackageUpdate_resIdChanged() throws Exception { final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000); final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001); @@ -5416,7 +5417,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate_systemAppUpdate() { + public void testHandlePackageUpdate_systemAppUpdate() { // Package1 is a system app. Package 2 is not a system app, so it's not scanned // in this test at all. @@ -5522,7 +5523,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.getUserShortcutsLocked(USER_10).getLastAppScanOsFingerprint()); } - public void HandlePackageChanged() { + public void testHandlePackageChanged() { final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1"); final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2"); @@ -5652,7 +5653,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate_activityNoLongerMain() throws Throwable { + public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcutWithActivity("s1a", @@ -5738,7 +5739,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * - Unpinned dynamic shortcuts * - Bitmaps */ - public void BackupAndRestore() { + public void testBackupAndRestore() { assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt"); assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml"); @@ -5759,7 +5760,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_backupRestoreTwice() { + public void testBackupAndRestore_backupRestoreTwice() { prepareForBackupTest(); checkBackupAndRestore_success(/*firstRestore=*/ true); @@ -5775,7 +5776,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ false); } - public void BackupAndRestore_restoreToNewVersion() { + public void testBackupAndRestore_restoreToNewVersion() { prepareForBackupTest(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2); @@ -5784,7 +5785,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_restoreToSuperSetSignatures() { + public void testBackupAndRestore_restoreToSuperSetSignatures() { prepareForBackupTest(); // Change package signatures. @@ -5981,7 +5982,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_publisherWrongSignature() { + public void testBackupAndRestore_publisherWrongSignature() { prepareForBackupTest(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature @@ -5989,7 +5990,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH); } - public void BackupAndRestore_publisherNoLongerBackupTarget() { + public void testBackupAndRestore_publisherNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(CALLING_PACKAGE_1, @@ -6118,7 +6119,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_launcherLowerVersion() { + public void testBackupAndRestore_launcherLowerVersion() { prepareForBackupTest(); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version @@ -6127,7 +6128,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_launcherWrongSignature() { + public void testBackupAndRestore_launcherWrongSignature() { prepareForBackupTest(); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature @@ -6135,7 +6136,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_launcherNotRestored(true); } - public void BackupAndRestore_launcherNoLongerBackupTarget() { + public void testBackupAndRestore_launcherNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(LAUNCHER_1, @@ -6240,7 +6241,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_launcherAndPackageNoLongerBackupTarget() { + public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(CALLING_PACKAGE_1, @@ -6338,7 +6339,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_disabled() { + public void testBackupAndRestore_disabled() { prepareCrossProfileDataSet(); // Before doing backup & restore, disable s1. @@ -6403,7 +6404,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void BackupAndRestore_manifestRePublished() { + public void testBackupAndRestore_manifestRePublished() { // Publish two manifest shortcuts. addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -6494,7 +6495,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * logcat. * - if it has allowBackup=false, we don't touch any of the existing shortcuts. */ - public void BackupAndRestore_appAlreadyInstalledWhenRestored() { + public void testBackupAndRestore_appAlreadyInstalledWhenRestored() { // Pre-backup. Same as testBackupAndRestore_manifestRePublished(). // Publish two manifest shortcuts. @@ -6619,7 +6620,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for restoring the pre-P backup format. */ - public void BackupAndRestore_api27format() throws Exception { + public void testBackupAndRestore_api27format() throws Exception { final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222"); @@ -6657,7 +6658,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void SaveAndLoad_crossProfile() { + public void testSaveAndLoad_crossProfile() { prepareCrossProfileDataSet(); dumpsysOnLogcat("Before save & load"); @@ -6860,7 +6861,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .getPackageUserId()); } - public void OnApplicationActive_permission() { + public void testOnApplicationActive_permission() { assertExpectException(SecurityException.class, "Missing permission", () -> mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10)); @@ -6869,7 +6870,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10); } - public void GetShareTargets_permission() { + public void testGetShareTargets_permission() { addPackage(CHOOSER_ACTIVITY_PACKAGE, CHOOSER_ACTIVITY_UID, 10, "sig1"); mInjectedChooserActivity = ComponentName.createRelative(CHOOSER_ACTIVITY_PACKAGE, ".ChooserActivity"); @@ -6888,7 +6889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HasShareTargets_permission() { + public void testHasShareTargets_permission() { assertExpectException(SecurityException.class, "Missing permission", () -> mManager.hasShareTargets(CALLING_PACKAGE_1)); @@ -6897,7 +6898,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.hasShareTargets(CALLING_PACKAGE_1); } - public void isSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException { + public void testisSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException { setCaller(LAUNCHER_1, USER_10); IntentFilter filter_any = new IntentFilter(); @@ -6912,18 +6913,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.hasShareTargets(CALLING_PACKAGE_1); } - public void Dumpsys_crossProfile() { + public void testDumpsys_crossProfile() { prepareCrossProfileDataSet(); dumpsysOnLogcat("test1", /* force= */ true); } - public void Dumpsys_withIcons() throws IOException { - Icons(); + public void testDumpsys_withIcons() throws IOException { + testIcons(); // Dump after having some icons. dumpsysOnLogcat("test1", /* force= */ true); } - public void ManifestShortcut_publishOnUnlockUser() { + public void testManifestShortcut_publishOnUnlockUser() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_1); @@ -7137,7 +7138,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_10)); } - public void ManifestShortcut_publishOnBroadcast() { + public void testManifestShortcut_publishOnBroadcast() { // First, no packages are installed. uninstallPackage(USER_10, CALLING_PACKAGE_1); uninstallPackage(USER_10, CALLING_PACKAGE_2); @@ -7393,7 +7394,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_missingMandatoryFields() { + public void testManifestShortcuts_missingMandatoryFields() { // Start with no apps installed. uninstallPackage(USER_10, CALLING_PACKAGE_1); uninstallPackage(USER_10, CALLING_PACKAGE_2); @@ -7462,7 +7463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_intentDefinitions() { + public void testManifestShortcuts_intentDefinitions() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_error_4); @@ -7604,7 +7605,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_checkAllFields() { + public void testManifestShortcuts_checkAllFields() { mService.handleUnlockUser(USER_10); // Package 1 updated, which has one valid manifest shortcut and one invalid. @@ -7709,7 +7710,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_localeChange() throws InterruptedException { + public void testManifestShortcuts_localeChange() throws InterruptedException { mService.handleUnlockUser(USER_10); // Package 1 updated, which has one valid manifest shortcut and one invalid. @@ -7813,7 +7814,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_updateAndDisabled_notPinned() { + public void testManifestShortcuts_updateAndDisabled_notPinned() { mService.handleUnlockUser(USER_10); // First, just publish a manifest shortcut. @@ -7853,7 +7854,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_updateAndDisabled_pinned() { + public void testManifestShortcuts_updateAndDisabled_pinned() { mService.handleUnlockUser(USER_10); // First, just publish a manifest shortcut. @@ -7909,7 +7910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_duplicateInSingleActivity() { + public void testManifestShortcuts_duplicateInSingleActivity() { mService.handleUnlockUser(USER_10); // The XML has two shortcuts with the same ID. @@ -7934,7 +7935,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_duplicateInTwoActivities() { + public void testManifestShortcuts_duplicateInTwoActivities() { mService.handleUnlockUser(USER_10); // ShortcutActivity has shortcut ms1 @@ -7986,7 +7987,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Manifest shortcuts cannot override shortcuts that were published via the APIs. */ - public void ManifestShortcuts_cannotOverrideNonManifest() { + public void testManifestShortcuts_cannotOverrideNonManifest() { mService.handleUnlockUser(USER_10); // Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut. @@ -8059,7 +8060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Make sure the APIs won't work on manifest shortcuts. */ - public void ManifestShortcuts_immutable() { + public void testManifestShortcuts_immutable() { mService.handleUnlockUser(USER_10); // Create a non-pinned manifest shortcut, a pinned shortcut that was originally @@ -8152,7 +8153,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Make sure the APIs won't work on manifest shortcuts. */ - public void ManifestShortcuts_tooMany() { + public void testManifestShortcuts_tooMany() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8171,7 +8172,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_set() { + public void testMaxShortcutCount_set() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8252,7 +8253,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_add() { + public void testMaxShortcutCount_add() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8379,7 +8380,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_update() { + public void testMaxShortcutCount_update() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8470,7 +8471,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ShortcutsPushedOutByManifest() { + public void testShortcutsPushedOutByManifest() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8578,7 +8579,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ReturnedByServer() { + public void testReturnedByServer() { // Package 1 updated, with manifest shortcuts. addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -8624,7 +8625,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void IsForegroundDefaultLauncher_true() { + public void testIsForegroundDefaultLauncher_true() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8635,7 +8636,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void IsForegroundDefaultLauncher_defaultButNotForeground() { + public void testIsForegroundDefaultLauncher_defaultButNotForeground() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8645,7 +8646,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(mInternal.isForegroundDefaultLauncher("default", uid)); } - public void IsForegroundDefaultLauncher_foregroundButNotDefault() { + public void testIsForegroundDefaultLauncher_foregroundButNotDefault() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8655,7 +8656,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(mInternal.isForegroundDefaultLauncher("another", uid)); } - public void ParseShareTargetsFromManifest() { + public void testParseShareTargetsFromManifest() { // These values must exactly match the content of shortcuts_share_targets.xml resource List<ShareTargetInfo> expectedValues = new ArrayList<>(); expectedValues.add(new ShareTargetInfo( @@ -8707,7 +8708,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } - public void ShareTargetInfo_saveToXml() throws IOException, XmlPullParserException { + public void testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException { List<ShareTargetInfo> expectedValues = new ArrayList<>(); expectedValues.add(new ShareTargetInfo( new ShareTargetInfo.TargetData[]{new ShareTargetInfo.TargetData( @@ -8773,7 +8774,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } - public void IsSharingShortcut() throws IntentFilter.MalformedMimeTypeException { + public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_share_targets); @@ -8823,7 +8824,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { filter_any)); } - public void IsSharingShortcut_PinnedAndCachedOnlyShortcuts() + public void testIsSharingShortcut_PinnedAndCachedOnlyShortcuts() throws IntentFilter.MalformedMimeTypeException { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -8880,7 +8881,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { filter_any)); } - public void AddingShortcuts_ExcludesHiddenFromLauncherShortcuts() { + public void testAddingShortcuts_ExcludesHiddenFromLauncherShortcuts() { final ShortcutInfo s1 = makeShortcutExcludedFromLauncher("s1"); final ShortcutInfo s2 = makeShortcutExcludedFromLauncher("s2"); final ShortcutInfo s3 = makeShortcutExcludedFromLauncher("s3"); @@ -8901,7 +8902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void UpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() { + public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() { final ShortcutInfo s1 = makeShortcut("s1"); final ShortcutInfo s2 = makeShortcut("s2"); final ShortcutInfo s3 = makeShortcut("s3"); @@ -8914,7 +8915,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinHiddenShortcuts_ThrowsException() { + public void testPinHiddenShortcuts_ThrowsException() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertThrown(IllegalArgumentException.class, () -> { mManager.requestPinShortcut(makeShortcutExcludedFromLauncher("s1"), null); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java index af7f703e9c31..b33233107766 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java @@ -101,9 +101,12 @@ public class ConditionProvidersTest extends UiServiceTestCase { mProviders.notifyConditions("package", msi, conditionsToNotify); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0])); - verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1])); - verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2])); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]), + eq(100)); + verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]), + eq(100)); + verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]), + eq(100)); verifyNoMoreInteractions(mCallback); } @@ -121,8 +124,10 @@ public class ConditionProvidersTest extends UiServiceTestCase { mProviders.notifyConditions("package", msi, conditionsToNotify); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0])); - verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1])); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]), + eq(100)); + verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]), + eq(100)); verifyNoMoreInteractions(mCallback); } @@ -141,8 +146,10 @@ public class ConditionProvidersTest extends UiServiceTestCase { mProviders.notifyConditions("package", msi, conditionsToNotify); - verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0])); - verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3])); + verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]), + eq(100)); + verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]), + eq(100)); verifyNoMoreInteractions(mCallback); } 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 301165f8151d..7885c9b902e2 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -11213,7 +11213,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Representative used to verify getCallingZenUser(). mBinderService.getAutomaticZenRules(); - verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT)); + verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT), anyInt()); } @Test @@ -11225,7 +11225,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Representative used to verify getCallingZenUser(). mBinderService.getAutomaticZenRules(); - verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle())); + verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle()), anyInt()); } /** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 1884bbd39bb9..6ef078b6da8a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -291,7 +291,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING); + return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING, + com.android.server.notification.Flags.FLAG_FIX_CALLING_UID_FROM_CPS); } public ZenModeHelperTest(FlagsParameterization flags) { @@ -2617,7 +2618,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testSetAutomaticZenRuleState_nullPkg() { + public void testSetAutomaticZenRuleStateFromConditionProvider_nullPkg() { AutomaticZenRule zenRule = new AutomaticZenRule("name", null, new ComponentName(mContext.getPackageName(), "ScheduleConditionProvider"), @@ -2627,10 +2628,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule, ORIGIN_APP, "test", CUSTOM_PKG_UID); - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, zenRule.getConditionId(), - new Condition(zenRule.getConditionId(), "", STATE_TRUE), - ORIGIN_APP, - CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, + zenRule.getConditionId(), new Condition(zenRule.getConditionId(), "", STATE_TRUE), + ORIGIN_APP, CUSTOM_PKG_UID); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals(STATE_TRUE, ruleInConfig.condition.state); @@ -2726,8 +2726,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_SYSTEM, "test", SYSTEM_UID); Condition condition = new Condition(sharedUri, "", STATE_TRUE); - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition, - ORIGIN_SYSTEM, SYSTEM_UID); + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, sharedUri, + condition, ORIGIN_SYSTEM, SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2741,8 +2741,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { } condition = new Condition(sharedUri, "", STATE_FALSE); - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition, - ORIGIN_SYSTEM, SYSTEM_UID); + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, sharedUri, + condition, ORIGIN_SYSTEM, SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2780,9 +2780,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - ORIGIN_APP, "reasons", 0); + ORIGIN_APP, "reasons", CUSTOM_PKG_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo( new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -2814,9 +2815,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); } @@ -2845,7 +2847,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_USER_IN_SYSTEMUI, "reasons", 0); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); } @@ -2863,7 +2866,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2875,9 +2878,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(updateFromApp) .build(), - ORIGIN_APP, "reasons", 0); + ORIGIN_APP, "reasons", CUSTOM_PKG_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo( new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // From update. @@ -2898,7 +2902,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2908,9 +2912,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromSystem) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem); } @@ -2926,7 +2931,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) @@ -2939,9 +2944,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromUser) .build(), - ORIGIN_USER_IN_SYSTEMUI, "reasons", 0); + ORIGIN_USER_IN_SYSTEMUI, "reasons", SYSTEM_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); } @@ -2959,15 +2965,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars .build()) .build(), - ORIGIN_APP, "reasons", 0); + ORIGIN_APP, "reasons", CUSTOM_PKG_UID); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, new AutomaticZenRule.Builder("Rule", CONDITION_ID) // no zen policy .build(), - ORIGIN_APP, "reasons", 0); + ORIGIN_APP, "reasons", CUSTOM_PKG_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls()) .isEqualTo(STATE_DISALLOW); } @@ -2988,7 +2995,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowReminders(true) .build()) .build(), - ORIGIN_SYSTEM, "reasons", 0); + ORIGIN_SYSTEM, "reasons", SYSTEM_UID); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, new AutomaticZenRule.Builder("Rule", CONDITION_ID) @@ -2996,9 +3003,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) .build()) .build(), - ORIGIN_APP, "reasons", 0); + ORIGIN_APP, "reasons", CUSTOM_PKG_UID); - AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls()) .isEqualTo(STATE_ALLOW); // from update assertThat(savedRule.getZenPolicy().getPriorityCallSenders()) @@ -4441,7 +4449,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.triggerDescription = TRIGGER_DESC; mZenModeHelper.mConfig.automaticRules.put(rule.id, rule); - AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id); + AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id, + SYSTEM_UID); assertEquals(NAME, actual.getName()); assertEquals(OWNER, actual.getOwner()); @@ -4508,16 +4517,17 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // Checks the name can be changed by the app because the user has not modified it. AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) .setName("NewName") .build(); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP, - "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + "reason", CUSTOM_PKG_UID); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); assertThat(rule.getName()).isEqualTo("NewName"); // The user modifies some other field in the rule, which makes the rule as a whole not @@ -4534,8 +4544,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setName("NewAppName") .build(); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP, - "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + "reason", CUSTOM_PKG_UID); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); assertThat(rule.getName()).isEqualTo("NewAppName"); // The user modifies the name. @@ -4544,7 +4554,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); assertThat(rule.getName()).isEqualTo("UserProvidedName"); // The app is no longer able to modify the name. @@ -4552,8 +4562,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setName("NewAppName") .build(); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP, - "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + "reason", CUSTOM_PKG_UID); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); assertThat(rule.getName()).isEqualTo("UserProvidedName"); } @@ -4568,8 +4578,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // Modifies the filter, icon, zen policy, and device effects ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy()) @@ -4589,7 +4600,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Update the rule with the AZR from origin user. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); // UPDATE_ORIGIN_USER should change the bitmask and change the values. assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); @@ -4625,8 +4636,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // Modifies the icon, zen policy and device effects ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy()) @@ -4646,7 +4658,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Update the rule with the AZR from origin systemUI. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_SYSTEM, "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask. assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID); @@ -4675,8 +4687,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); ZenPolicy policy = new ZenPolicy.Builder() .allowReminders(true) @@ -4693,7 +4706,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule. // The bitmask is not modified. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP, - "reason", SYSTEM_UID); + "reason", CUSTOM_PKG_UID); ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(storedRule.userModifiedFields).isEqualTo(0); @@ -4717,9 +4730,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Zen rule update coming from the app again. This cannot fully update the rule, because // the rule is already considered user modified. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleIdUser, azrUpdate, ORIGIN_APP, - "reason", SYSTEM_UID); + "reason", CUSTOM_PKG_UID); AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - ruleIdUser); + ruleIdUser, CUSTOM_PKG_UID); // The app can only change the value if the rule is not already user modified, // so the rule is not changed, and neither is the bitmask. @@ -4749,8 +4762,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build()) .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // The values are modified but the bitmask is not. assertThat(rule.getZenPolicy().getPriorityCategoryReminders()) @@ -4771,7 +4785,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase) // Sets Device Effects to null @@ -4781,8 +4795,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason", - SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept. assertThat(rule.getDeviceEffects()).isEqualTo(zde); @@ -4797,8 +4812,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase) // Set zen policy to null @@ -4808,8 +4822,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason", - SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged // (equivalent to the provided policy, with additional fields filled in with defaults). @@ -4829,8 +4844,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); // Create a fully populated ZenPolicy. ZenPolicy policy = new ZenPolicy.Builder() @@ -4860,7 +4874,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Default config defined in getDefaultConfigParser() is used as the original rule. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); // New ZenPolicy differs from the default config assertThat(rule.getZenPolicy()).isNotNull(); @@ -4890,8 +4905,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, - mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -4903,7 +4919,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Applies the update to the rule. mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID); // New ZenDeviceEffects is used; all fields considered set, since previously were null. assertThat(rule.getDeviceEffects()).isNotNull(); @@ -5286,7 +5302,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); assertThat(result).isNotNull(); assertThat(result.getOwner().getClassName()).isEqualTo("brand.new.cps"); } @@ -5306,7 +5323,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update, ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); - AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID); assertThat(result).isNotNull(); assertThat(result.getOwner().getClassName()).isEqualTo("old.third.party.cps"); } @@ -5518,8 +5536,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime()) - .isEqualTo(1000); + assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000); // User customizes it. AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule) @@ -5546,7 +5564,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // - ZenPolicy is the one that the user had set. // - rule still has the user-modified fields. AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000. assertThat(newRuleId).isEqualTo(ruleId); assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); @@ -5575,8 +5593,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime()) - .isEqualTo(1000); + assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000); // App deletes it. mTestClock.advanceByMillis(1000); @@ -5592,7 +5610,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Verify that the rule was recreated. This means id and creation time are new. AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getCreationTime()).isEqualTo(3000); assertThat(newRuleId).isNotEqualTo(ruleId); } @@ -5609,8 +5627,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime()) - .isEqualTo(1000); + assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID) + .getCreationTime()).isEqualTo(1000); // User customizes it. mTestClock.advanceByMillis(1000); @@ -5637,7 +5655,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Verify that the rule was recreated. This means id and creation time are new, and the rule // matches the latest data supplied to addAZR. AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getCreationTime()).isEqualTo(4000); assertThat(newRuleId).isNotEqualTo(ruleId); assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); @@ -5660,8 +5678,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime()) - .isEqualTo(1000); + assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000); // User customizes it. mTestClock.advanceByMillis(1000); @@ -5686,7 +5704,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Verify that the rule was recreated. This means id and creation time are new. AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getCreationTime()).isEqualTo(4000); assertThat(newRuleId).isNotEqualTo(ruleId); } @@ -5728,7 +5746,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Verify that the rule was NOT restored: assertThat(newRuleId).isNotEqualTo(ruleId); AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner")); @@ -5869,7 +5887,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // The rule is restored... assertThat(newRuleId).isEqualTo(ruleId); AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); // ... but it is NOT active @@ -5923,7 +5941,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // The rule is restored... assertThat(newRuleId).isEqualTo(ruleId); AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - newRuleId); + newRuleId, CUSTOM_PKG_UID); assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); // ... but it is NEITHER active NOR snoozed. @@ -6005,22 +6023,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_APP, "reasons", CUSTOM_PKG_UID); // Null condition -> STATE_FALSE - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id)) + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID)) .isEqualTo(Condition.STATE_FALSE); mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_TRUE, ORIGIN_APP, CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id)) + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID)) .isEqualTo(Condition.STATE_TRUE); mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_FALSE, ORIGIN_APP, CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id)) + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID)) .isEqualTo(Condition.STATE_FALSE); mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "", CUSTOM_PKG_UID); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id)) + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID)) .isEqualTo(Condition.STATE_UNKNOWN); } @@ -6036,8 +6054,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID); assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule")) - .isEqualTo(Condition.STATE_UNKNOWN); + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule", + CUSTOM_PKG_UID)).isEqualTo(Condition.STATE_UNKNOWN); } @Test @@ -6063,7 +6081,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(FLAG_MODES_API) - public void setAutomaticZenRuleState_conditionForNotOwnedRule_ignored() { + public void setAutomaticZenRuleStateFromConditionProvider_conditionForNotOwnedRule_ignored() { // Assume existence of an other-package-owned rule that is currently ACTIVE. assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); ZenRule otherRule = newZenRule("another.package", Instant.now(), null); @@ -6075,7 +6093,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); // Should be ignored. - mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, otherRule.conditionId, + mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, + otherRule.conditionId, new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE), ORIGIN_APP, CUSTOM_PKG_UID); @@ -6182,7 +6201,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); // From user, update that rule's interruption filter. - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) .build(); @@ -6214,7 +6234,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); // From user, update something in that rule, but not the interruption filter. - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) .setName("Renamed") .build(); @@ -6315,7 +6336,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName()); // User chooses a new name. - AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, new AutomaticZenRule.Builder(azr).setName("User chose this").build(), ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); @@ -6414,7 +6436,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build(); // From user, update that rule's policy. - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds() .allowAlarms(true).build(); AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) @@ -6456,7 +6479,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build(); // From user, update something in that rule, but not the ZenPolicy. - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) .setName("Rule renamed, not touching policy") .build(); @@ -6509,7 +6533,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName()); // User chooses a new name. - AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId); + AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, + SYSTEM_UID); mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, new AutomaticZenRule.Builder(azr).setName("User chose this").build(), ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID); @@ -6645,7 +6670,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(), ORIGIN_APP, "reason", CUSTOM_PKG_UID); AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, - ruleId); + ruleId, CUSTOM_PKG_UID); assertThat(storedRule.getIconResId()).isEqualTo(0); } @@ -7087,8 +7112,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME)); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id)) - .isEqualTo(STATE_TRUE); + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id, + CUSTOM_PKG_UID)).isEqualTo(STATE_TRUE); assertThat(implicitRule.isActive()).isTrue(); assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); } @@ -7108,8 +7133,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME)); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id)) - .isEqualTo(STATE_FALSE); + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id, + CUSTOM_PKG_UID)).isEqualTo(STATE_FALSE); assertThat(implicitRule.isActive()).isFalse(); assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); } @@ -7177,7 +7202,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId)) + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, SYSTEM_UID)) .isEqualTo(STATE_TRUE); ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); @@ -7192,14 +7217,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null); if (Flags.modesUi()) { - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId)) - .isEqualTo(STATE_TRUE); + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, + SYSTEM_UID)).isEqualTo(STATE_TRUE); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE); assertThat(zenRule.condition).isNull(); } else { - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId)) - .isEqualTo(STATE_FALSE); + assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, + SYSTEM_UID)).isEqualTo(STATE_FALSE); } } @@ -7218,7 +7243,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId, new Condition(rule.getConditionId(), "snooze", STATE_FALSE, SOURCE_USER_ACTION), ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId)) + assertThat( + mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID)) .isEqualTo(STATE_FALSE); ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE); @@ -7232,7 +7258,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { TypedXmlPullParser parser = getParserForByteStream(xmlBytes); mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null); - assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId)) + assertThat( + mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID)) .isEqualTo(STATE_TRUE); zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java index b8d554b405d1..98a4fb3c473f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java @@ -184,12 +184,12 @@ public class AppCompatResizeOverridesTest extends WindowTestsBase { void checkShouldOverrideForceResizeApp(boolean expected) { Assert.assertEquals(expected, activity().top().mAppCompatController - .getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); + .getResizeOverrides().shouldOverrideForceResizeApp()); } void checkShouldOverrideForceNonResizeApp(boolean expected) { Assert.assertEquals(expected, activity().top().mAppCompatController - .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp()); + .getResizeOverrides().shouldOverrideForceNonResizeApp()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index b6e393d7be0c..03d904283e83 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -342,8 +342,8 @@ public class AppTransitionTests extends WindowTestsBase { public void testCancelRemoteAnimationWhenFreeze() { final DisplayContent dc = createNewDisplay(Display.STATE_ON); doReturn(false).when(dc).onDescendantOrientationChanged(any()); - final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION, - dc, "exiting app"); + final WindowState exitingAppWindow = newWindowBuilder("exiting app", + TYPE_BASE_APPLICATION).setDisplay(dc).build(); final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord; // Wait until everything in animation handler get executed to prevent the exiting window // from being removed during WindowSurfacePlacer Traversal. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java index 14276ae21899..7033d79d0ee1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -266,10 +266,10 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); prepareSecondaryDisplay(); - final WindowState defaultDisplayWindow = createWindow(/* parent= */ null, - TYPE_BASE_APPLICATION, mDisplayContent, "DefaultDisplayWindow"); - final WindowState secondaryDisplayWindow = createWindow(/* parent= */ null, - TYPE_BASE_APPLICATION, mSecondaryDisplayContent, "SecondaryDisplayWindow"); + final WindowState defaultDisplayWindow = newWindowBuilder("DefaultDisplayWindow", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); + final WindowState secondaryDisplayWindow = newWindowBuilder("SecondaryDisplayWindow", + TYPE_BASE_APPLICATION).setDisplay(mSecondaryDisplayContent).build(); makeWindowVisibleAndNotDrawn(defaultDisplayWindow, secondaryDisplayWindow); // Mark as display switching only for the default display as we filter out diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index bd15bc42e811..347d1bc1becc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -379,13 +379,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); - final WindowState firstActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, - "firstActivityWin"); + final WindowState firstActivityWin = newWindowBuilder("firstActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build(); spyOn(firstActivityWin); - final WindowState secondActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity, - "firstActivityWin"); + final WindowState secondActivityWin = newWindowBuilder("secondActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mSecondActivity).build(); spyOn(secondActivityWin); // firstActivityWin should be the target @@ -424,13 +422,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { setupImeWindow(); final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); - final WindowState firstActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, - "firstActivityWin"); + final WindowState firstActivityWin = newWindowBuilder("firstActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build(); spyOn(firstActivityWin); - final WindowState secondActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity, - "secondActivityWin"); + final WindowState secondActivityWin = newWindowBuilder("secondActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mSecondActivity).build(); spyOn(secondActivityWin); // firstActivityWin should be the target @@ -464,9 +460,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); - final WindowState firstActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, - "firstActivityWin"); + final WindowState firstActivityWin = newWindowBuilder("firstActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build(); spyOn(firstActivityWin); // firstActivityWin should be the target doReturn(true).when(firstActivityWin).canBeImeTarget(); @@ -499,9 +494,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); // firstActivityWin should be the target - final WindowState firstActivityWin = - createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, - "firstActivityWin"); + final WindowState firstActivityWin = newWindowBuilder("firstActivityWin", + TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build(); spyOn(firstActivityWin); doReturn(true).when(firstActivityWin).canBeImeTarget(); WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); @@ -560,8 +554,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } private void setupImeWindow() { - final WindowState imeWindow = createWindow(null /* parent */, - TYPE_INPUT_METHOD, mDisplay, "mImeWindow"); + final WindowState imeWindow = newWindowBuilder("mImeWindow", TYPE_INPUT_METHOD).setDisplay( + mDisplay).build(); imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; mDisplay.mInputMethodWindow = imeWindow; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index dc4adcc4315b..299717393028 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -878,8 +878,10 @@ public class TaskFragmentTest extends WindowTestsBase { .build(); final ActivityRecord activity0 = tf0.getTopMostActivity(); final ActivityRecord activity1 = tf1.getTopMostActivity(); - final WindowState win0 = createWindow(null, TYPE_BASE_APPLICATION, activity0, "win0"); - final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity1, "win1"); + final WindowState win0 = newWindowBuilder("win0", TYPE_BASE_APPLICATION).setWindowToken( + activity0).build(); + final WindowState win1 = newWindowBuilder("win1", TYPE_BASE_APPLICATION).setWindowToken( + activity1).build(); doReturn(false).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeInputTarget(win0); @@ -1174,8 +1176,8 @@ public class TaskFragmentTest extends WindowTestsBase { } private WindowState createAppWindow(ActivityRecord app, String name) { - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name, - 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow()); + final WindowState win = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken( + app).setClientWindow(new TestIWindow()).build(); mWm.mWindowMap.put(win.mClient.asBinder(), win); return win; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java index f145b40d2292..f9250f9ecf5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -63,7 +63,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testAppRemoved() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mCache.putSnapshot(window.getTask(), createSnapshot()); assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); mCache.onAppRemoved(window.mActivityRecord); @@ -72,7 +72,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testAppDied() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mCache.putSnapshot(window.getTask(), createSnapshot()); assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); mCache.onAppDied(window.mActivityRecord); @@ -81,7 +81,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testTaskRemoved() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mCache.putSnapshot(window.getTask(), createSnapshot()); assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); mCache.onIdRemoved(window.getTask().mTaskId); @@ -90,7 +90,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testReduced_notCached() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); @@ -105,7 +105,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testRestoreFromDisk() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); @@ -117,7 +117,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { @Test public void testClearCache() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mCache.putSnapshot(window.getTask(), mSnapshot); assertEquals(mSnapshot, mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index c6b2a6b8d42f..1bca53aff034 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -74,8 +74,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { @Test public void testGetClosingApps_closing() { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); + final WindowState closingWindow = newWindowBuilder("closingWindow", + FIRST_APPLICATION_WINDOW).build(); closingWindow.mActivityRecord.commitVisibility( false /* visible */, true /* performLayout */); final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); @@ -88,8 +88,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { @Test public void testGetClosingApps_notClosing() { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); + final WindowState closingWindow = newWindowBuilder("closingWindow", + FIRST_APPLICATION_WINDOW).build(); final WindowState openingWindow = createAppWindow(closingWindow.getTask(), FIRST_APPLICATION_WINDOW, "openingWindow"); closingWindow.mActivityRecord.commitVisibility( @@ -105,8 +105,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { @Test public void testGetClosingApps_skipClosingAppsSnapshotTasks() { - final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW, - "closingWindow"); + final WindowState closingWindow = newWindowBuilder("closingWindow", + FIRST_APPLICATION_WINDOW).build(); closingWindow.mActivityRecord.commitVisibility( false /* visible */, true /* performLayout */); final ArraySet<ActivityRecord> closingApps = new ArraySet<>(); @@ -133,19 +133,19 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { @Test public void testGetSnapshotMode() { - final WindowState disabledWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow"); + final WindowState disabledWindow = newWindowBuilder("disabledWindow", + FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build(); disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false); assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask())); - final WindowState normalWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow"); + final WindowState normalWindow = newWindowBuilder("normalWindow", + FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build(); assertEquals(SNAPSHOT_MODE_REAL, mWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask())); - final WindowState secureWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow"); + final WindowState secureWindow = newWindowBuilder("secureWindow", + FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build(); secureWindow.mAttrs.flags |= FLAG_SECURE; assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); @@ -297,8 +297,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { spyOn(mWm.mTaskSnapshotController); doReturn(false).when(mWm.mTaskSnapshotController).shouldDisableSnapshots(); - final WindowState normalWindow = createWindow(null, - FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow"); + final WindowState normalWindow = newWindowBuilder("normalWindow", + FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build(); final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder() .setTopActivityComponent(normalWindow.mActivityRecord.mActivityComponent).build(); doReturn(snapshot).when(mWm.mTaskSnapshotController).snapshot(any()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java index 9bde0663d4a3..51ea498811fc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java @@ -41,7 +41,7 @@ import java.io.File; * Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader} * * Build/Install/Run: - * atest TaskSnapshotPersisterLoaderTest + * atest TaskSnapshotLowResDisabledTest */ @MediumTest @Presubmit @@ -126,7 +126,7 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas @Test public void testReduced_notCached() { - final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window"); + final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build(); mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot()); mSnapshotPersistQueue.waitForQueueEmpty(); assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 1fa657822189..5ed2df30518b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -504,8 +504,8 @@ public class WindowContainerTests extends WindowTestsBase { assertTrue(child.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)); assertFalse(child.isAnimating(PARENTS, ANIMATION_TYPE_SCREEN_ROTATION)); - final WindowState windowState = createWindow(null /* parent */, TYPE_BASE_APPLICATION, - mDisplayContent, "TestWindowState"); + final WindowState windowState = newWindowBuilder("TestWindowState", + TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); WindowContainer parent = windowState.getParent(); spyOn(windowState.mSurfaceAnimator); doReturn(true).when(windowState.mSurfaceAnimator).isAnimating(); @@ -1045,8 +1045,8 @@ public class WindowContainerTests extends WindowTestsBase { // An animating window with mRemoveOnExit can be removed by handleCompleteDeferredRemoval // once it no longer animates. - final WindowState exitingWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, - displayContent, "exiting window"); + final WindowState exitingWindow = newWindowBuilder("exiting window", + TYPE_APPLICATION_OVERLAY).setDisplay(displayContent).build(); exitingWindow.startAnimation(exitingWindow.getPendingTransaction(), mock(AnimationAdapter.class), false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); @@ -1063,7 +1063,7 @@ public class WindowContainerTests extends WindowTestsBase { final ActivityRecord r = new TaskBuilder(mSupervisor).setCreateActivity(true) .setDisplay(displayContent).build().getTopMostActivity(); // Add a window and make the activity animating so the removal of activity is deferred. - createWindow(null, TYPE_BASE_APPLICATION, r, "win"); + newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(r).build(); doReturn(true).when(r).isAnimating(anyInt(), anyInt()); displayContent.remove(); @@ -1216,7 +1216,8 @@ public class WindowContainerTests extends WindowTestsBase { public void testFreezeInsets() { final Task task = createTask(mDisplayContent); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); // Set visibility to false, verify the main window of the task will be set the frozen // insets state immediately. @@ -1233,7 +1234,8 @@ public class WindowContainerTests extends WindowTestsBase { final Task rootTask = createTask(mDisplayContent); final Task task = createTaskInRootTask(rootTask, 0 /* userId */); final ActivityRecord activity = createActivityRecord(mDisplayContent, task); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken( + activity).build(); task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); spyOn(win); doReturn(true).when(task).okToAnimate(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index 72935cb546d9..8606581539ff 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -49,9 +49,10 @@ public class WindowContainerTraversalTests extends WindowTestsBase { @SetupWindows(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD }) @Test public void testDockedDividerPosition() { - final WindowState splitScreenWindow = createWindow(null, - WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "splitScreenWindow"); + final WindowState splitScreenWindow = newWindowBuilder("splitScreenWindow", + TYPE_BASE_APPLICATION).setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay( + mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(splitScreenWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 50e0e181cd68..ab9abfc4a876 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -154,9 +154,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsParentWindowHidden() { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); + final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build(); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); + final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); // parentWindow is initially set to hidden. assertTrue(parentWindow.mHidden); @@ -172,10 +174,12 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsChildWindow() { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); - final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); + final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build(); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); + final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); + final WindowState randomWindow = newWindowBuilder("randomWindow", TYPE_APPLICATION).build(); assertFalse(parentWindow.isChildWindow()); assertTrue(child1.isChildWindow()); @@ -185,12 +189,15 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testHasChild() { - final WindowState win1 = createWindow(null, TYPE_APPLICATION, "win1"); - final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW, "win11"); - final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW, "win12"); - final WindowState win2 = createWindow(null, TYPE_APPLICATION, "win2"); - final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW, "win21"); - final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow"); + final WindowState win1 = newWindowBuilder("win1", TYPE_APPLICATION).build(); + final WindowState win11 = newWindowBuilder("win11", FIRST_SUB_WINDOW).setParent( + win1).build(); + final WindowState win12 = newWindowBuilder("win12", FIRST_SUB_WINDOW).setParent( + win1).build(); + final WindowState win2 = newWindowBuilder("win2", TYPE_APPLICATION).build(); + final WindowState win21 = newWindowBuilder("win21", FIRST_SUB_WINDOW).setParent( + win2).build(); + final WindowState randomWindow = newWindowBuilder("randomWindow", TYPE_APPLICATION).build(); assertTrue(win1.hasChild(win11)); assertTrue(win1.hasChild(win12)); @@ -206,9 +213,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testGetParentWindow() { - final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow"); - final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2"); + final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build(); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); + final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent( + parentWindow).build(); assertNull(parentWindow.getParentWindow()); assertEquals(parentWindow, child1.getParentWindow()); @@ -217,8 +226,8 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testOverlayWindowHiddenWhenSuspended() { - final WindowState overlayWindow = spy(createWindow(null, TYPE_APPLICATION_OVERLAY, - "overlayWindow")); + final WindowState overlayWindow = spy( + newWindowBuilder("overlayWindow", TYPE_APPLICATION_OVERLAY).build()); overlayWindow.setHiddenWhileSuspended(true); verify(overlayWindow).hide(true /* doAnimation */, true /* requestAnim */); overlayWindow.setHiddenWhileSuspended(false); @@ -227,9 +236,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testGetTopParentWindow() { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); + final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build(); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + root).build(); + final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent( + child1).build(); assertEquals(root, root.getTopParentWindow()); assertEquals(root, child1.getTopParentWindow()); @@ -244,7 +255,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsOnScreen_hiddenByPolicy() { - final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build(); window.setHasSurface(true); assertTrue(window.isOnScreen()); window.hide(false /* doAnimation */, false /* requestAnim */); @@ -273,8 +284,8 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanBeImeTarget() { - final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); - final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow"); + final WindowState appWindow = newWindowBuilder("appWindow", TYPE_APPLICATION).build(); + final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_INPUT_METHOD).build(); // Setting FLAG_NOT_FOCUSABLE prevents the window from being an IME target. appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; @@ -328,16 +339,17 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testGetWindow() { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild"); - final WindowState mediaOverlayChild = createWindow(root, - TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild"); - final WindowState attachedDialogChild = createWindow(root, - TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild"); - final WindowState subPanelChild = createWindow(root, - TYPE_APPLICATION_SUB_PANEL, "subPanelChild"); - final WindowState aboveSubPanelChild = createWindow(root, - TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild"); + final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build(); + final WindowState mediaChild = newWindowBuilder("mediaChild", + TYPE_APPLICATION_MEDIA).setParent(root).build(); + final WindowState mediaOverlayChild = newWindowBuilder("mediaOverlayChild", + TYPE_APPLICATION_MEDIA_OVERLAY).setParent(root).build(); + final WindowState attachedDialogChild = newWindowBuilder("attachedDialogChild", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(root).build(); + final WindowState subPanelChild = newWindowBuilder("subPanelChild", + TYPE_APPLICATION_SUB_PANEL).setParent(root).build(); + final WindowState aboveSubPanelChild = newWindowBuilder("aboveSubPanelChild", + TYPE_APPLICATION_ABOVE_SUB_PANEL).setParent(root).build(); final LinkedList<WindowState> windows = new LinkedList<>(); @@ -358,7 +370,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testDestroySurface() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build(); win.mHasSurface = win.mAnimatingExit = true; win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class); win.onExitAnimationDone(); @@ -384,8 +396,10 @@ public class WindowStateTests extends WindowTestsBase { // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before // calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity. final ActivityRecord activity = createActivityRecord(mDisplayContent); - final WindowState first = createWindow(null, TYPE_APPLICATION, activity, "first"); - final WindowState second = createWindow(null, TYPE_APPLICATION, activity, "second"); + final WindowState first = newWindowBuilder("first", TYPE_APPLICATION).setWindowToken( + activity).build(); + final WindowState second = newWindowBuilder("second", TYPE_APPLICATION).setWindowToken( + activity).build(); testPrepareWindowToDisplayDuringRelayout(first, false /* expectedWakeupCalled */, true /* expectedCurrentLaunchCanTurnScreenOn */); @@ -423,10 +437,10 @@ public class WindowStateTests extends WindowTestsBase { // Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an // activity. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup final WindowToken windowToken = createTestWindowToken(FIRST_SUB_WINDOW, mDisplayContent); - final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken, - "firstWindow"); - final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken, - "secondWindow"); + final WindowState firstWindow = newWindowBuilder("firstWindow", + TYPE_APPLICATION).setWindowToken(windowToken).build(); + final WindowState secondWindow = newWindowBuilder("secondWindow", + TYPE_APPLICATION).setWindowToken(windowToken).build(); firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; @@ -459,7 +473,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanAffectSystemUiFlags() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); app.mActivityRecord.setVisible(true); assertTrue(app.canAffectSystemUiFlags()); app.mActivityRecord.setVisible(false); @@ -471,7 +485,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanAffectSystemUiFlags_starting() { - final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION_STARTING).build(); app.mActivityRecord.setVisible(true); app.mStartingData = new SnapshotStartingData(mWm, null, 0); assertFalse(app.canAffectSystemUiFlags()); @@ -481,7 +495,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanAffectSystemUiFlags_disallow() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); app.mActivityRecord.setVisible(true); assertTrue(app.canAffectSystemUiFlags()); app.getTask().setCanAffectSystemUiFlags(false); @@ -538,9 +552,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsSelfOrAncestorWindowAnimating() { - final WindowState root = createWindow(null, TYPE_APPLICATION, "root"); - final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1"); - final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2"); + final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build(); + final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent( + root).build(); + final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent( + child1).build(); assertFalse(child2.isSelfOrAncestorWindowAnimatingExit()); child2.mAnimatingExit = true; assertTrue(child2.isSelfOrAncestorWindowAnimatingExit()); @@ -551,7 +567,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testDeferredRemovalByAnimating() { - final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + final WindowState appWindow = newWindowBuilder("appWindow", TYPE_APPLICATION).build(); makeWindowVisible(appWindow); spyOn(appWindow.mWinAnimator); doReturn(true).when(appWindow.mWinAnimator).getShown(); @@ -571,8 +587,9 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testOnExitAnimationDone() { - final WindowState parent = createWindow(null, TYPE_APPLICATION, "parent"); - final WindowState child = createWindow(parent, TYPE_APPLICATION_PANEL, "child"); + final WindowState parent = newWindowBuilder("parent", TYPE_APPLICATION).build(); + final WindowState child = newWindowBuilder("child", TYPE_APPLICATION_PANEL).setParent( + parent).build(); final SurfaceControl.Transaction t = parent.getPendingTransaction(); child.startAnimation(t, mock(AnimationAdapter.class), false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); @@ -609,7 +626,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testLayoutSeqResetOnReparent() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); app.mLayoutSeq = 1; mDisplayContent.mLayoutSeq = 1; @@ -622,7 +639,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testDisplayIdUpdatedOnReparent() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); // fake a different display app.mInputWindowHandle.setDisplayId(mDisplayContent.getDisplayId() + 1); app.onDisplayChanged(mDisplayContent); @@ -633,7 +650,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testApplyWithNextDraw() { - final WindowState win = createWindow(null, TYPE_APPLICATION_OVERLAY, "app"); + final WindowState win = newWindowBuilder("app", TYPE_APPLICATION_OVERLAY).build(); final SurfaceControl.Transaction[] handledT = { null }; // The normal case that the draw transaction is applied with finishing drawing. win.applyWithNextDraw(t -> handledT[0] = t); @@ -657,7 +674,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testSeamlesslyRotateWindow() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); final SurfaceControl.Transaction t = spy(StubTransaction.class); makeWindowVisible(app); @@ -707,7 +724,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testVisibilityChangeSwitchUser() { - final WindowState window = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState window = newWindowBuilder("app", TYPE_APPLICATION).build(); window.mHasSurface = true; spyOn(window); doReturn(false).when(window).showForAllUsers(); @@ -729,8 +746,9 @@ public class WindowStateTests extends WindowTestsBase { final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages; spyOn(cmp); doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt()); - final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win"); - final WindowState child = createWindow(w, TYPE_APPLICATION_PANEL, "child"); + final WindowState w = newWindowBuilder("win", TYPE_APPLICATION_OVERLAY).build(); + final WindowState child = newWindowBuilder("child", TYPE_APPLICATION_PANEL).setParent( + w).build(); assertTrue(w.hasCompatScale()); assertTrue(child.hasCompatScale()); @@ -788,7 +806,8 @@ public class WindowStateTests extends WindowTestsBase { // Child window without scale (e.g. different app) should apply inverse scale of parent. doReturn(1f).when(cmp).getCompatScale(anyString(), anyInt()); - final WindowState child2 = createWindow(w, TYPE_APPLICATION_SUB_PANEL, "child2"); + final WindowState child2 = newWindowBuilder("child2", TYPE_APPLICATION_SUB_PANEL).setParent( + w).build(); makeWindowVisible(w, child2); clearInvocations(t); child2.prepareSurfaces(); @@ -798,10 +817,10 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE }) @Test public void testRequestDrawIfNeeded() { - final WindowState startingApp = createWindow(null /* parent */, - TYPE_BASE_APPLICATION, "startingApp"); - final WindowState startingWindow = createWindow(null /* parent */, - TYPE_APPLICATION_STARTING, startingApp.mToken, "starting"); + final WindowState startingApp = newWindowBuilder("startingApp", + TYPE_BASE_APPLICATION).build(); + final WindowState startingWindow = newWindowBuilder("starting", + TYPE_APPLICATION_STARTING).setWindowToken(startingApp.mToken).build(); startingApp.mActivityRecord.mStartingWindow = startingWindow; final WindowState keyguardHostWindow = mNotificationShadeWindow; final WindowState allDrawnApp = mAppWindow; @@ -878,7 +897,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testRequestResizeForBlastSync() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "window"); + final WindowState win = newWindowBuilder("window", TYPE_APPLICATION).build(); makeWindowVisible(win); makeLastConfigReportedToClient(win, true /* visible */); win.mLayoutSeq = win.getDisplayContent().mLayoutSeq; @@ -926,8 +945,8 @@ public class WindowStateTests extends WindowTestsBase { final Task task = createTask(mDisplayContent); final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity(); - final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity, - "App window"); + final WindowState win = newWindowBuilder("App window", TYPE_APPLICATION).setWindowToken( + embeddedActivity).build(); doReturn(true).when(embeddedActivity).isVisible(); embeddedActivity.setVisibleRequested(true); makeWindowVisible(win); @@ -949,14 +968,14 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCantReceiveTouchWhenAppTokenHiddenRequested() { - final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); + final WindowState win0 = newWindowBuilder("win0", TYPE_APPLICATION).build(); win0.mActivityRecord.setVisibleRequested(false); assertFalse(win0.canReceiveTouchInput()); } @Test public void testCantReceiveTouchWhenNotFocusable() { - final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); + final WindowState win0 = newWindowBuilder("win0", TYPE_APPLICATION).build(); final Task rootTask = win0.mActivityRecord.getRootTask(); spyOn(rootTask); when(rootTask.shouldIgnoreInput()).thenReturn(true); @@ -969,7 +988,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testUpdateInputWindowHandle() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build(); win.mAttrs.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; win.mAttrs.flags = FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH; final InputWindowHandle handle = new InputWindowHandle( @@ -982,7 +1001,6 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(handleWrapper.isChanged()); assertTrue(testFlag(handle.inputConfig, InputConfig.WATCH_OUTSIDE_TOUCH)); - assertFalse(testFlag(handle.inputConfig, InputConfig.PREVENT_SPLITTING)); assertTrue(testFlag(handle.inputConfig, InputConfig.DISABLE_USER_ACTIVITY)); // The window of standard resizable task should not use surface crop as touchable region. assertFalse(handle.replaceTouchableRegionWithCrop); @@ -1026,7 +1044,7 @@ public class WindowStateTests extends WindowTestsBase { @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) @Test public void testTouchRegionUsesLetterboxBoundsIfTransformedBoundsAndLetterboxScrolling() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build(); // Transformed bounds used for size of touchable region if letterbox inner bounds are empty. final Rect transformedBounds = new Rect(0, 0, 300, 500); @@ -1051,7 +1069,7 @@ public class WindowStateTests extends WindowTestsBase { @DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) @Test public void testTouchRegionUsesLetterboxBoundsIfNullTransformedBoundsAndLetterboxScrolling() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build(); // Fragment bounds used for size of touchable region if letterbox inner bounds are empty // and Transform bounds are null. @@ -1083,7 +1101,7 @@ public class WindowStateTests extends WindowTestsBase { @EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX) @Test public void testTouchRegionUsesTransformedBoundsIfLetterboxScrolling() { - final WindowState win = createWindow(null, TYPE_APPLICATION, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build(); // Transformed bounds used for size of touchable region if letterbox inner bounds are empty. final Rect transformedBounds = new Rect(0, 0, 300, 500); @@ -1109,7 +1127,7 @@ public class WindowStateTests extends WindowTestsBase { public void testHasActiveVisibleWindow() { final int uid = ActivityBuilder.DEFAULT_FAKE_UID; - final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).setOwnerId(uid).build(); app.mActivityRecord.setVisible(false); app.mActivityRecord.setVisibility(false); assertFalse(mAtm.hasActiveVisibleWindow(uid)); @@ -1120,15 +1138,17 @@ public class WindowStateTests extends WindowTestsBase { // Make the activity invisible and add a visible toast. The uid should have no active // visible window because toast can be misused by legacy app to bypass background check. app.mActivityRecord.setVisibility(false); - final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay", uid); - final WindowState toast = createWindow(null, TYPE_TOAST, app.mToken, "toast", uid); + final WindowState overlay = newWindowBuilder("overlay", + TYPE_APPLICATION_OVERLAY).setOwnerId(uid).build(); + final WindowState toast = newWindowBuilder("toast", TYPE_TOAST).setWindowToken( + app.mToken).setOwnerId(uid).build(); toast.onSurfaceShownChanged(true); assertFalse(mAtm.hasActiveVisibleWindow(uid)); // Though starting window should belong to system. Make sure it is ignored to avoid being // allow-list unexpectedly, see b/129563343. - final WindowState starting = - createWindow(null, TYPE_APPLICATION_STARTING, app.mToken, "starting", uid); + final WindowState starting = newWindowBuilder("starting", + TYPE_APPLICATION_STARTING).setWindowToken(app.mToken).setOwnerId(uid).build(); starting.onSurfaceShownChanged(true); assertFalse(mAtm.hasActiveVisibleWindow(uid)); @@ -1145,8 +1165,8 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) @Test public void testNeedsRelativeLayeringToIme_notAttached() { - WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken, - "SameTokenWindow"); + WindowState sameTokenWindow = newWindowBuilder("SameTokenWindow", + TYPE_BASE_APPLICATION).setWindowToken(mAppWindow.mToken).build(); mDisplayContent.setImeLayeringTarget(mAppWindow); makeWindowVisible(mImeWindow); sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); @@ -1158,8 +1178,8 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD }) @Test public void testNeedsRelativeLayeringToIme_startingWindow() { - WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING, - mAppWindow.mToken, "SameTokenWindow"); + WindowState sameTokenWindow = newWindowBuilder("SameTokenWindow", + TYPE_APPLICATION_STARTING).setWindowToken(mAppWindow.mToken).build(); mDisplayContent.setImeLayeringTarget(mAppWindow); makeWindowVisible(mImeWindow); sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); @@ -1169,9 +1189,9 @@ public class WindowStateTests extends WindowTestsBase { @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) @Test public void testNeedsRelativeLayeringToIme_systemDialog() { - WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY, - mDisplayContent, - "SystemDialog", true); + WindowState systemDialogWindow = newWindowBuilder("SystemDialog", + TYPE_SECURE_SYSTEM_OVERLAY).setDisplay( + mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build(); mDisplayContent.setImeLayeringTarget(mAppWindow); mAppWindow.getTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); makeWindowVisible(mImeWindow); @@ -1182,20 +1202,21 @@ public class WindowStateTests extends WindowTestsBase { @UseTestDisplay(addWindows = {W_INPUT_METHOD}) @Test public void testNeedsRelativeLayeringToIme_notificationShadeShouldNotHideSystemDialog() { - WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY, - mDisplayContent, - "SystemDialog", true); + WindowState systemDialogWindow = newWindowBuilder("SystemDialog", + TYPE_SECURE_SYSTEM_OVERLAY).setDisplay( + mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build(); mDisplayContent.setImeLayeringTarget(systemDialogWindow); makeWindowVisible(mImeWindow); - WindowState notificationShade = createWindow(null, TYPE_NOTIFICATION_SHADE, - mDisplayContent, "NotificationShade", true); + WindowState notificationShade = newWindowBuilder("NotificationShade", + TYPE_NOTIFICATION_SHADE).setDisplay( + mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build(); notificationShade.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; assertFalse(notificationShade.needsRelativeLayeringToIme()); } @Test public void testSetFreezeInsetsState() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); spyOn(app); doReturn(true).when(app).isVisible(); @@ -1216,7 +1237,7 @@ public class WindowStateTests extends WindowTestsBase { verify(app).notifyInsetsChanged(); // Verify that invisible non-activity window won't dispatch insets changed. - final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay"); + final WindowState overlay = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build(); makeWindowVisible(overlay); assertTrue(overlay.isReadyToDispatchInsetsState()); overlay.mHasSurface = false; @@ -1244,9 +1265,9 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testAdjustImeInsetsVisibilityWhenSwitchingApps() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); - final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); + final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).build(); + final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_APPLICATION).build(); spyOn(imeWindow); doReturn(true).when(imeWindow).isVisible(); mDisplayContent.mInputMethodWindow = imeWindow; @@ -1279,10 +1300,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testAdjustImeInsetsVisibilityWhenSwitchingApps_toAppInMultiWindowMode() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - final WindowState app2 = createWindow(null, WINDOWING_MODE_MULTI_WINDOW, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app2"); - final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); + final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay( + mDisplayContent).build(); + final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_APPLICATION).build(); spyOn(imeWindow); doReturn(true).when(imeWindow).isVisible(); mDisplayContent.mInputMethodWindow = imeWindow; @@ -1321,8 +1343,8 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = W_ACTIVITY) @Test public void testUpdateImeControlTargetWhenLeavingMultiWindow() { - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, - mAppWindow.mToken, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken( + mAppWindow.mToken).build(); mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController()); spyOn(app); @@ -1349,8 +1371,8 @@ public class WindowStateTests extends WindowTestsBase { @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test public void testNotificationShadeHasImeInsetsWhenMultiWindow() { - WindowState app = createWindow(null, TYPE_BASE_APPLICATION, - mAppWindow.mToken, "app"); + WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken( + mAppWindow.mToken).build(); // Simulate entering multi-window mode and windowing mode is multi-window. app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); @@ -1376,7 +1398,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testRequestedVisibility() { - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); app.mActivityRecord.setVisible(false); app.mActivityRecord.setVisibility(false); assertFalse(app.isVisibleRequested()); @@ -1391,7 +1413,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testKeepClearAreas() { - final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build(); makeWindowVisible(window); final Rect keepClearArea1 = new Rect(0, 0, 10, 10); @@ -1433,7 +1455,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testUnrestrictedKeepClearAreas() { - final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build(); makeWindowVisible(window); final Rect keepClearArea1 = new Rect(0, 0, 10, 10); @@ -1481,8 +1503,9 @@ public class WindowStateTests extends WindowTestsBase { final InputMethodManagerInternal immi = InputMethodManagerInternal.get(); spyOn(immi); - final WindowState imeTarget = createWindow(null /* parent */, TYPE_BASE_APPLICATION, - createActivityRecord(mDisplayContent), "imeTarget"); + final WindowState imeTarget = newWindowBuilder("imeTarget", + TYPE_BASE_APPLICATION).setWindowToken( + createActivityRecord(mDisplayContent)).build(); imeTarget.mActivityRecord.setVisibleRequested(true); makeWindowVisible(imeTarget); @@ -1562,8 +1585,8 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsSecureLocked_flagSecureSet() { - WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window", - 1 /* ownerId */); + WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId( + 1).build(); window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE; assertTrue(window.isSecureLocked()); @@ -1571,8 +1594,8 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testIsSecureLocked_flagSecureNotSet() { - WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window", - 1 /* ownerId */); + WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId( + 1).build(); assertFalse(window.isSecureLocked()); } @@ -1581,8 +1604,8 @@ public class WindowStateTests extends WindowTestsBase { public void testIsSecureLocked_disableSecureWindows() { assumeTrue(Build.IS_DEBUGGABLE); - WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window", - 1 /* ownerId */); + WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId( + 1).build(); window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE; ContentResolver cr = useFakeSettingsProvider(); @@ -1617,8 +1640,10 @@ public class WindowStateTests extends WindowTestsBase { String testPackage = "test"; int ownerId1 = 20; int ownerId2 = 21; - final WindowState window1 = createWindow(null, TYPE_APPLICATION, "window1", ownerId1); - final WindowState window2 = createWindow(null, TYPE_APPLICATION, "window2", ownerId2); + final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setOwnerId( + ownerId1).build(); + final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setOwnerId( + ownerId2).build(); // Setting packagename for targeted feature window1.mAttrs.packageName = testPackage; @@ -1638,7 +1663,8 @@ public class WindowStateTests extends WindowTestsBase { public void testIsSecureLocked_sensitiveContentBlockOrClearScreenCaptureForApp() { String testPackage = "test"; int ownerId = 20; - final WindowState window = createWindow(null, TYPE_APPLICATION, "window", ownerId); + final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).setOwnerId( + ownerId).build(); window.mAttrs.packageName = testPackage; assertFalse(window.isSecureLocked()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index f226b9d29ca0..a02c3db1e636 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -74,11 +74,16 @@ public class WindowTokenTests extends WindowTestsBase { assertEquals(0, token.getWindowsCount()); - final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); - final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11"); - final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12"); - final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); - final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3"); + final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setWindowToken( + token).build(); + final WindowState window11 = newWindowBuilder("window11", FIRST_SUB_WINDOW).setParent( + window1).setWindowToken(token).build(); + final WindowState window12 = newWindowBuilder("window12", FIRST_SUB_WINDOW).setParent( + window1).setWindowToken(token).build(); + final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setWindowToken( + token).build(); + final WindowState window3 = newWindowBuilder("window3", TYPE_APPLICATION).setWindowToken( + token).build(); token.addWindow(window1); // NOTE: Child windows will not be added to the token as window containers can only @@ -105,8 +110,10 @@ public class WindowTokenTests extends WindowTestsBase { public void testAddWindow_assignsLayers() { final TestWindowToken token1 = createTestWindowToken(0, mDisplayContent); final TestWindowToken token2 = createTestWindowToken(0, mDisplayContent); - final WindowState window1 = createWindow(null, TYPE_STATUS_BAR, token1, "window1"); - final WindowState window2 = createWindow(null, TYPE_STATUS_BAR, token2, "window2"); + final WindowState window1 = newWindowBuilder("window1", TYPE_STATUS_BAR).setWindowToken( + token1).build(); + final WindowState window2 = newWindowBuilder("window2", TYPE_STATUS_BAR).setWindowToken( + token2).build(); token1.addWindow(window1); token2.addWindow(window2); @@ -122,8 +129,10 @@ public class WindowTokenTests extends WindowTestsBase { assertEquals(token, dc.getWindowToken(token.token)); - final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1"); - final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2"); + final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setWindowToken( + token).build(); + final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setWindowToken( + token).build(); window2.removeImmediately(); // The token should still be mapped in the display content since it still has a child. @@ -147,8 +156,10 @@ public class WindowTokenTests extends WindowTestsBase { // Verify that the token is on the display assertNotNull(mDisplayContent.getWindowToken(token.token)); - final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1"); - final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2"); + final WindowState window1 = newWindowBuilder("window1", TYPE_TOAST).setWindowToken( + token).build(); + final WindowState window2 = newWindowBuilder("window2", TYPE_TOAST).setWindowToken( + token).build(); mDisplayContent.removeWindowToken(token.token, true /* animateExit */); // Verify that the token is no longer mapped on the display @@ -231,7 +242,8 @@ public class WindowTokenTests extends WindowTestsBase { assertNull(fromClientToken.mSurfaceControl); - createWindow(null, TYPE_APPLICATION_OVERLAY, fromClientToken, "window"); + newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).setWindowToken( + fromClientToken).build(); assertNotNull(fromClientToken.mSurfaceControl); final WindowToken nonClientToken = new WindowToken.Builder(mDisplayContent.mWmService, @@ -285,7 +297,7 @@ public class WindowTokenTests extends WindowTestsBase { // Simulate an app window to be the IME layering target, assume the app window has no // frozen insets state by default. - final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build(); mDisplayContent.setImeLayeringTarget(app); assertNull(app.getFrozenInsetsState()); assertTrue(app.isImeLayeringTarget()); @@ -299,7 +311,8 @@ public class WindowTokenTests extends WindowTestsBase { @Test public void testRemoveWindowToken_noAnimateExitWhenSet() { final TestWindowToken token = createTestWindowToken(0, mDisplayContent); - final WindowState win = createWindow(null, TYPE_APPLICATION, token, "win"); + final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).setWindowToken( + token).build(); makeWindowVisible(win); assertTrue(win.isOnScreen()); spyOn(win); diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 4f60106db93d..84e21181a7b9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -221,7 +221,7 @@ public class ZOrderingTests extends WindowTestsBase { } WindowState createWindow(String name) { - return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name); + return newWindowBuilder(name, TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); } @Test @@ -263,12 +263,12 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() { final WindowState imeAppTarget = createWindow("imeAppTarget"); - final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget, - TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken, - "imeAppTargetChildAboveWindow"); - final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget, - TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken, - "imeAppTargetChildBelowWindow"); + final WindowState imeAppTargetChildAboveWindow = newWindowBuilder( + "imeAppTargetChildAboveWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent( + imeAppTarget).setWindowToken(imeAppTarget.mToken).build(); + final WindowState imeAppTargetChildBelowWindow = newWindowBuilder( + "imeAppTargetChildBelowWindow", TYPE_APPLICATION_MEDIA_OVERLAY).setParent( + imeAppTarget).setWindowToken(imeAppTarget.mToken).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); makeWindowVisible(mImeWindow); @@ -313,9 +313,9 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForImeNonAppImeTarget() { - final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY, - mDisplayContent, "imeSystemOverlayTarget", - true /* ownerCanAddInternalSystemWindow */); + final WindowState imeSystemOverlayTarget = newWindowBuilder("imeSystemOverlayTarget", + TYPE_SYSTEM_OVERLAY).setDisplay(mDisplayContent).setOwnerCanAddInternalSystemWindow( + true).build(); mDisplayContent.setImeLayeringTarget(imeSystemOverlayTarget); mDisplayContent.assignChildLayers(mTransaction); @@ -354,18 +354,19 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testStackLayers() { final WindowState anyWindow1 = createWindow("anyWindow"); - final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED, - ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, - "pinnedStackWindow"); - final WindowState dockedStackWindow = createWindow(null, - WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, - mDisplayContent, "dockedStackWindow"); - final WindowState assistantStackWindow = createWindow(null, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, - mDisplayContent, "assistantStackWindow"); - final WindowState homeActivityWindow = createWindow(null, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_HOME, TYPE_BASE_APPLICATION, - mDisplayContent, "homeActivityWindow"); + final WindowState pinnedStackWindow = newWindowBuilder("pinnedStackWindow", + TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_PINNED).setActivityType( + ACTIVITY_TYPE_STANDARD).setDisplay(mDisplayContent).build(); + final WindowState dockedStackWindow = newWindowBuilder("dockedStackWindow", + TYPE_BASE_APPLICATION).setWindowingMode( + WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay( + mDisplayContent).build(); + final WindowState assistantStackWindow = newWindowBuilder("assistantStackWindow", + TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_FULLSCREEN).setActivityType( + ACTIVITY_TYPE_ASSISTANT).setDisplay(mDisplayContent).build(); + final WindowState homeActivityWindow = newWindowBuilder("homeActivityWindow", + TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_FULLSCREEN).setActivityType( + ACTIVITY_TYPE_HOME).setDisplay(mDisplayContent).build(); final WindowState anyWindow2 = createWindow("anyWindow2"); mDisplayContent.assignChildLayers(mTransaction); @@ -383,13 +384,12 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForSysUiPanels() { - final WindowState navBarPanel = - createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel"); - final WindowState statusBarPanel = - createWindow(null, TYPE_STATUS_BAR_ADDITIONAL, mDisplayContent, - "StatusBarAdditional"); - final WindowState statusBarSubPanel = - createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel"); + final WindowState navBarPanel = newWindowBuilder("NavBarPanel", + TYPE_NAVIGATION_BAR_PANEL).setDisplay(mDisplayContent).build(); + final WindowState statusBarPanel = newWindowBuilder("StatusBarAdditional", + TYPE_STATUS_BAR_ADDITIONAL).setDisplay(mDisplayContent).build(); + final WindowState statusBarSubPanel = newWindowBuilder("StatusBarSubPanel", + TYPE_STATUS_BAR_SUB_PANEL).setDisplay(mDisplayContent).build(); mDisplayContent.assignChildLayers(mTransaction); // Ime should be above all app windows and below system windows if it is targeting an app @@ -401,15 +401,16 @@ public class ZOrderingTests extends WindowTestsBase { @Test public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() { - final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION, - mAppWindow.mActivityRecord, "imeAppTarget"); + final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", + TYPE_APPLICATION).setWindowToken(mAppWindow.mActivityRecord).build(); mDisplayContent.setImeInputTarget(imeAppTarget); mDisplayContent.setImeLayeringTarget(imeAppTarget); mDisplayContent.setImeControlTarget(imeAppTarget); // Set a popup IME layering target and keeps the original IME control target behinds it. - final WindowState popupImeTargetWin = createWindow(imeAppTarget, - TYPE_APPLICATION_SUB_PANEL, mAppWindow.mActivityRecord, "popupImeTargetWin"); + final WindowState popupImeTargetWin = newWindowBuilder("popupImeTargetWin", + TYPE_APPLICATION_SUB_PANEL).setParent(imeAppTarget).setWindowToken( + mAppWindow.mActivityRecord).build(); mDisplayContent.setImeLayeringTarget(popupImeTargetWin); mDisplayContent.updateImeParent(); @@ -424,11 +425,11 @@ public class ZOrderingTests extends WindowTestsBase { // then we can drop all negative layering on the windowing side. final WindowState anyWindow = createWindow("anyWindow"); - final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent, - "TypeApplicationMediaChild"); - final WindowState mediaOverlayChild = createWindow(anyWindow, - TYPE_APPLICATION_MEDIA_OVERLAY, - mDisplayContent, "TypeApplicationMediaOverlayChild"); + final WindowState child = newWindowBuilder("TypeApplicationMediaChild", + TYPE_APPLICATION_MEDIA).setParent(anyWindow).setDisplay(mDisplayContent).build(); + final WindowState mediaOverlayChild = newWindowBuilder("TypeApplicationMediaOverlayChild", + TYPE_APPLICATION_MEDIA_OVERLAY).setParent(anyWindow).setDisplay( + mDisplayContent).build(); mDisplayContent.assignChildLayers(mTransaction); @@ -440,14 +441,17 @@ public class ZOrderingTests extends WindowTestsBase { public void testAssignWindowLayers_ForPostivelyZOrderedSubtype() { final WindowState anyWindow = createWindow("anyWindow"); final ArrayList<WindowState> childList = new ArrayList<>(); - childList.add(createWindow(anyWindow, TYPE_APPLICATION_PANEL, mDisplayContent, - "TypeApplicationPanelChild")); - childList.add(createWindow(anyWindow, TYPE_APPLICATION_SUB_PANEL, mDisplayContent, - "TypeApplicationSubPanelChild")); - childList.add(createWindow(anyWindow, TYPE_APPLICATION_ATTACHED_DIALOG, mDisplayContent, - "TypeApplicationAttachedDialogChild")); - childList.add(createWindow(anyWindow, TYPE_APPLICATION_ABOVE_SUB_PANEL, mDisplayContent, - "TypeApplicationAboveSubPanelPanelChild")); + childList.add(newWindowBuilder("TypeApplicationPanelChild", + TYPE_APPLICATION_PANEL).setParent(anyWindow).setDisplay(mDisplayContent).build()); + childList.add(newWindowBuilder("TypeApplicationSubPanelChild", + TYPE_APPLICATION_SUB_PANEL).setParent(anyWindow).setDisplay( + mDisplayContent).build()); + childList.add(newWindowBuilder("TypeApplicationAttachedDialogChild", + TYPE_APPLICATION_ATTACHED_DIALOG).setParent(anyWindow).setDisplay( + mDisplayContent).build()); + childList.add(newWindowBuilder("TypeApplicationAboveSubPanelPanelChild", + TYPE_APPLICATION_ABOVE_SUB_PANEL).setParent(anyWindow).setDisplay( + mDisplayContent).build()); final LayerRecordingTransaction t = mTransaction; mDisplayContent.assignChildLayers(t); @@ -469,8 +473,8 @@ public class ZOrderingTests extends WindowTestsBase { // Create a popupWindow assertWindowHigher(mImeWindow, mAppWindow); - final WindowState popupWindow = createWindow(mAppWindow, TYPE_APPLICATION_PANEL, - mDisplayContent, "PopupWindow"); + final WindowState popupWindow = newWindowBuilder("PopupWindow", + TYPE_APPLICATION_PANEL).setParent(mAppWindow).setDisplay(mDisplayContent).build(); spyOn(popupWindow); mDisplayContent.assignChildLayers(mTransaction); @@ -492,8 +496,9 @@ public class ZOrderingTests extends WindowTestsBase { makeWindowVisible(mImeWindow); // Create a popupWindow - final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY, - mDisplayContent, "SystemDialog", true); + final WindowState systemDialogWindow = newWindowBuilder("SystemDialog", + TYPE_SECURE_SYSTEM_OVERLAY).setDisplay( + mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build(); systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; spyOn(systemDialogWindow); diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java index d49214ab718b..a9ae5f7dfc3f 100644 --- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java +++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java @@ -16,6 +16,10 @@ package com.android.server.texttospeech; +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_SCHEDULE_LIKE_TOP_APP; + import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; import android.annotation.NonNull; @@ -95,7 +99,7 @@ final class TextToSpeechManagerPerUserService extends ITextToSpeechSessionCallback callback) { super(context, new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine), - Context.BIND_AUTO_CREATE | Context.BIND_SCHEDULE_LIKE_TOP_APP, + BIND_AUTO_CREATE | BIND_SCHEDULE_LIKE_TOP_APP | BIND_FOREGROUND_SERVICE, userId, ITextToSpeechService.Stub::asInterface); mEngine = engine; diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index b5dfb631609c..e18fad3eda79 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -78,6 +78,9 @@ public interface SatelliteTransmissionUpdateCallback { /** * Called when framework receives a request to send a datagram. * + * Informs external apps that device is working on sending a datagram out and is in the process + * of checking if all the conditions required to send datagrams are met. + * * @param datagramType The type of the requested datagram. */ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 71f303311047..cadb0bdb41e1 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -72,6 +72,7 @@ android_ravenwood_test { "tests/**/*.java", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } // Make the current.txt available for use by the cts/tests/signature and /vendor tests. diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java index df92898d76b1..9640a84eb9ca 100644 --- a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java +++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java @@ -29,6 +29,7 @@ public class JankUtils { AppJankStats jankStats = new AppJankStats( /*App Uid*/APP_ID, /*Widget Id*/"test widget id", + /*navigationComponent*/null, /*Widget Category*/AppJankStats.WIDGET_CATEGORY_SCROLL, /*Widget State*/AppJankStats.WIDGET_STATE_SCROLLING, /*Total Frames*/100, diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp index b2414a85c095..63a13849ee7f 100644 --- a/tests/InputScreenshotTest/robotests/Android.bp +++ b/tests/InputScreenshotTest/robotests/Android.bp @@ -66,7 +66,6 @@ android_robolectric_test { "android.test.mock.stubs.system", "truth", ], - upstream: true, java_resource_dirs: ["config"], instrumentation_for: "InputRoboApp", diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp index 9f35c7b7fa33..e294da101fb7 100644 --- a/tests/Internal/Android.bp +++ b/tests/Internal/Android.bp @@ -65,6 +65,7 @@ android_ravenwood_test { "src/com/android/internal/util/ParcellingTests.java", ], auto_gen_config: true, + team: "trendy_team_ravenwood", } java_test_helper_library { diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index 449d93dd8c0b..20315561cceb 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -53,61 +53,67 @@ std::string GetSafePath(StringPiece arg) { void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func))); } void Command::AddRequiredFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlag(StringPiece name, StringPiece description, std::optional<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::unordered_set<std::string>* value) { - auto func = [value](StringPiece arg) -> bool { + auto func = [value](StringPiece arg, std::ostream* out_error) -> bool { value->emplace(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) { - auto func = [value](StringPiece arg) -> bool { + auto func = [value](StringPiece arg, std::ostream* out_error) -> bool { *value = true; return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 0, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 0, std::move(func))); } void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental) { @@ -172,19 +178,74 @@ void Command::Usage(std::ostream* out) { argline = " "; } } - *out << " " << std::setw(kWidth) << std::left << "-h" - << "Displays this help menu\n"; out->flush(); } -int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_error) { +const std::string& Command::addEnvironmentArg(const Flag& flag, const char* env) { + if (*env && flag.num_args > 0) { + return environment_args_.emplace_back(flag.name + '=' + env); + } + return flag.name; +} + +// +// Looks for the flags specified in the environment and adds them to |args|. +// Expected format: +// - _AAPT2_UPPERCASE_NAME are added before all of the command line flags, so it's +// a default for the flag that may get overridden by the command line. +// - AAPT2_UPPERCASE_NAME_ are added after them, making this to be the final value +// even if there was something on the command line. +// - All dashes in the flag name get replaced with underscores, the rest of it is +// intact. +// +// E.g. +// --set-some-flag becomes either _AAPT2_SET_SOME_FLAG or AAPT2_SET_SOME_FLAG_ +// --set-param=2 is _AAPT2_SET_SOME_FLAG=2 +// +// Values get passed as it, with no processing or quoting. +// +// This way one can make sure aapt2 has the flags they need even when it is +// launched in a way they can't control, e.g. deep inside a build. +// +void Command::parseFlagsFromEnvironment(std::vector<StringPiece>& args) { + // If the first argument is a subcommand then skip it and prepend the flags past that (the root + // command should only have a single '-h' flag anyway). + const int insert_pos = args.empty() ? 0 : args.front().starts_with('-') ? 0 : 1; + + std::string env_name; + for (const Flag& flag : flags_) { + // First, the prefix version. + env_name.assign("_AAPT2_"); + // Append the uppercased flag name, skipping all dashes in front and replacing them with + // underscores later. + auto name_start = flag.name.begin(); + while (name_start != flag.name.end() && *name_start == '-') { + ++name_start; + } + std::transform(name_start, flag.name.end(), std::back_inserter(env_name), + [](char c) { return c == '-' ? '_' : toupper(c); }); + if (auto prefix_env = getenv(env_name.c_str())) { + args.insert(args.begin() + insert_pos, addEnvironmentArg(flag, prefix_env)); + } + // Now reuse the same name variable to construct a suffix version: append the + // underscore and just skip the one in front. + env_name += '_'; + if (auto suffix_env = getenv(env_name.c_str() + 1)) { + args.push_back(addEnvironmentArg(flag, suffix_env)); + } + } +} + +int Command::Execute(std::vector<StringPiece>& args, std::ostream* out_error) { TRACE_NAME_ARGS("Command::Execute", args); std::vector<std::string> file_args; + parseFlagsFromEnvironment(args); + for (size_t i = 0; i < args.size(); i++) { StringPiece arg = args[i]; if (*(arg.data()) != '-') { - // Continue parsing as the subcommand if the first argument matches one of the subcommands + // Continue parsing as a subcommand if the first argument matches one of the subcommands if (i == 0) { for (auto& subcommand : subcommands_) { if (arg == subcommand->name_ || (!subcommand->short_name_.empty() @@ -211,37 +272,67 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err return 1; } + static constexpr auto matchShortArg = [](std::string_view arg, const Flag& flag) static { + return flag.name.starts_with("--") && + arg.compare(0, 2, std::string_view(flag.name.c_str() + 1, 2)) == 0; + }; + bool match = false; for (Flag& flag : flags_) { - // Allow both "--arg value" and "--arg=value" syntax. + // Allow both "--arg value" and "--arg=value" syntax, and look for the cases where we can + // safely deduce the "--arg" flag from the short "-a" version when there's no value expected + bool matched_current = false; if (arg.starts_with(flag.name) && (arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) { - if (flag.num_args > 0) { - if (arg.size() == flag.name.size()) { - i++; - if (i >= args.size()) { - *out_error << flag.name << " missing argument.\n\n"; - Usage(out_error); - return 1; - } - arg = args[i]; - } else { - arg.remove_prefix(flag.name.size() + 1); - // Disallow empty arguments after '='. - if (arg.empty()) { - *out_error << flag.name << " has empty argument.\n\n"; - Usage(out_error); - return 1; - } + matched_current = true; + } else if (flag.num_args == 0 && matchShortArg(arg, flag)) { + matched_current = true; + // It matches, now need to make sure no other flag would match as well. + // This is really inefficient, but we don't expect to have enough flags for it to matter + // (famous last words). + for (const Flag& other_flag : flags_) { + if (&other_flag == &flag) { + continue; + } + if (matchShortArg(arg, other_flag)) { + matched_current = false; // ambiguous, skip this match + break; + } + } + } + if (!matched_current) { + continue; + } + + if (flag.num_args > 0) { + if (arg.size() == flag.name.size()) { + i++; + if (i >= args.size()) { + *out_error << flag.name << " missing argument.\n\n"; + Usage(out_error); + return 1; } - flag.action(arg); + arg = args[i]; } else { - flag.action({}); + arg.remove_prefix(flag.name.size() + 1); + // Disallow empty arguments after '='. + if (arg.empty()) { + *out_error << flag.name << " has empty argument.\n\n"; + Usage(out_error); + return 1; + } + } + if (!flag.action(arg, out_error)) { + return 1; + } + } else { + if (!flag.action({}, out_error)) { + return 1; } - flag.found = true; - match = true; - break; } + flag.found = true; + match = true; + break; } if (!match) { diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index 1416e980ed19..767ca9b0de9f 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -14,10 +14,11 @@ * limitations under the License. */ -#ifndef AAPT_COMMAND_H -#define AAPT_COMMAND_H +#pragma once +#include <deque> #include <functional> +#include <memory> #include <optional> #include <ostream> #include <string> @@ -30,10 +31,17 @@ namespace aapt { class Command { public: - explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){}; + explicit Command(android::StringPiece name) : Command(name, {}) { + } explicit Command(android::StringPiece name, android::StringPiece short_name) - : name_(name), short_name_(short_name), full_subcommand_name_(name){}; + : name_(name), short_name_(short_name), full_subcommand_name_(name) { + flags_.emplace_back("--help", "Displays this help menu", false, 0, + [this](android::StringPiece arg, std::ostream* out) { + Usage(out); + return false; + }); + } Command(Command&&) = default; Command& operator=(Command&&) = default; @@ -76,41 +84,51 @@ class Command { // Parses the command line arguments, sets the flag variable values, and runs the action of // the command. If the arguments fail to parse to the command and its subcommands, then the action // will not be run and the usage will be printed instead. - int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError); + int Execute(std::vector<android::StringPiece>& args, std::ostream* out_error); + + // Same, but for a temporary vector of args. + int Execute(std::vector<android::StringPiece>&& args, std::ostream* out_error) { + return Execute(args, out_error); + } // The action to preform when the command is executed. virtual int Action(const std::vector<std::string>& args) = 0; private: struct Flag { - explicit Flag(android::StringPiece name, android::StringPiece description, - const bool is_required, const size_t num_args, - std::function<bool(android::StringPiece value)>&& action) + explicit Flag(android::StringPiece name, android::StringPiece description, bool is_required, + const size_t num_args, + std::function<bool(android::StringPiece value, std::ostream* out_err)>&& action) : name(name), description(description), - is_required(is_required), + action(std::move(action)), num_args(num_args), - action(std::move(action)) { + is_required(is_required) { } - const std::string name; - const std::string description; - const bool is_required; - const size_t num_args; - const std::function<bool(android::StringPiece value)> action; + std::string name; + std::string description; + std::function<bool(android::StringPiece value, std::ostream* out_error)> action; + size_t num_args; + bool is_required; bool found = false; }; + const std::string& addEnvironmentArg(const Flag& flag, const char* env); + void parseFlagsFromEnvironment(std::vector<android::StringPiece>& args); + std::string name_; std::string short_name_; - std::string description_ = ""; + std::string description_; std::string full_subcommand_name_; std::vector<Flag> flags_; std::vector<std::unique_ptr<Command>> subcommands_; std::vector<std::unique_ptr<Command>> experimental_subcommands_; + // A collection of arguments loaded from environment variables, with stable positions + // in memory - we add them to the vector of string views so the pointers may not change, + // with or without short string buffer utilization in std::string. + std::deque<std::string> environment_args_; }; } // namespace aapt - -#endif // AAPT_COMMAND_H diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp index 20d87e0025c3..2a3cb2a0c65d 100644 --- a/tools/aapt2/cmd/Command_test.cpp +++ b/tools/aapt2/cmd/Command_test.cpp @@ -118,4 +118,45 @@ TEST(CommandTest, OptionsWithValues) { EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr)); } +TEST(CommandTest, ShortOptions) { + TestCommand command; + bool flag = false; + command.AddOptionalSwitch("--flag", "", &flag); + + ASSERT_EQ(0, command.Execute({"--flag"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // Short version of a switch should work. + flag = false; + ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // Ambiguous names shouldn't parse via short options. + command.AddOptionalSwitch("--flag-2", "", &flag); + ASSERT_NE(0, command.Execute({"-f"s}, &std::cerr)); + + // But when we have a proper flag like that it should still work. + flag = false; + command.AddOptionalSwitch("-f", "", &flag); + ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // A regular short flag works fine as well. + flag = false; + command.AddOptionalSwitch("-d", "", &flag); + ASSERT_EQ(0, command.Execute({"-d"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // A flag with a value only works via its long name syntax. + std::optional<std::string> val; + command.AddOptionalFlag("--with-val", "", &val); + ASSERT_EQ(0, command.Execute({"--with-val"s, "1"s}, &std::cerr)); + EXPECT_TRUE(val); + EXPECT_STREQ("1", val->c_str()); + + // Make sure the flags that require a value can't be parsed via short syntax, -w=blah + // looks weird. + ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr)); +} + } // namespace aapt
\ No newline at end of file |