diff options
368 files changed, 9289 insertions, 4889 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index a16aa2dea25b..7a1add3eb57e 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -98,6 +98,7 @@ aconfig_declarations_group { "framework-jobscheduler-job.flags-aconfig-java", "framework_graphics_flags_java_lib", "hwui_flags_java_lib", + "libcore_exported_aconfig_flags_lib", "power_flags_lib", "sdk_sandbox_flags_lib", "surfaceflinger_flags_java_lib", @@ -140,6 +141,14 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Core Libraries / libcore +java_aconfig_library { + name: "libcore_exported_aconfig_flags_lib", + aconfig_declarations: "libcore-aconfig-flags", + mode: "exported", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Telecom java_aconfig_library { name: "telecom_flags_core_java_lib", @@ -233,6 +242,12 @@ cc_aconfig_library { aconfig_declarations: "com.android.text.flags-aconfig", } +rust_aconfig_library { + name: "libandroid_text_flags_rust", + crate_name: "android_text_flags", + aconfig_declarations: "com.android.text.flags-aconfig", +} + // Location aconfig_declarations { name: "android.location.flags-aconfig", @@ -363,6 +378,7 @@ java_aconfig_library { min_sdk_version: "30", apex_available: [ "//apex_available:platform", + "com.android.btservices", "com.android.mediaprovider", "com.android.permission", ], @@ -403,17 +419,6 @@ java_aconfig_library { cc_aconfig_library { name: "android.companion.virtualdevice.flags-aconfig-cc", aconfig_declarations: "android.companion.virtualdevice.flags-aconfig", -} - -cc_aconfig_library { - name: "android.companion.virtualdevice.flags-aconfig-cc-host", - aconfig_declarations: "android.companion.virtualdevice.flags-aconfig", - host_supported: true, -} - -cc_aconfig_library { - name: "android.companion.virtualdevice.flags-aconfig-cc-test", - aconfig_declarations: "android.companion.virtualdevice.flags-aconfig", host_supported: true, mode: "test", } @@ -1490,6 +1495,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "backstage_power_flags_lib-host", + aconfig_declarations: "backstage_power_flags", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Dropbox data aconfig_declarations { name: "dropbox_flags", diff --git a/Android.bp b/Android.bp index f0aa62cc37ae..eabd9c7565da 100644 --- a/Android.bp +++ b/Android.bp @@ -417,7 +417,6 @@ java_defaults { "modules-utils-fastxmlserializer", "modules-utils-preconditions", "modules-utils-statemachine", - "modules-utils-synchronous-result-receiver", "modules-utils-os", "modules-utils-uieventlogger-interface", "framework-permission-aidl-java", diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp index 1653edc77de9..856dba3f804c 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -38,3 +38,10 @@ android_test { ], certificate: "platform", } + +filegroup { + name: "multi_user_trace_config", + srcs: [ + "trace_configs/trace_config_multi_user.textproto", + ], +} diff --git a/api/Android.bp b/api/Android.bp index 89a0c186651a..d931df165a8f 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -62,40 +62,8 @@ metalava_cmd = "$(location metalava)" metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED " metalava_cmd += " --quiet " -soong_config_module_type { - name: "enable_crashrecovery_module", - module_type: "combined_apis_defaults", - config_namespace: "ANDROID", - bool_variables: ["release_crashrecovery_module"], - properties: [ - "bootclasspath", - "system_server_classpath", - ], -} - -soong_config_bool_variable { - name: "release_crashrecovery_module", -} - -enable_crashrecovery_module { - name: "crashrecovery_module_defaults", - soong_config_variables: { - release_crashrecovery_module: { - bootclasspath: [ - "framework-crashrecovery", - ], - system_server_classpath: [ - "service-crashrecovery", - ], - }, - }, -} - combined_apis { name: "frameworks-base-api", - defaults: [ - "crashrecovery_module_defaults", - ], bootclasspath: [ "android.net.ipsec.ike", "art.module.public.api", @@ -128,7 +96,12 @@ combined_apis { "framework-virtualization", "framework-wifi", "i18n.module.public.api", - ], + ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { + "true": [ + "framework-crashrecovery", + ], + default: [], + }), system_server_classpath: [ "service-art", "service-configinfrastructure", @@ -137,7 +110,12 @@ combined_apis { "service-permission", "service-rkp", "service-sdksandbox", - ], + ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { + "true": [ + "service-crashrecovery", + ], + default: [], + }), } genrule { diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 12820f9ff277..8dfddf0e13c8 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -1345,4 +1345,5 @@ java_library { ":hwbinder-stubs-docs", ], visibility: ["//visibility:public"], + is_stubs_module: true, } diff --git a/api/api.go b/api/api.go index f0d1f42f61d4..b6b1a7e44510 100644 --- a/api/api.go +++ b/api/api.go @@ -63,7 +63,6 @@ type CombinedApisProperties struct { type CombinedApis struct { android.ModuleBase - android.DefaultableModuleBase properties CombinedApisProperties } @@ -74,7 +73,6 @@ func init() { func registerBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("combined_apis", combinedApisModuleFactory) - ctx.RegisterModuleType("combined_apis_defaults", CombinedApisModuleDefaultsFactory) } var PrepareForCombinedApisTest = android.FixtureRegisterWithContext(registerBuildComponents) @@ -576,7 +574,6 @@ func combinedApisModuleFactory() android.Module { module := &CombinedApis{} module.AddProperties(&module.properties) android.InitAndroidModule(module) - android.InitDefaultableModule(module) android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) }) return module } @@ -613,16 +610,3 @@ func remove(s []string, v string) []string { } return s2 } - -// Defaults -type CombinedApisModuleDefaults struct { - android.ModuleBase - android.DefaultsModuleBase -} - -func CombinedApisModuleDefaultsFactory() android.Module { - module := &CombinedApisModuleDefaults{} - module.AddProperties(&CombinedApisProperties{}) - android.InitDefaultsModule(module) - return module -} diff --git a/core/api/current.txt b/core/api/current.txt index d610f4c8d4ed..69c409bb5261 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -54862,6 +54862,8 @@ package android.view.accessibility { method @Deprecated public void addAction(int); method public void addChild(android.view.View); method public void addChild(android.view.View, int); + method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View); + method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int); method public boolean canOpenPopup(); method public int describeContents(); method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String); @@ -54890,6 +54892,7 @@ package android.view.accessibility { method public int getInputType(); method public android.view.accessibility.AccessibilityNodeInfo getLabelFor(); method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy(); + method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList(); method public int getLiveRegion(); method public int getMaxTextLength(); method @NonNull public java.time.Duration getMinDurationBetweenContentChanges(); @@ -54950,6 +54953,8 @@ package android.view.accessibility { method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction); method public boolean removeChild(android.view.View); method public boolean removeChild(android.view.View, int); + method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View); + method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int); method public void setAccessibilityDataSensitive(boolean); method public void setAccessibilityFocused(boolean); method public void setAvailableExtraData(java.util.List<java.lang.String>); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e0c3230f8c27..88b5275d37f8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -423,6 +423,7 @@ package android.app { public final class PictureInPictureParams implements android.os.Parcelable { method public float getAspectRatioFloat(); method public float getExpandedAspectRatioFloat(); + method public static boolean isSameAspectRatio(@NonNull android.graphics.Rect, @NonNull android.util.Rational); } public final class PictureInPictureUiState implements android.os.Parcelable { diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 845a346be593..ac3711366ec7 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -1381,6 +1381,18 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim } int toId = findLatestEventIdForTime(playTime); handleAnimationEvents(-1, toId, playTime); + + if (mSeekState.isActive()) { + // Pump a frame to the on-going animators + for (int i = 0; i < mPlayingSet.size(); i++) { + Node node = mPlayingSet.get(i); + if (!node.mEnded) { + pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node)); + } + } + } + + // Remove all the finished anims for (int i = mPlayingSet.size() - 1; i >= 0; i--) { if (mPlayingSet.get(i).mEnded) { mPlayingSet.remove(i); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 36b1eaba89df..6df971a9cea8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2105,8 +2105,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void scheduleTaskFragmentTransaction(@NonNull ITaskFragmentOrganizer organizer, @NonNull TaskFragmentTransaction transaction) throws RemoteException { - // TODO(b/260873529): ITaskFragmentOrganizer can be cleanup to be a IBinder token - // after flag removal. + // TODO(b/352665082): ITaskFragmentOrganizer can be cleanup to be a IBinder token organizer.onTransactionReady(transaction); } diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index 96d874ee110b..afe915eece26 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -654,6 +654,33 @@ public final class PictureInPictureParams implements Parcelable { && !hasSetSubtitle() && mIsLaunchIntoPip == null; } + /** + * Compare a given {@link Rect} against the aspect ratio, with rounding error tolerance. + * @param bounds The {@link Rect} represents the source rect hint, this check is not needed + * if app provides a null source rect hint. + * @param aspectRatio {@link Rational} representation of aspect ratio, this check is not needed + * if app provides a null aspect ratio. + * @return {@code true} if the given {@link Rect} matches the aspect ratio. + * @hide + */ + @SuppressWarnings("UnflaggedApi") + @TestApi + public static boolean isSameAspectRatio(@NonNull Rect bounds, @NonNull Rational aspectRatio) { + // Validations + if (bounds.isEmpty() || aspectRatio.floatValue() <= 0) { + return false; + } + // Check against both the width and height. + final int exactWidth = (aspectRatio.getNumerator() * bounds.height()) + / aspectRatio.getDenominator(); + if (Math.abs(exactWidth - bounds.width()) <= 1) { + return true; + } + final int exactHeight = (aspectRatio.getDenominator() * bounds.width()) + / aspectRatio.getNumerator(); + return Math.abs(exactHeight - bounds.height()) <= 1; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 9437c748ee80..e73f4718732f 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -468,6 +468,11 @@ public final class SystemServiceRegistry { public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException { IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE); IVpnManager service = IVpnManager.Stub.asInterface(b); + if (service == null + && ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) + && android.server.Flags.allowRemovingVpnService()) { + throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE); + } return new VpnManager(ctx, service); }}); diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java index 598bd8a75637..e86ca37b99ca 100644 --- a/core/java/android/app/servertransaction/ObjectPool.java +++ b/core/java/android/app/servertransaction/ObjectPool.java @@ -16,70 +16,39 @@ package android.app.servertransaction; -import com.android.window.flags.Flags; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - /** * An object pool that can provide reused objects if available. + * * @hide + * @deprecated This class is deprecated. Directly create new instances of objects instead of + * obtaining them from this pool. + * TODO(b/311089192): Clean up usages of the pool. */ +@Deprecated class ObjectPool { - private static final Object sPoolSync = new Object(); - private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap = - new HashMap<>(); - - private static final int MAX_POOL_SIZE = 50; - /** * Obtain an instance of a specific class from the pool - * @param itemClass The class of the object we're looking for. + * + * @param ignoredItemClass The class of the object we're looking for. * @return An instance or null if there is none. + * @deprecated This method is deprecated. Directly create new instances of objects instead of + * obtaining them from this pool. */ - public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) { - if (Flags.disableObjectPool()) { - return null; - } - synchronized (sPoolSync) { - @SuppressWarnings("unchecked") - final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass); - if (itemPool != null && !itemPool.isEmpty()) { - return itemPool.remove(itemPool.size() - 1); - } - return null; - } + @Deprecated + public static <T extends ObjectPoolItem> T obtain(Class<T> ignoredItemClass) { + return null; } /** * Recycle the object to the pool. The object should be properly cleared before this. - * @param item The object to recycle. + * + * @param ignoredItem The object to recycle. * @see ObjectPoolItem#recycle() + * @deprecated This method is deprecated. The object pool is no longer used, so there's + * no need to recycle objects. */ - public static <T extends ObjectPoolItem> void recycle(T item) { - if (Flags.disableObjectPool()) { - return; - } - synchronized (sPoolSync) { - @SuppressWarnings("unchecked") - ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass()); - if (itemPool == null) { - itemPool = new ArrayList<>(); - sPoolMap.put(item.getClass(), itemPool); - } - // Check if the item is already in the pool - final int size = itemPool.size(); - for (int i = 0; i < size; i++) { - if (itemPool.get(i) == item) { - throw new IllegalStateException("Trying to recycle already recycled item"); - } - } - - if (size < MAX_POOL_SIZE) { - itemPool.add(item); - } - } + @Deprecated + public static <T extends ObjectPoolItem> void recycle(T ignoredItem) { } } diff --git a/core/java/android/app/servertransaction/ObjectPoolItem.java b/core/java/android/app/servertransaction/ObjectPoolItem.java index 17bd4f30640f..0141f6eff53b 100644 --- a/core/java/android/app/servertransaction/ObjectPoolItem.java +++ b/core/java/android/app/servertransaction/ObjectPoolItem.java @@ -18,12 +18,20 @@ package android.app.servertransaction; /** * Base interface for all lifecycle items that can be put in object pool. + * * @hide + * @deprecated This interface is deprecated. Objects should no longer be pooled. + * TODO(b/311089192): Clean up usages of this interface. */ +@Deprecated public interface ObjectPoolItem { /** * Clear the contents of the item and putting it to a pool. The implementation should call * {@link ObjectPool#recycle(ObjectPoolItem)} passing itself. + * + * @deprecated This method is deprecated. The object pool is no longer used, so there's + * no need to recycle objects. */ + @Deprecated void recycle(); } diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java index 67e21957839a..c7f8878f104e 100644 --- a/core/java/android/os/AggregateBatteryConsumer.java +++ b/core/java/android/os/AggregateBatteryConsumer.java @@ -55,7 +55,7 @@ public final class AggregateBatteryConsumer extends BatteryConsumer { @Override public void dump(PrintWriter pw, boolean skipEmptyComponents) { - mPowerComponents.dump(pw, skipEmptyComponents); + mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents); } @Override diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 744f6a8d61ac..2447ff93fdbc 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -19,14 +19,19 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.database.Cursor; import android.database.CursorWindow; +import android.util.IntArray; import android.util.Slog; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * Interface for objects containing battery attribution data. @@ -192,31 +197,106 @@ public abstract class BatteryConsumer { sProcessStateNames[PROCESS_STATE_CACHED] = "cached"; } - private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = { - POWER_COMPONENT_CPU, - POWER_COMPONENT_MOBILE_RADIO, - POWER_COMPONENT_WIFI, - POWER_COMPONENT_BLUETOOTH, - POWER_COMPONENT_AUDIO, - POWER_COMPONENT_VIDEO, - POWER_COMPONENT_FLASHLIGHT, - POWER_COMPONENT_CAMERA, - POWER_COMPONENT_GNSS, + private static final IntArray SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE; + static { + int[] supportedPowerComponents = { + POWER_COMPONENT_CPU, + POWER_COMPONENT_MOBILE_RADIO, + POWER_COMPONENT_WIFI, + POWER_COMPONENT_BLUETOOTH, + POWER_COMPONENT_AUDIO, + POWER_COMPONENT_VIDEO, + POWER_COMPONENT_FLASHLIGHT, + POWER_COMPONENT_CAMERA, + POWER_COMPONENT_GNSS}; + Arrays.sort(supportedPowerComponents); + SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = IntArray.wrap(supportedPowerComponents); }; static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0; static final int COLUMN_COUNT = 1; /** + * Identifiers of consumed power aggregations per SCREEN state. + * + * @hide + */ + @IntDef(prefix = {"SCREEN_STATE_"}, value = { + SCREEN_STATE_UNSPECIFIED, + SCREEN_STATE_ANY, + SCREEN_STATE_ON, + SCREEN_STATE_OTHER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScreenState { + } + + public static final int SCREEN_STATE_UNSPECIFIED = 0; + public static final int SCREEN_STATE_ANY = SCREEN_STATE_UNSPECIFIED; + public static final int SCREEN_STATE_ON = 1; + public static final int SCREEN_STATE_OTHER = 2; // Off, doze etc + + public static final int SCREEN_STATE_COUNT = 3; + + private static final String[] sScreenStateNames = new String[SCREEN_STATE_COUNT]; + + static { + // Assign individually to avoid future mismatch + sScreenStateNames[SCREEN_STATE_UNSPECIFIED] = "unspecified"; + sScreenStateNames[SCREEN_STATE_ON] = "on"; + sScreenStateNames[SCREEN_STATE_OTHER] = "off/doze"; + } + + /** + * Identifiers of consumed power aggregations per POWER state. + * + * @hide + */ + @IntDef(prefix = {"POWER_STATE_"}, value = { + POWER_STATE_UNSPECIFIED, + POWER_STATE_ANY, + POWER_STATE_BATTERY, + POWER_STATE_OTHER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PowerState { + } + + public static final int POWER_STATE_UNSPECIFIED = 0; + public static final int POWER_STATE_ANY = POWER_STATE_UNSPECIFIED; + public static final int POWER_STATE_BATTERY = 1; + public static final int POWER_STATE_OTHER = 2; // Plugged in, or on wireless charger, etc. + + public static final int POWER_STATE_COUNT = 3; + + private static final String[] sPowerStateNames = new String[POWER_STATE_COUNT]; + + static { + // Assign individually to avoid future mismatch + sPowerStateNames[POWER_STATE_UNSPECIFIED] = "unspecified"; + sPowerStateNames[POWER_STATE_BATTERY] = "on battery"; + sPowerStateNames[POWER_STATE_OTHER] = "not on battery"; + } + + /** * Identifies power attribution dimensions that a caller is interested in. */ public static final class Dimensions { public final @PowerComponent int powerComponent; public final @ProcessState int processState; + public final @ScreenState int screenState; + public final @PowerState int powerState; - public Dimensions(int powerComponent, int processState) { + public Dimensions(@PowerComponent int powerComponent, @ProcessState int processState) { + this(powerComponent, processState, SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED); + } + + public Dimensions(@PowerComponent int powerComponent, int processState, + @ScreenState int screenState, @PowerState int powerState) { this.powerComponent = powerComponent; this.processState = processState; + this.screenState = screenState; + this.powerState = powerState; } @Override @@ -234,6 +314,20 @@ public abstract class BatteryConsumer { sb.append("processState=").append(sProcessStateNames[processState]); dimensionSpecified = true; } + if (screenState != SCREEN_STATE_ANY) { + if (dimensionSpecified) { + sb.append(", "); + } + sb.append("screenState=").append(screenStateToString(screenState)); + dimensionSpecified = true; + } + if (powerState != POWER_STATE_ANY) { + if (dimensionSpecified) { + sb.append(", "); + } + sb.append("powerState=").append(powerStateToString(powerState)); + dimensionSpecified = true; + } if (!dimensionSpecified) { sb.append("any components and process states"); } @@ -242,7 +336,8 @@ public abstract class BatteryConsumer { } public static final Dimensions UNSPECIFIED_DIMENSIONS = - new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY); + new Dimensions(POWER_COMPONENT_ANY, PROCESS_STATE_ANY, SCREEN_STATE_ANY, + POWER_STATE_ANY); /** * Identifies power attribution dimensions that are captured by a data element of @@ -258,52 +353,93 @@ public abstract class BatteryConsumer { public static final class Key { public final @PowerComponent int powerComponent; public final @ProcessState int processState; + public final @ScreenState int screenState; + public final @PowerState int powerState; final int mPowerModelColumnIndex; final int mPowerColumnIndex; final int mDurationColumnIndex; - private String mShortString; - private Key(int powerComponent, int processState, int powerModelColumnIndex, + private Key(@PowerComponent int powerComponent, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex, int powerColumnIndex, int durationColumnIndex) { this.powerComponent = powerComponent; this.processState = processState; + this.screenState = screenState; + this.powerState = powerState; mPowerModelColumnIndex = powerModelColumnIndex; mPowerColumnIndex = powerColumnIndex; mDurationColumnIndex = durationColumnIndex; } + /** + * Returns true if this key should be included in an enumeration parameterized with + * the supplied dimensions. + */ + boolean matches(@PowerComponent int powerComponent, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState) { + if (powerComponent != POWER_COMPONENT_ANY && this.powerComponent != powerComponent) { + return false; + } + if (processState != PROCESS_STATE_ANY && this.processState != processState) { + return false; + } + if (screenState != SCREEN_STATE_ANY && this.screenState != screenState) { + return false; + } + if (powerState != POWER_STATE_ANY && this.powerState != powerState) { + return false; + } + return true; + } + @SuppressWarnings("EqualsUnsafeCast") @Override public boolean equals(Object o) { // Skipping null and class check for performance final Key key = (Key) o; return powerComponent == key.powerComponent - && processState == key.processState; + && processState == key.processState + && screenState == key.screenState + && powerState == key.powerState; } @Override public int hashCode() { int result = powerComponent; result = 31 * result + processState; + result = 31 * result + screenState; + result = 31 * result + powerState; return result; } /** * Returns a string suitable for use in dumpsys. */ - public String toShortString() { - if (mShortString == null) { - StringBuilder sb = new StringBuilder(); - sb.append(powerComponentIdToString(powerComponent)); - if (processState != PROCESS_STATE_UNSPECIFIED) { - sb.append(':'); - sb.append(processStateToString(processState)); - } - mShortString = sb.toString(); + public static String toString(@PowerComponent int powerComponent, + @ProcessState int processState, @ScreenState int screenState, + @PowerState int powerState) { + StringBuilder sb = new StringBuilder(); + sb.append(powerComponentIdToString(powerComponent)); + if (processState != PROCESS_STATE_UNSPECIFIED) { + sb.append(':'); + sb.append(processStateToString(processState)); + } + if (screenState != SCREEN_STATE_UNSPECIFIED) { + sb.append(":scr-"); + sb.append(sScreenStateNames[screenState]); + } + if (powerState != POWER_STATE_UNSPECIFIED) { + sb.append(":pwr-"); + sb.append(sPowerStateNames[powerState]); } - return mShortString; + return sb.toString(); + } + + @Override + public String toString() { + return toString(powerComponent, processState, screenState, powerState); } } @@ -335,11 +471,18 @@ public abstract class BatteryConsumer { } /** + * Returns the amount of usage time aggregated over the specified dimensions, in millis. + */ + public long getUsageDurationMillis(@NonNull Dimensions dimensions) { + return mPowerComponents.getUsageDurationMillis(dimensions); + } + + /** * Returns keys for various power values attributed to the specified component * held by this BatteryUsageStats object. */ public Key[] getKeys(@PowerComponent int componentId) { - return mData.getKeys(componentId); + return mData.layout.getKeys(componentId); } /** @@ -347,14 +490,16 @@ public abstract class BatteryConsumer { * for all values of other dimensions such as process state. */ public Key getKey(@PowerComponent int componentId) { - return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED); + return mData.layout.getKey(componentId, PROCESS_STATE_UNSPECIFIED, SCREEN_STATE_UNSPECIFIED, + POWER_STATE_UNSPECIFIED); } /** * Returns the key for the power attributed to the specified component and process state. */ public Key getKey(@PowerComponent int componentId, @ProcessState int processState) { - return mData.getKey(componentId, processState); + return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED, + POWER_STATE_UNSPECIFIED); } /** @@ -365,8 +510,8 @@ public abstract class BatteryConsumer { * @return Amount of consumed power in mAh. */ public double getConsumedPower(@PowerComponent int componentId) { - return mPowerComponents.getConsumedPower( - mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED)); + return mPowerComponents.getConsumedPower(componentId, PROCESS_STATE_UNSPECIFIED, + SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED); } /** @@ -388,7 +533,8 @@ public abstract class BatteryConsumer { */ public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) { return mPowerComponents.getPowerModel( - mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED)); + mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED, + SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED)); } /** @@ -507,6 +653,20 @@ public abstract class BatteryConsumer { } /** + * Returns the human-readable name of the specified power state (on battery or not) + */ + public static String powerStateToString(@PowerState int powerState) { + return sPowerStateNames[powerState]; + } + + /** + * Returns the human-readable name of the specified screen state (on or off/doze) + */ + public static String screenStateToString(@ScreenState int screenState) { + return sScreenStateNames[screenState]; + } + + /** * Prints the stats in a human-readable format. */ public void dump(PrintWriter pw) { @@ -591,42 +751,11 @@ public abstract class BatteryConsumer { return new BatteryConsumerData(cursorWindow, cursorRow, layout); } - public Key[] getKeys(int componentId) { - return layout.keys[componentId]; - } - - Key getKeyOrThrow(int componentId, int processState) { - Key key = getKey(componentId, processState); - if (key == null) { - if (processState == PROCESS_STATE_ANY) { - throw new IllegalArgumentException( - "Unsupported power component ID: " + componentId); - } else { - throw new IllegalArgumentException( - "Unsupported power component ID: " + componentId - + " process state: " + processState); - } - } - return key; - } - - Key getKey(int componentId, int processState) { - if (componentId >= POWER_COMPONENT_COUNT) { - return null; - } - - if (processState == PROCESS_STATE_ANY) { - // The 0-th key for each component corresponds to the roll-up, - // across all dimensions. We might as well skip the iteration over the array. - return layout.keys[componentId][0]; - } else { - for (Key key : layout.keys[componentId]) { - if (key.processState == processState) { - return key; - } - } + boolean hasValue(int columnIndex) { + if (mCursorRow == -1) { + return false; } - return null; + return mCursorWindow.getType(mCursorRow, columnIndex) != Cursor.FIELD_TYPE_NULL; } void putInt(int columnIndex, int value) { @@ -693,113 +822,158 @@ public abstract class BatteryConsumer { public final int customPowerComponentCount; public final boolean powerModelsIncluded; public final boolean processStateDataIncluded; - public final Key[][] keys; + public final boolean screenStateDataIncluded; + public final boolean powerStateDataIncluded; + public final Key[] keys; + public final SparseArray<Key> indexedKeys; public final int totalConsumedPowerColumnIndex; public final int firstCustomConsumedPowerColumn; public final int firstCustomUsageDurationColumn; public final int columnCount; - public final Key[][] processStateKeys; + private Key[][] mPerComponentKeys; private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames, - boolean powerModelsIncluded, boolean includeProcessStateData) { + boolean powerModelsIncluded, boolean includeProcessStateData, + boolean includeScreenState, boolean includePowerState) { this.customPowerComponentNames = customPowerComponentNames; this.customPowerComponentCount = customPowerComponentNames.length; this.powerModelsIncluded = powerModelsIncluded; this.processStateDataIncluded = includeProcessStateData; + this.screenStateDataIncluded = includeScreenState; + this.powerStateDataIncluded = includePowerState; int columnIndex = firstColumn; totalConsumedPowerColumnIndex = columnIndex++; - keys = new Key[POWER_COMPONENT_COUNT][]; + ArrayList<Key> keyList = new ArrayList<>(); + for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) { + if (!includeScreenState && screenState != SCREEN_STATE_UNSPECIFIED) { + continue; + } + for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) { + if (!includePowerState && powerState != POWER_STATE_UNSPECIFIED) { + continue; + } + for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) { + columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData, + componentId, screenState, powerState, columnIndex); + } + } + } + + firstCustomConsumedPowerColumn = columnIndex; + columnIndex += customPowerComponentCount; + + firstCustomUsageDurationColumn = columnIndex; + columnIndex += customPowerComponentCount; + + columnCount = columnIndex; - ArrayList<Key> perComponentKeys = new ArrayList<>(); - for (int componentId = 0; componentId < POWER_COMPONENT_COUNT; componentId++) { - perComponentKeys.clear(); + keys = keyList.toArray(KEY_ARRAY); + indexedKeys = new SparseArray<>(keys.length); + for (int i = 0; i < keys.length; i++) { + Key key = keys[i]; + int index = keyIndex(key.powerComponent, key.processState, key.screenState, + key.powerState); + indexedKeys.put(index, key); + } + } - // Declare the Key for the power component, ignoring other dimensions. - perComponentKeys.add( - new Key(componentId, PROCESS_STATE_ANY, + private int addKeys(List<Key> keys, boolean powerModelsIncluded, + boolean includeProcessStateData, int componentId, + int screenState, int powerState, int columnIndex) { + keys.add(new Key(componentId, PROCESS_STATE_ANY, screenState, powerState, + powerModelsIncluded + ? columnIndex++ + : POWER_MODEL_NOT_INCLUDED, // power model + columnIndex++, // power + columnIndex++ // usage duration + )); + + // Declare Keys for all process states, if needed + if (includeProcessStateData) { + boolean isSupported = SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE + .binarySearch(componentId) >= 0; + if (isSupported) { + for (int processState = 0; processState < PROCESS_STATE_COUNT; + processState++) { + if (processState == PROCESS_STATE_UNSPECIFIED) { + continue; + } + + keys.add(new Key(componentId, processState, screenState, powerState, powerModelsIncluded ? columnIndex++ - : POWER_MODEL_NOT_INCLUDED, // power model + : POWER_MODEL_NOT_INCLUDED, // power model columnIndex++, // power columnIndex++ // usage duration )); - - // Declare Keys for all process states, if needed - if (includeProcessStateData) { - boolean isSupported = false; - for (int id : SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE) { - if (id == componentId) { - isSupported = true; - break; - } - } - if (isSupported) { - for (int processState = 0; processState < PROCESS_STATE_COUNT; - processState++) { - if (processState == PROCESS_STATE_UNSPECIFIED) { - continue; - } - - perComponentKeys.add( - new Key(componentId, processState, - powerModelsIncluded - ? columnIndex++ - : POWER_MODEL_NOT_INCLUDED, // power model - columnIndex++, // power - columnIndex++ // usage duration - )); - } } } - - keys[componentId] = perComponentKeys.toArray(KEY_ARRAY); } + return columnIndex; + } - if (includeProcessStateData) { - processStateKeys = new Key[BatteryConsumer.PROCESS_STATE_COUNT][]; - ArrayList<Key> perProcStateKeys = new ArrayList<>(); - for (int processState = 0; processState < PROCESS_STATE_COUNT; processState++) { - if (processState == PROCESS_STATE_UNSPECIFIED) { - continue; - } + Key getKey(@PowerComponent int componentId, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState) { + return indexedKeys.get(keyIndex(componentId, processState, screenState, powerState)); + } - perProcStateKeys.clear(); - for (int i = 0; i < keys.length; i++) { - for (int j = 0; j < keys[i].length; j++) { - if (keys[i][j].processState == processState) { - perProcStateKeys.add(keys[i][j]); - } + Key getKeyOrThrow(@PowerComponent int componentId, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState) { + Key key = getKey(componentId, processState, screenState, powerState); + if (key == null) { + throw new IllegalArgumentException( + "Unsupported power component ID: " + Key.toString(componentId, processState, + screenState, powerState)); + } + return key; + } + + public Key[] getKeys(@PowerComponent int componentId) { + synchronized (this) { + if (mPerComponentKeys == null) { + mPerComponentKeys = new Key[BatteryConsumer.POWER_COMPONENT_COUNT][]; + } + Key[] componentKeys = mPerComponentKeys[componentId]; + if (componentKeys == null) { + ArrayList<Key> out = new ArrayList<>(); + for (Key key : keys) { + if (key.powerComponent == componentId) { + out.add(key); } } - processStateKeys[processState] = perProcStateKeys.toArray(KEY_ARRAY); + componentKeys = out.toArray(new Key[out.size()]); + mPerComponentKeys[componentId] = componentKeys; } - } else { - processStateKeys = null; + return componentKeys; } + } - firstCustomConsumedPowerColumn = columnIndex; - columnIndex += customPowerComponentCount; - - firstCustomUsageDurationColumn = columnIndex; - columnIndex += customPowerComponentCount; - - columnCount = columnIndex; + private int keyIndex(@PowerComponent int componentId, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState) { + // [CCCCCCPPPSSBB] + // C - component ID + // P - process state + // S - screen state + // B - power state + return componentId << 7 | processState << 4 | screenState << 2 | powerState; } } static BatteryConsumerDataLayout createBatteryConsumerDataLayout( String[] customPowerComponentNames, boolean includePowerModels, - boolean includeProcessStateData) { + boolean includeProcessStateData, boolean includeScreenStateData, + boolean includePowerStateData) { int columnCount = BatteryConsumer.COLUMN_COUNT; columnCount = Math.max(columnCount, AggregateBatteryConsumer.COLUMN_COUNT); columnCount = Math.max(columnCount, UidBatteryConsumer.COLUMN_COUNT); columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT); return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames, - includePowerModels, includeProcessStateData); + includePowerModels, includeProcessStateData, includeScreenStateData, + includePowerStateData); } protected abstract static class BaseBuilder<T extends BaseBuilder<?>> { @@ -816,12 +990,19 @@ public abstract class BatteryConsumer { @Nullable public Key[] getKeys(@PowerComponent int componentId) { - return mData.getKeys(componentId); + return mData.layout.getKeys(componentId); } @Nullable public Key getKey(@PowerComponent int componentId, @ProcessState int processState) { - return mData.getKey(componentId, processState); + return mData.layout.getKey(componentId, processState, SCREEN_STATE_UNSPECIFIED, + POWER_STATE_UNSPECIFIED); + } + + @Nullable + public Key getKey(@PowerComponent int componentId, @ProcessState int processState, + @ScreenState int screenState, @PowerState int powerState) { + return mData.layout.getKey(componentId, processState, screenState, powerState); } /** diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 61cc23d994f3..dd484f6bace5 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -102,9 +102,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable { static final String XML_ATTR_SCOPE = "scope"; static final String XML_ATTR_PREFIX_CUSTOM_COMPONENT = "custom_component_"; static final String XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA = "includes_proc_state_data"; + static final String XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA = "includes_screen_state_data"; + static final String XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA = "includes_power_state_data"; static final String XML_ATTR_START_TIMESTAMP = "start_timestamp"; static final String XML_ATTR_END_TIMESTAMP = "end_timestamp"; static final String XML_ATTR_PROCESS_STATE = "process_state"; + static final String XML_ATTR_SCREEN_STATE = "screen_state"; + static final String XML_ATTR_POWER_STATE = "power_state"; static final String XML_ATTR_POWER = "power"; static final String XML_ATTR_DURATION = "duration"; static final String XML_ATTR_MODEL = "model"; @@ -144,10 +148,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final String[] mCustomPowerComponentNames; private final boolean mIncludesPowerModels; private final boolean mIncludesProcessStateData; + private final boolean mIncludesScreenStateData; + private final boolean mIncludesPowerStateData; private final List<UidBatteryConsumer> mUidBatteryConsumers; private final List<UserBatteryConsumer> mUserBatteryConsumers; private final AggregateBatteryConsumer[] mAggregateBatteryConsumers; private final BatteryStatsHistory mBatteryStatsHistory; + private BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout; private CursorWindow mBatteryConsumersCursorWindow; private BatteryUsageStats(@NonNull Builder builder) { @@ -165,6 +172,9 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mCustomPowerComponentNames = builder.mCustomPowerComponentNames; mIncludesPowerModels = builder.mIncludePowerModels; mIncludesProcessStateData = builder.mIncludesProcessStateData; + mIncludesScreenStateData = builder.mIncludesScreenStateData; + mIncludesPowerStateData = builder.mIncludesPowerStateData; + mBatteryConsumerDataLayout = builder.mBatteryConsumerDataLayout; mBatteryConsumersCursorWindow = builder.mBatteryConsumersCursorWindow; double totalPowerMah = 0; @@ -347,11 +357,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable { mCustomPowerComponentNames = source.readStringArray(); mIncludesPowerModels = source.readBoolean(); mIncludesProcessStateData = source.readBoolean(); + mIncludesScreenStateData = source.readBoolean(); + mIncludesPowerStateData = source.readBoolean(); mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source); - BatteryConsumer.BatteryConsumerDataLayout dataLayout = - BatteryConsumer.createBatteryConsumerDataLayout(mCustomPowerComponentNames, - mIncludesPowerModels, mIncludesProcessStateData); + mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout( + mCustomPowerComponentNames, mIncludesPowerModels, mIncludesProcessStateData, + mIncludesScreenStateData, mIncludesPowerStateData); final int numRows = mBatteryConsumersCursorWindow.getNumRows(); @@ -363,7 +375,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { for (int i = 0; i < numRows; i++) { final BatteryConsumer.BatteryConsumerData data = new BatteryConsumer.BatteryConsumerData(mBatteryConsumersCursorWindow, i, - dataLayout); + mBatteryConsumerDataLayout); int consumerType = mBatteryConsumersCursorWindow.getInt(i, BatteryConsumer.COLUMN_INDEX_BATTERY_CONSUMER_TYPE); @@ -405,6 +417,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { dest.writeStringArray(mCustomPowerComponentNames); dest.writeBoolean(mIncludesPowerModels); dest.writeBoolean(mIncludesProcessStateData); + dest.writeBoolean(mIncludesScreenStateData); + dest.writeBoolean(mIncludesPowerStateData); mBatteryConsumersCursorWindow.writeToParcel(dest, flags); @@ -598,23 +612,16 @@ public final class BatteryUsageStats implements Parcelable, Closeable { for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; componentId++) { - for (BatteryConsumer.Key key : deviceConsumer.getKeys(componentId)) { - final double devicePowerMah = deviceConsumer.getConsumedPower(key); - final double appsPowerMah = appsConsumer.getConsumedPower(key); - if (devicePowerMah == 0 && appsPowerMah == 0) { - continue; - } - - String label = BatteryConsumer.powerComponentIdToString(componentId); - if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { - label = label - + "(" + BatteryConsumer.processStateToString(key.processState) + ")"; - } - printPowerComponent(pw, prefix, label, devicePowerMah, appsPowerMah, - mIncludesPowerModels ? deviceConsumer.getPowerModel(key) - : BatteryConsumer.POWER_MODEL_UNDEFINED, - deviceConsumer.getUsageDurationMillis(key)); + final double devicePowerMah = deviceConsumer.getConsumedPower(componentId); + final double appsPowerMah = appsConsumer.getConsumedPower(componentId); + if (devicePowerMah == 0 && appsPowerMah == 0) { + continue; } + + printPowerComponent(pw, prefix, BatteryConsumer.powerComponentIdToString(componentId), + devicePowerMah, appsPowerMah, + BatteryConsumer.POWER_MODEL_UNDEFINED, + deviceConsumer.getUsageDurationMillis(componentId)); } for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; @@ -635,6 +642,59 @@ public final class BatteryUsageStats implements Parcelable, Closeable { deviceConsumer.getUsageDurationForCustomComponentMillis(componentId)); } + if (mIncludesScreenStateData || mIncludesPowerStateData) { + String prefixPlus = prefix + " "; + StringBuilder stateLabel = new StringBuilder(); + int screenState = BatteryConsumer.SCREEN_STATE_UNSPECIFIED; + int powerState = BatteryConsumer.POWER_STATE_UNSPECIFIED; + for (BatteryConsumer.Key key : mBatteryConsumerDataLayout.keys) { + if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + continue; + } + + if (key.screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED + && key.powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) { + // Totals already printed earlier in this method + continue; + } + + final double devicePowerMah = deviceConsumer.getConsumedPower(key); + final double appsPowerMah = appsConsumer.getConsumedPower(key); + if (devicePowerMah == 0 && appsPowerMah == 0) { + continue; + } + + if (key.screenState != screenState || key.powerState != powerState) { + screenState = key.screenState; + powerState = key.powerState; + + boolean empty = true; + stateLabel.setLength(0); + stateLabel.append(" ("); + if (powerState != BatteryConsumer.POWER_STATE_UNSPECIFIED) { + stateLabel.append(BatteryConsumer.powerStateToString(powerState)); + empty = false; + } + if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { + if (!empty) { + stateLabel.append(", "); + } + stateLabel.append("screen ").append( + BatteryConsumer.screenStateToString(screenState)); + empty = false; + } + if (!empty) { + stateLabel.append(")"); + pw.println(stateLabel); + } + } + String label = BatteryConsumer.powerComponentIdToString(key.powerComponent); + printPowerComponent(pw, prefixPlus, label, devicePowerMah, appsPowerMah, + mIncludesPowerModels ? deviceConsumer.getPowerModel(key) + : BatteryConsumer.POWER_MODEL_UNDEFINED, + deviceConsumer.getUsageDurationMillis(key)); + } + } dumpSortedBatteryConsumers(pw, prefix, getUidBatteryConsumers()); dumpSortedBatteryConsumers(pw, prefix, getUserBatteryConsumers()); pw.println(); @@ -643,7 +703,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private void printPowerComponent(PrintWriter pw, String prefix, String label, double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) { StringBuilder sb = new StringBuilder(); - sb.append(prefix).append(" ").append(label).append(": ") + sb.append(prefix).append(" ").append(label).append(": ") .append(BatteryStats.formatCharge(devicePowerMah)); if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) { @@ -657,7 +717,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { BatteryStats.formatTimeMs(sb, durationMs); } - pw.println(sb.toString()); + pw.println(sb); } private void dumpSortedBatteryConsumers(PrintWriter pw, String prefix, @@ -670,9 +730,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { continue; } pw.print(prefix); - pw.print(" "); + pw.print(" "); consumer.dump(pw); - pw.println(); } } @@ -686,6 +745,10 @@ public final class BatteryUsageStats implements Parcelable, Closeable { } serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, mIncludesProcessStateData); + serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA, + mIncludesScreenStateData); + serializer.attributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, + mIncludesPowerStateData); serializer.attributeLong(null, XML_ATTR_START_TIMESTAMP, mStatsStartTimestampMs); serializer.attributeLong(null, XML_ATTR_END_TIMESTAMP, mStatsEndTimestampMs); serializer.attributeLong(null, XML_ATTR_DURATION, mStatsDurationMs); @@ -732,9 +795,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable { final boolean includesProcStateData = parser.getAttributeBoolean(null, XML_ATTR_PREFIX_INCLUDES_PROC_STATE_DATA, false); + final boolean includesScreenStateData = parser.getAttributeBoolean(null, + XML_ATTR_PREFIX_INCLUDES_SCREEN_STATE_DATA, false); + final boolean includesPowerStateData = parser.getAttributeBoolean(null, + XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, false); builder = new Builder(customComponentNames.toArray(new String[0]), true, - includesProcStateData, 0); + includesProcStateData, includesScreenStateData, includesPowerStateData, 0); builder.setStatsStartTimestamp( parser.getAttributeLong(null, XML_ATTR_START_TIMESTAMP)); @@ -818,6 +885,8 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private final String[] mCustomPowerComponentNames; private final boolean mIncludePowerModels; private final boolean mIncludesProcessStateData; + private final boolean mIncludesScreenStateData; + private final boolean mIncludesPowerStateData; private final double mMinConsumedPowerThreshold; private final BatteryConsumer.BatteryConsumerDataLayout mBatteryConsumerDataLayout; private long mStatsStartTimestampMs; @@ -839,21 +908,24 @@ public final class BatteryUsageStats implements Parcelable, Closeable { private BatteryStatsHistory mBatteryStatsHistory; public Builder(@NonNull String[] customPowerComponentNames) { - this(customPowerComponentNames, false, false, 0); + this(customPowerComponentNames, false, false, false, false, 0); } public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels, - boolean includeProcessStateData, double minConsumedPowerThreshold) { + boolean includeProcessStateData, boolean includeScreenStateData, + boolean includesPowerStateData, double minConsumedPowerThreshold) { mBatteryConsumersCursorWindow = new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE); - mBatteryConsumerDataLayout = - BatteryConsumer.createBatteryConsumerDataLayout(customPowerComponentNames, - includePowerModels, includeProcessStateData); + mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout( + customPowerComponentNames, includePowerModels, includeProcessStateData, + includeScreenStateData, includesPowerStateData); mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount); mCustomPowerComponentNames = customPowerComponentNames; mIncludePowerModels = includePowerModels; mIncludesProcessStateData = includeProcessStateData; + mIncludesScreenStateData = includeScreenStateData; + mIncludesPowerStateData = includesPowerStateData; mMinConsumedPowerThreshold = minConsumedPowerThreshold; for (int scope = 0; scope < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; scope++) { final BatteryConsumer.BatteryConsumerData data = @@ -869,6 +941,14 @@ public final class BatteryUsageStats implements Parcelable, Closeable { return mIncludesProcessStateData; } + public boolean isScreenStateDataNeeded() { + return mIncludesScreenStateData; + } + + public boolean isPowerStateDataNeeded() { + return mIncludesPowerStateData; + } + /** * Returns true if this Builder is configured to hold data for the specified * custom power component ID. diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 203ef47d857e..d0ed297d2bda 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -73,6 +73,10 @@ public final class BatteryUsageStatsQuery implements Parcelable { public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010; + public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE = 0x0020; + + public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE = 0x0040; + private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000; private final int mFlags; @@ -123,6 +127,14 @@ public final class BatteryUsageStatsQuery implements Parcelable { return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0; } + public boolean isScreenStateDataNeeded() { + return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0; + } + + public boolean isPowerStateDataNeeded() { + return (mFlags & FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0; + } + /** * Returns the power components that should be estimated or null if all power components * are being requested. @@ -297,6 +309,24 @@ public final class BatteryUsageStatsQuery implements Parcelable { } /** + * Requests that screen state data (screen-on, screen-other) be included in the + * BatteryUsageStats, if available. + */ + public Builder includeScreenStateData() { + mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE; + return this; + } + + /** + * Requests that power state data (on-battery, power-other) be included in the + * BatteryUsageStats, if available. + */ + public Builder includePowerStateData() { + mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE; + return this; + } + + /** * Requests to aggregate stored snapshots between the two supplied timestamps * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis() diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index a49ee7d00751..0c34c6fea1d0 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -110,92 +110,6 @@ interface INetworkManagementService void shutdown(); /** - ** TETHERING RELATED - **/ - - /** - * Returns true if IP forwarding is enabled - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}") - boolean getIpForwardingEnabled(); - - /** - * Enables/Disables IP Forwarding - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Avoid using this directly. Instead, enable tethering with " - + "{@code android.net.TetheringManager#startTethering}. See also " - + "{@code INetd#ipfwdEnableForwarding(String)}.") - void setIpForwardingEnabled(boolean enabled); - - /** - * Start tethering services with the specified dhcp server range - * arg is a set of start end pairs defining the ranges. - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "{@code android.net.TetheringManager#startTethering}") - void startTethering(in String[] dhcpRanges); - - /** - * Stop currently running tethering services - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}") - void stopTethering(); - - /** - * Returns true if tethering services are started - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Generally track your own tethering requests. " - + "See also {@code android.net.INetd#tetherIsEnabled()}") - boolean isTetheringStarted(); - - /** - * Tethers the specified interface - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Avoid using this directly. Instead, enable tethering with " - + "{@code android.net.TetheringManager#startTethering}. See also " - + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.") - void tetherInterface(String iface); - - /** - * Untethers the specified interface - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Avoid using this directly. Instead, disable " - + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. " - + "See also {@code NetdUtils#untetherInterface}.") - void untetherInterface(String iface); - - /** - * Returns a list of currently tethered interfaces - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}") - String[] listTetheredInterfaces(); - - /** - * Enables Network Address Translation between two interfaces. - * The address and netmask of the external interface is used for - * the NAT'ed network. - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Avoid using this directly. Instead, enable tethering with " - + "{@code android.net.TetheringManager#startTethering}.") - void enableNat(String internalInterface, String externalInterface); - - /** - * Disables Network Address Translation between two interfaces. - */ - @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553, - publicAlternatives = "Avoid using this directly. Instead, disable tethering with " - + "{@code android.net.TetheringManager#stopTethering(int)}.") - void disableNat(String internalInterface, String externalInterface); - - /** ** DATA USAGE RELATED **/ diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java index b035f123d5db..f22e1eac9643 100644 --- a/core/java/android/os/PowerComponents.java +++ b/core/java/android/os/PowerComponents.java @@ -17,8 +17,12 @@ package android.os; import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED; import static android.os.BatteryConsumer.POWER_COMPONENT_ANY; +import static android.os.BatteryConsumer.POWER_STATE_ANY; +import static android.os.BatteryConsumer.POWER_STATE_UNSPECIFIED; import static android.os.BatteryConsumer.PROCESS_STATE_ANY; import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED; +import static android.os.BatteryConsumer.SCREEN_STATE_ANY; +import static android.os.BatteryConsumer.SCREEN_STATE_UNSPECIFIED; import static android.os.BatteryConsumer.convertMahToDeciCoulombs; import android.annotation.NonNull; @@ -56,23 +60,100 @@ class PowerComponents { * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh. */ public double getConsumedPower(@NonNull BatteryConsumer.Dimensions dimensions) { - if (dimensions.powerComponent != POWER_COMPONENT_ANY) { - return mData.getDouble(mData.getKeyOrThrow(dimensions.powerComponent, - dimensions.processState).mPowerColumnIndex); - } else if (dimensions.processState != PROCESS_STATE_ANY) { - if (!mData.layout.processStateDataIncluded) { - throw new IllegalArgumentException( - "No data included in BatteryUsageStats for " + dimensions); + return getConsumedPower(dimensions.powerComponent, dimensions.processState, + dimensions.screenState, dimensions.powerState); + } + + /** + * Total power consumed by this consumer, aggregated over the specified dimensions, in mAh. + */ + public double getConsumedPower(@BatteryConsumer.PowerComponent int powerComponent, + @BatteryConsumer.ProcessState int processState, + @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState) { + if (powerComponent == POWER_COMPONENT_ANY && processState == PROCESS_STATE_ANY + && screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) { + return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex); + } + + if (powerComponent != POWER_COMPONENT_ANY + && ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY) + || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY))) { + BatteryConsumer.Key key = mData.layout.getKey(powerComponent, + processState, screenState, powerState); + if (key != null) { + return mData.getDouble(key.mPowerColumnIndex); } - final BatteryConsumer.Key[] keys = - mData.layout.processStateKeys[dimensions.processState]; - double totalPowerMah = 0; - for (int i = keys.length - 1; i >= 0; i--) { - totalPowerMah += mData.getDouble(keys[i].mPowerColumnIndex); + return 0; + } + + if (mData.layout.processStateDataIncluded || mData.layout.screenStateDataIncluded + || mData.layout.powerStateDataIncluded) { + double total = 0; + for (BatteryConsumer.Key key : mData.layout.keys) { + if (key.processState != PROCESS_STATE_UNSPECIFIED + && key.matches(powerComponent, processState, screenState, powerState)) { + total += mData.getDouble(key.mPowerColumnIndex); + } } - return totalPowerMah; + if (total != 0) { + return total; + } + } + + BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState, + SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED); + if (key != null) { + return mData.getDouble(key.mPowerColumnIndex); } else { - return mData.getDouble(mData.layout.totalConsumedPowerColumnIndex); + return 0; + } + } + + /** + * Total usage duration by this consumer, aggregated over the specified dimensions, in ms. + */ + public long getUsageDurationMillis(@NonNull BatteryConsumer.Dimensions dimensions) { + return getUsageDurationMillis(dimensions.powerComponent, dimensions.processState, + dimensions.screenState, dimensions.powerState); + } + + /** + * Total usage duration by this consumer, aggregated over the specified dimensions, in ms. + */ + public long getUsageDurationMillis(@BatteryConsumer.PowerComponent int powerComponent, + @BatteryConsumer.ProcessState int processState, + @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState) { + if ((mData.layout.screenStateDataIncluded && screenState != SCREEN_STATE_ANY) + || (mData.layout.powerStateDataIncluded && powerState != POWER_STATE_ANY)) { + BatteryConsumer.Key key = mData.layout.getKey(powerComponent, + processState, screenState, powerState); + if (key != null) { + return mData.getLong(key.mDurationColumnIndex); + } + return 0; + } + + if (mData.layout.screenStateDataIncluded || mData.layout.powerStateDataIncluded) { + long total = 0; + for (BatteryConsumer.Key key : mData.layout.keys) { + if (key.processState != PROCESS_STATE_UNSPECIFIED + && key.matches(powerComponent, processState, screenState, powerState)) { + total += mData.getLong(key.mDurationColumnIndex); + } + } + if (total != 0) { + return total; + } + } + + BatteryConsumer.Key key = mData.layout.getKey(powerComponent, processState, + SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED); + if (key != null) { + return mData.getLong(key.mDurationColumnIndex); + } else { + return 0; } } @@ -84,7 +165,11 @@ class PowerComponents { * @return Amount of consumed power in mAh. */ public double getConsumedPower(@NonNull BatteryConsumer.Key key) { - return mData.getDouble(key.mPowerColumnIndex); + if (mData.hasValue(key.mPowerColumnIndex)) { + return mData.getDouble(key.mPowerColumnIndex); + } + return getConsumedPower(key.powerComponent, key.processState, key.screenState, + key.powerState); } /** @@ -135,7 +220,12 @@ class PowerComponents { * @return Amount of time in milliseconds. */ public long getUsageDurationMillis(BatteryConsumer.Key key) { - return mData.getLong(key.mDurationColumnIndex); + if (mData.hasValue(key.mDurationColumnIndex)) { + return mData.getLong(key.mDurationColumnIndex); + } + + return getUsageDurationMillis(key.powerComponent, key.processState, key.screenState, + key.powerState); } /** @@ -154,51 +244,77 @@ class PowerComponents { } } - public void dump(PrintWriter pw, boolean skipEmptyComponents) { - String separator = ""; + void dump(PrintWriter pw, @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) { StringBuilder sb = new StringBuilder(); - for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; componentId++) { - for (BatteryConsumer.Key key: mData.getKeys(componentId)) { - final double componentPower = getConsumedPower(key); - final long durationMs = getUsageDurationMillis(key); - if (skipEmptyComponents && componentPower == 0 && durationMs == 0) { - continue; + dump(sb, componentId, PROCESS_STATE_ANY, screenState, powerState, skipEmptyComponents); + if (mData.layout.processStateDataIncluded) { + for (int processState = 0; processState < BatteryConsumer.PROCESS_STATE_COUNT; + processState++) { + if (processState == PROCESS_STATE_UNSPECIFIED) { + continue; + } + dump(sb, componentId, processState, screenState, powerState, + skipEmptyComponents); } + } + } - sb.append(separator); - separator = " "; - sb.append(key.toShortString()); - sb.append("="); - sb.append(BatteryStats.formatCharge(componentPower)); - - if (durationMs != 0) { - sb.append(" ("); - BatteryStats.formatTimeMsNoSpace(sb, durationMs); - sb.append(")"); + // TODO(b/352835319): take into account screen and power states + if (screenState == SCREEN_STATE_ANY && powerState == POWER_STATE_ANY) { + final int customComponentCount = mData.layout.customPowerComponentCount; + for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + + customComponentCount; + customComponentId++) { + final double customComponentPower = + getConsumedPowerForCustomComponent(customComponentId); + if (skipEmptyComponents && customComponentPower == 0) { + continue; } + sb.append(getCustomPowerComponentName(customComponentId)); + sb.append("="); + sb.append(BatteryStats.formatCharge(customComponentPower)); + sb.append(" "); } } - final int customComponentCount = mData.layout.customPowerComponentCount; - for (int customComponentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; - customComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID - + customComponentCount; - customComponentId++) { - final double customComponentPower = - getConsumedPowerForCustomComponent(customComponentId); - if (skipEmptyComponents && customComponentPower == 0) { - continue; - } - sb.append(separator); - separator = " "; - sb.append(getCustomPowerComponentName(customComponentId)); - sb.append("="); - sb.append(BatteryStats.formatCharge(customComponentPower)); + // Remove trailing spaces + while (!sb.isEmpty() && Character.isWhitespace(sb.charAt(sb.length() - 1))) { + sb.setLength(sb.length() - 1); } - pw.print(sb); + pw.println(sb); + } + + private void dump(StringBuilder sb, @BatteryConsumer.PowerComponent int powerComponent, + @BatteryConsumer.ProcessState int processState, + @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState, boolean skipEmptyComponents) { + final double componentPower = getConsumedPower(powerComponent, processState, screenState, + powerState); + final long durationMs = getUsageDurationMillis(powerComponent, processState, screenState, + powerState); + if (skipEmptyComponents && componentPower == 0 && durationMs == 0) { + return; + } + + sb.append(BatteryConsumer.powerComponentIdToString(powerComponent)); + if (processState != PROCESS_STATE_UNSPECIFIED) { + sb.append(':'); + sb.append(BatteryConsumer.processStateToString(processState)); + } + sb.append("="); + sb.append(BatteryStats.formatCharge(componentPower)); + + if (durationMs != 0) { + sb.append(" ("); + BatteryStats.formatTimeMsNoSpace(sb, durationMs); + sb.append(")"); + } + sb.append(' '); } /** Returns whether there are any atoms.proto POWER_COMPONENTS data to write to a proto. */ @@ -220,11 +336,13 @@ class PowerComponents { for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; componentId++) { - - final BatteryConsumer.Key[] keys = mData.getKeys(componentId); + final BatteryConsumer.Key[] keys = mData.layout.getKeys(componentId); for (BatteryConsumer.Key key : keys) { - final long powerDeciCoulombs = convertMahToDeciCoulombs(getConsumedPower(key)); - final long durationMs = getUsageDurationMillis(key); + final long powerDeciCoulombs = convertMahToDeciCoulombs( + getConsumedPower(key.powerComponent, key.processState, key.screenState, + key.powerState)); + final long durationMs = getUsageDurationMillis(key.powerComponent, key.processState, + key.screenState, key.powerState); if (powerDeciCoulombs == 0 && durationMs == 0) { // No interesting data. Make sure not to even write the COMPONENT int. @@ -329,34 +447,43 @@ class PowerComponents { void writeToXml(TypedXmlSerializer serializer) throws IOException { serializer.startTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS); - for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; - componentId++) { - final BatteryConsumer.Key[] keys = mData.getKeys(componentId); - for (BatteryConsumer.Key key : keys) { - final double powerMah = getConsumedPower(key); - final long durationMs = getUsageDurationMillis(key); - if (powerMah == 0 && durationMs == 0) { - continue; - } + for (BatteryConsumer.Key key : mData.layout.keys) { + if (!mData.hasValue(key.mPowerColumnIndex) + && !mData.hasValue(key.mDurationColumnIndex)) { + continue; + } - serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT); - serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId); - if (key.processState != PROCESS_STATE_UNSPECIFIED) { - serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE, - key.processState); - } - if (powerMah != 0) { - serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); - } - if (durationMs != 0) { - serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); - } - if (mData.layout.powerModelsIncluded) { - serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL, - getPowerModel(key)); - } - serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + final double powerMah = getConsumedPower(key); + final long durationMs = getUsageDurationMillis(key); + if (powerMah == 0 && durationMs == 0) { + continue; + } + + serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT); + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, key.powerComponent); + if (key.processState != PROCESS_STATE_UNSPECIFIED) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE, + key.processState); + } + if (key.screenState != SCREEN_STATE_UNSPECIFIED) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_SCREEN_STATE, + key.screenState); + } + if (key.powerState != POWER_STATE_UNSPECIFIED) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_POWER_STATE, + key.powerState); + } + if (powerMah != 0) { + serializer.attributeDouble(null, BatteryUsageStats.XML_ATTR_POWER, powerMah); + } + if (durationMs != 0) { + serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs); + } + if (mData.layout.powerModelsIncluded) { + serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL, + getPowerModel(key)); } + serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT); } final int customComponentEnd = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID @@ -401,6 +528,8 @@ class PowerComponents { case BatteryUsageStats.XML_TAG_COMPONENT: { int componentId = -1; int processState = PROCESS_STATE_UNSPECIFIED; + int screenState = SCREEN_STATE_UNSPECIFIED; + int powerState = POWER_STATE_UNSPECIFIED; double powerMah = 0; long durationMs = 0; int model = BatteryConsumer.POWER_MODEL_UNDEFINED; @@ -412,6 +541,12 @@ class PowerComponents { case BatteryUsageStats.XML_ATTR_PROCESS_STATE: processState = parser.getAttributeInt(i); break; + case BatteryUsageStats.XML_ATTR_SCREEN_STATE: + screenState = parser.getAttributeInt(i); + break; + case BatteryUsageStats.XML_ATTR_POWER_STATE: + powerState = parser.getAttributeInt(i); + break; case BatteryUsageStats.XML_ATTR_POWER: powerMah = parser.getAttributeDouble(i); break; @@ -423,8 +558,8 @@ class PowerComponents { break; } } - final BatteryConsumer.Key key = - builder.mData.getKey(componentId, processState); + final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId, + processState, screenState, powerState); builder.setConsumedPower(key, powerMah, model); builder.setUsageDurationMillis(key, durationMs); break; @@ -468,11 +603,9 @@ class PowerComponents { Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) { mData = data; mMinConsumedPowerThreshold = minConsumedPowerThreshold; - for (BatteryConsumer.Key[] keys : mData.layout.keys) { - for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { - mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED); - } + for (BatteryConsumer.Key key : mData.layout.keys) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { + mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED); } } } @@ -572,51 +705,41 @@ class PowerComponents { + ", expected: " + mData.layout.customPowerComponentCount); } - for (int componentId = BatteryConsumer.POWER_COMPONENT_COUNT - 1; componentId >= 0; - componentId--) { - final BatteryConsumer.Key[] keys = mData.layout.keys[componentId]; - for (BatteryConsumer.Key key: keys) { - BatteryConsumer.Key otherKey = null; - for (BatteryConsumer.Key aKey: otherData.layout.keys[componentId]) { - if (aKey.equals(key)) { - otherKey = aKey; - break; - } - } - - if (otherKey == null) { - continue; - } + for (BatteryConsumer.Key key : mData.layout.keys) { + BatteryConsumer.Key otherKey = otherData.layout.getKey(key.powerComponent, + key.processState, key.screenState, key.powerState); + if (otherKey == null) { + continue; + } - mData.putDouble(key.mPowerColumnIndex, - mData.getDouble(key.mPowerColumnIndex) - + otherData.getDouble(otherKey.mPowerColumnIndex)); - mData.putLong(key.mDurationColumnIndex, - mData.getLong(key.mDurationColumnIndex) - + otherData.getLong(otherKey.mDurationColumnIndex)); + mData.putDouble(key.mPowerColumnIndex, + mData.getDouble(key.mPowerColumnIndex) + + otherData.getDouble(otherKey.mPowerColumnIndex)); + mData.putLong(key.mDurationColumnIndex, + mData.getLong(key.mDurationColumnIndex) + + otherData.getLong(otherKey.mDurationColumnIndex)); - if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { - continue; - } + if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { + continue; + } - boolean undefined = false; - if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { + boolean undefined = false; + if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) { + undefined = true; + } else { + final int powerModel = mData.getInt(key.mPowerModelColumnIndex); + int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex); + if (powerModel == POWER_MODEL_UNINITIALIZED) { + mData.putInt(key.mPowerModelColumnIndex, otherPowerModel); + } else if (powerModel != otherPowerModel + && otherPowerModel != POWER_MODEL_UNINITIALIZED) { undefined = true; - } else { - final int powerModel = mData.getInt(key.mPowerModelColumnIndex); - int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex); - if (powerModel == POWER_MODEL_UNINITIALIZED) { - mData.putInt(key.mPowerModelColumnIndex, otherPowerModel); - } else if (powerModel != otherPowerModel - && otherPowerModel != POWER_MODEL_UNINITIALIZED) { - undefined = true; - } } + } - if (undefined) { - mData.putInt(key.mPowerModelColumnIndex, - BatteryConsumer.POWER_MODEL_UNDEFINED); - } + if (undefined) { + mData.putInt(key.mPowerModelColumnIndex, + BatteryConsumer.POWER_MODEL_UNDEFINED); } } @@ -631,10 +754,8 @@ class PowerComponents { final int usageColumnIndex = mData.layout.firstCustomUsageDurationColumn + i; final int otherDurationColumnIndex = otherData.layout.firstCustomUsageDurationColumn + i; - mData.putLong(usageColumnIndex, - mData.getLong(usageColumnIndex) + otherData.getLong( - otherDurationColumnIndex) - ); + mData.putLong(usageColumnIndex, mData.getLong(usageColumnIndex) + + otherData.getLong(otherDurationColumnIndex)); } } @@ -647,7 +768,8 @@ class PowerComponents { for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; componentId++) { totalPowerMah += mData.getDouble( - mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY).mPowerColumnIndex); + mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_ANY, SCREEN_STATE_ANY, + POWER_STATE_ANY).mPowerColumnIndex); } for (int i = 0; i < mData.layout.customPowerComponentCount; i++) { totalPowerMah += mData.getDouble( @@ -661,19 +783,17 @@ class PowerComponents { */ @NonNull public PowerComponents build() { - for (BatteryConsumer.Key[] keys : mData.layout.keys) { - for (BatteryConsumer.Key key : keys) { - if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { - if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) { - mData.putInt(key.mPowerModelColumnIndex, - BatteryConsumer.POWER_MODEL_UNDEFINED); - } + for (BatteryConsumer.Key key: mData.layout.keys) { + if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) { + if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) { + mData.putInt(key.mPowerModelColumnIndex, + BatteryConsumer.POWER_MODEL_UNDEFINED); } + } - if (mMinConsumedPowerThreshold != 0) { - if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) { - mData.putDouble(key.mPowerColumnIndex, 0); - } + if (mMinConsumedPowerThreshold != 0) { + if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) { + mData.putDouble(key.mPowerColumnIndex, 0); } } } diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index 0be2d3e30c33..e95c6a44c281 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -277,7 +277,7 @@ public final class ServiceManager { if (service != null) { return service; } else { - return Binder.allowBlocking(getIServiceManager().checkService(name)); + return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder()); } } catch (RemoteException e) { Log.e(TAG, "error in checkService", e); @@ -425,7 +425,7 @@ public final class ServiceManager { private static IBinder rawGetService(String name) throws RemoteException { final long start = sStatLogger.getTime(); - final IBinder binder = getIServiceManager().getService(name); + final IBinder binder = getIServiceManager().getService(name).getBinder(); final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start); diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java index 7b91dd5822e7..6c9a5c7f9fff 100644 --- a/core/java/android/os/ServiceManagerNative.java +++ b/core/java/android/os/ServiceManagerNative.java @@ -58,12 +58,12 @@ class ServiceManagerProxy implements IServiceManager { } @UnsupportedAppUsage - public IBinder getService(String name) throws RemoteException { + public Service getService(String name) throws RemoteException { // Same as checkService (old versions of servicemanager had both methods). - return mServiceManager.checkService(name); + return checkService(name); } - public IBinder checkService(String name) throws RemoteException { + public Service checkService(String name) throws RemoteException { return mServiceManager.checkService(name); } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 292e6bdba539..50b73a9d3f66 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -126,20 +126,18 @@ import java.util.function.Consumer; * method: * * <pre> - * public void onCreate() { - * StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}() - * .detectDiskReads() - * .detectDiskWrites() - * .detectNetwork() // or .detectAll() for all detectable problems - * .penaltyLog() - * .build()); - * StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}() - * .detectLeakedSqlLiteObjects() - * .detectLeakedClosableObjects() - * .penaltyLog() - * .penaltyDeath() - * .build()); - * super.onCreate(); + * override fun onCreate(savedInstanceState: Bundle?) { + * super.onCreate(savedInstanceState) + * StrictMode.setThreadPolicy( + * StrictMode.ThreadPolicy.Builder() + * .detectAll() + * .build() + * ) + * StrictMode.setVmPolicy( + * StrictMode.VmPolicy.Builder() + * .detectAll() + * .build() + * ) * } * </pre> * @@ -354,7 +352,7 @@ public final class StrictMode { public static final int NETWORK_POLICY_LOG = 1; /** {@hide} */ public static final int NETWORK_POLICY_REJECT = 2; - + /** * Detect explicit calls to {@link Runtime#gc()}. */ diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index b5029a6aaff3..2fde5e7eff6a 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -119,7 +119,7 @@ "PowerComponents\\.java", "[^/]*BatteryConsumer[^/]*\\.java" ], - "name": "BatteryUsageStatsProtoTests" + "name": "PowerStatsTests" }, { "file_patterns": ["SharedMemory[^/]*\\.java"], diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index 53af838cb535..9b5a378d6591 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -140,12 +140,50 @@ public final class UidBatteryConsumer extends BatteryConsumer { skipEmptyComponents); appendProcessStateData(sb, BatteryConsumer.PROCESS_STATE_CACHED, skipEmptyComponents); - pw.print(sb); + pw.println(sb); + } else { + pw.println(); } - pw.print(" ( "); - mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */); - pw.print(" ) "); + pw.print(" "); + mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, skipEmptyComponents); + + if (mData.layout.powerStateDataIncluded || mData.layout.screenStateDataIncluded) { + for (int powerState = 0; powerState < POWER_STATE_COUNT; powerState++) { + if (mData.layout.powerStateDataIncluded && powerState == POWER_STATE_UNSPECIFIED) { + continue; + } + + for (int screenState = 0; screenState < SCREEN_STATE_COUNT; screenState++) { + if (mData.layout.screenStateDataIncluded + && screenState == POWER_STATE_UNSPECIFIED) { + continue; + } + + final double consumedPower = mPowerComponents.getConsumedPower( + POWER_COMPONENT_ANY, + PROCESS_STATE_ANY, screenState, powerState); + if (consumedPower == 0) { + continue; + } + + pw.print(" ("); + if (powerState != POWER_STATE_UNSPECIFIED) { + pw.print(BatteryConsumer.powerStateToString(powerState)); + } + if (screenState != SCREEN_STATE_UNSPECIFIED) { + if (powerState != POWER_STATE_UNSPECIFIED) { + pw.print(", "); + } + pw.print("screen "); + pw.print(BatteryConsumer.screenStateToString(screenState)); + } + pw.print(") "); + mPowerComponents.dump(pw, screenState, powerState, + skipEmptyComponents /* skipTotalPowerComponent */); + } + } + } } private void appendProcessStateData(StringBuilder sb, @ProcessState int processState, @@ -160,10 +198,6 @@ public final class UidBatteryConsumer extends BatteryConsumer { .append(BatteryStats.formatCharge(power)); } - static UidBatteryConsumer create(BatteryConsumerData data) { - return new UidBatteryConsumer(data); - } - /** Serializes this object to XML */ void writeToXml(TypedXmlSerializer serializer) throws IOException { if (getConsumedPower() == 0) { diff --git a/core/java/android/os/UserBatteryConsumer.java b/core/java/android/os/UserBatteryConsumer.java index 23ba0c635eca..ea2be7b80e94 100644 --- a/core/java/android/os/UserBatteryConsumer.java +++ b/core/java/android/os/UserBatteryConsumer.java @@ -60,10 +60,10 @@ public class UserBatteryConsumer extends BatteryConsumer { pw.print("User "); pw.print(getUserId()); pw.print(": "); - pw.print(BatteryStats.formatCharge(consumedPower)); - pw.print(" ( "); - mPowerComponents.dump(pw, skipEmptyComponents /* skipTotalPowerComponent */); - pw.print(" ) "); + pw.println(BatteryStats.formatCharge(consumedPower)); + pw.print(" "); + mPowerComponents.dump(pw, SCREEN_STATE_ANY, POWER_STATE_ANY, + skipEmptyComponents /* skipTotalPowerComponent */); } /** Serializes this object to XML */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2562c8e31095..ff389208a579 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11075,6 +11075,13 @@ public final class Settings { public static final String MANDATORY_BIOMETRICS = "mandatory_biometrics"; /** + * Whether or not requirements for mandatory biometrics is satisfied. + * @hide + */ + public static final String MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = + "mandatory_biometrics_requirements_satisfied"; + + /** * Whether or not active unlock triggers on wake. * @hide */ diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index e4fc1cd9dad0..fbeab84fa96d 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -787,7 +787,6 @@ public class DreamService extends Service implements Window.Callback { */ public void setInteractive(boolean interactive) { mInteractive = interactive; - updateAccessibilityMessage(); } /** @@ -1641,9 +1640,9 @@ public class DreamService extends Service implements Window.Callback { if (mWindow == null) return; if (mDreamAccessibility == null) { final View rootView = mWindow.getDecorView(); - mDreamAccessibility = new DreamAccessibility(this, rootView); + mDreamAccessibility = new DreamAccessibility(this, rootView, this::wakeUp); } - mDreamAccessibility.updateAccessibilityConfiguration(isInteractive()); + mDreamAccessibility.updateAccessibilityConfiguration(); } private boolean getWindowFlagValue(int flag, boolean defaultValue) { diff --git a/core/java/android/service/dreams/utils/DreamAccessibility.java b/core/java/android/service/dreams/utils/DreamAccessibility.java index c38f41bab5a6..f504ff7d4650 100644 --- a/core/java/android/service/dreams/utils/DreamAccessibility.java +++ b/core/java/android/service/dreams/utils/DreamAccessibility.java @@ -18,6 +18,7 @@ package android.service.dreams.utils; import android.annotation.NonNull; import android.content.Context; +import android.os.Bundle; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -32,22 +33,22 @@ public class DreamAccessibility { private final Context mContext; private final View mView; private final View.AccessibilityDelegate mAccessibilityDelegate; + private final Runnable mDismissCallback; - public DreamAccessibility(@NonNull Context context, @NonNull View view) { + public DreamAccessibility(@NonNull Context context, @NonNull View view, + @NonNull Runnable dismissCallback) { mContext = context; mView = view; mAccessibilityDelegate = createNewAccessibilityDelegate(mContext); + mDismissCallback = dismissCallback; } /** - * @param interactive - * Removes and add accessibility configuration depending if the dream is interactive or not + * Adds default accessibility configuration if none exist on the dream */ - public void updateAccessibilityConfiguration(Boolean interactive) { - if (!interactive) { + public void updateAccessibilityConfiguration() { + if (mView.getAccessibilityDelegate() == null) { addAccessibilityConfiguration(); - } else { - removeCustomAccessibilityAction(); } } @@ -58,31 +59,28 @@ public class DreamAccessibility { mView.setAccessibilityDelegate(mAccessibilityDelegate); } - /** - * Removes Configured the accessibility actions for the given root view. - */ - private void removeCustomAccessibilityAction() { - if (mView.getAccessibilityDelegate() == mAccessibilityDelegate) { - mView.setAccessibilityDelegate(null); - } - } - private View.AccessibilityDelegate createNewAccessibilityDelegate(Context context) { return new View.AccessibilityDelegate() { @Override public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); - for (AccessibilityNodeInfo.AccessibilityAction action : info.getActionList()) { - if (action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { - info.removeAction(action); - break; - } - } info.addAction(new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.ACTION_CLICK, + AccessibilityNodeInfo.ACTION_DISMISS, context.getResources().getString(R.string.dream_accessibility_action_click) )); } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + switch(action){ + case AccessibilityNodeInfo.ACTION_DISMISS: + if (mDismissCallback != null) { + mDismissCallback.run(); + } + break; + } + return true; + } }; } } diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 5d84d17bdb6e..b07534f5fe52 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -68,4 +68,11 @@ public class ClientFlags { public static boolean fixMisalignedContextMenu() { return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU); } + + /** + * @see Flags#clearFontVariationSettings() + */ + public static boolean clearFontVariationSettings() { + return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS); + } } diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 9e02460d2637..4dca284b8a4d 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -61,6 +61,7 @@ public final class TextFlags { Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE, Flags.FLAG_ICU_BIDI_MIGRATION, Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, + Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS, }; /** @@ -75,6 +76,7 @@ public final class TextFlags { Flags.fixLineHeightForLocale(), Flags.icuBidiMigration(), Flags.fixMisalignedContextMenu(), + Flags.clearFontVariationSettings(), }; /** diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 8836c8a3a113..02c63db2c8a6 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -220,3 +220,23 @@ flag { is_fixed_read_only: true bug: "346915432" } + +flag { + name: "clear_font_variation_settings" + namespace: "text" + description: "The font variation settings must be cleared when the new Typeface is set" + bug: "353609778" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "portuguese_hyphenator" + namespace: "text" + description: "Portuguese taiored hyphenator" + bug: "344656282" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/text/format/DateIntervalFormat.java b/core/java/android/text/format/DateIntervalFormat.java index e8236fda42b6..8dea3228eb0c 100644 --- a/core/java/android/text/format/DateIntervalFormat.java +++ b/core/java/android/text/format/DateIntervalFormat.java @@ -26,6 +26,7 @@ import android.icu.util.ULocale; import android.util.LruCache; import com.android.internal.annotations.VisibleForTesting; +import com.android.libcore.Flags; import java.text.FieldPosition; import java.util.TimeZone; @@ -123,4 +124,14 @@ public final class DateIntervalFormat { && c.get(Calendar.SECOND) == 0 && c.get(Calendar.MILLISECOND) == 0; } + + + @VisibleForTesting(visibility = PACKAGE) + public static boolean isLibcoreVFlagEnabled() { + // Note that the Flags class is expected to be jarjar-ed in the build-time. + // See go/repackage_flags + // The full-qualified name should be like + // com.android.internal.hidden_from_bootclasspath.com.android.libcore.Flags + return Flags.vApis(); + } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index fedbe4a65e07..42d66ce6bf1b 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -366,13 +366,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Log.e(TAG, "Received invalid input event"); return; } - try { - vri.processingBackKey(true); - vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, - true /* processImmediately */); - } finally { - vri.processingBackKey(false); - } + vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, + true /* processImmediately */); }); } }; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index b2c39b13525f..ceaca2257af4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2798,9 +2798,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { - final boolean cancelChild = resetCancelNextUpFlag(target.child) - || intercepted; - if (dispatchTransformedTouchEvent(ev, cancelChild, + final boolean cancelChild = + (target.child != null && resetCancelNextUpFlag(target.child)) + || intercepted; + if (target.child != null && dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 596726f83c15..2f204f9b1be9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -727,8 +727,6 @@ public final class ViewRootImpl implements ViewParent, boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; - // While set, allow this VRI to handle back key without drop it. - private boolean mProcessingBackKey; /** * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back * key event host app. @@ -1135,6 +1133,8 @@ public final class ViewRootImpl implements ViewParent, // Take 24 and 30 as an example, 24 is not a divisor of 30. // We consider there is a conflict. private boolean mIsFrameRateConflicted = false; + // Used to check whether SurfaceControl has been replaced. + private boolean mSurfaceReplaced = false; // Used to set frame rate compatibility. @Surface.FrameRateCompatibility int mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; @@ -3831,6 +3831,7 @@ public final class ViewRootImpl implements ViewParent, surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId() || surfaceControlChanged) && mSurface.isValid(); if (surfaceReplaced) { + mSurfaceReplaced = true; mSurfaceSequenceId++; } if (alwaysConsumeSystemBarsChanged) { @@ -4443,6 +4444,7 @@ public final class ViewRootImpl implements ViewParent, mPreferredFrameRate = -1; mIsFrameRateConflicted = false; mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN; + mSurfaceReplaced = false; } else if (mPreferredFrameRate == 0) { // From MSG_FRAME_RATE_SETTING, where mPreferredFrameRate is set to 0 setPreferredFrameRate(0); @@ -7265,7 +7267,7 @@ public final class ViewRootImpl implements ViewParent, // Find a reason for dropping or canceling the event. final String reason; // The embedded window is focused, allow this VRI to handle back key. - if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent)) + if (!mAttachInfo.mHasWindowFocus && !isBack(q.mEvent) && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus @@ -7543,7 +7545,6 @@ public final class ViewRootImpl implements ViewParent, animationCallback.onBackCancelled(); } else { topCallback.onBackInvoked(); - return FINISH_HANDLED; } break; } @@ -7551,14 +7552,16 @@ public final class ViewRootImpl implements ViewParent, if (keyEvent.getAction() == KeyEvent.ACTION_UP) { if (!keyEvent.isCanceled()) { topCallback.onBackInvoked(); - return FINISH_HANDLED; } else { Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true"); } } } - - return FINISH_NOT_HANDLED; + if (keyEvent.getAction() == KeyEvent.ACTION_UP) { + // forward a cancelled event so that following stages cancel their back logic + keyEvent.cancel(); + } + return FORWARD; } @Override @@ -11213,11 +11216,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget(); } - // Make this VRI able to process back key without drop it. - void processingBackKey(boolean processing) { - mProcessingBackKey = processing; - } - /** * Collect and include any ScrollCaptureCallback instances registered with the window. * @@ -12549,15 +12547,8 @@ public final class ViewRootImpl implements ViewParent, * @return whether the event was handled (i.e. onKeyPreIme consumed it if preImeOnly=true) */ public boolean injectBackKeyEvents(boolean preImeOnly) { - boolean consumed; - try { - processingBackKey(true); - sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); - consumed = sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); - } finally { - processingBackKey(false); - } - return consumed; + sendBackKeyEvent(KeyEvent.ACTION_DOWN, preImeOnly); + return sendBackKeyEvent(KeyEvent.ACTION_UP, preImeOnly); } private boolean sendBackKeyEvent(int action, boolean preImeOnly) { @@ -12933,8 +12924,9 @@ public final class ViewRootImpl implements ViewParent, boolean traceFrameRateCategory = false; try { - if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT - && mLastPreferredFrameRateCategory != frameRateCategory) { + if ((frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT + && mLastPreferredFrameRateCategory != frameRateCategory) + || mSurfaceReplaced) { traceFrameRateCategory = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); if (traceFrameRateCategory) { String reason = reasonToString(frameRateReason); @@ -12998,7 +12990,7 @@ public final class ViewRootImpl implements ViewParent, boolean traceFrameRate = false; try { - if (mLastPreferredFrameRate != preferredFrameRate) { + if (mLastPreferredFrameRate != preferredFrameRate || mSurfaceReplaced) { traceFrameRate = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); if (traceFrameRate) { Trace.traceBegin( diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 561d979f2f9d..987c8c8213f3 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -1708,6 +1708,7 @@ public final class WindowInsets { } mTypeBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]); } + mSystemInsetsConsumed = false; return this; } @@ -1736,6 +1737,7 @@ public final class WindowInsets { } mTypeMaxBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]); } + mStableInsetsConsumed = false; return this; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index a5ba294d6a19..90cfcb1e64a8 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -982,6 +982,7 @@ public class AccessibilityNodeInfo implements Parcelable { private long mParentNodeId = UNDEFINED_NODE_ID; private long mLabelForId = UNDEFINED_NODE_ID; private long mLabeledById = UNDEFINED_NODE_ID; + private LongArray mLabeledByIds; private long mTraversalBefore = UNDEFINED_NODE_ID; private long mTraversalAfter = UNDEFINED_NODE_ID; @@ -3599,6 +3600,131 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Adds the view which serves as the label of the view represented by + * this info for accessibility purposes. When more than one labels are + * added, the content from each label is combined in the order that + * they are added. + * <p> + * If visible text can be used to describe or give meaning to this UI, + * this method is preferred. For example, a TextView before an EditText + * in the UI usually specifies what information is contained in the + * EditText. Hence, the EditText is labelled by the TextView. + * </p> + * + * @param label A view that labels this node's source. + */ + @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY) + public void addLabeledBy(@NonNull View label) { + addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID); + } + + /** + * Adds the view which serves as the label of the view represented by + * this info for accessibility purposes. If <code>virtualDescendantId</code> + * is {@link View#NO_ID} the root is set as the label. When more than one + * labels are added, the content from each label is combined in the order + * that they are added. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report themselves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * If visible text can be used to describe or give meaning to this UI, + * this method is preferred. For example, a TextView before an EditText + * in the UI usually specifies what information is contained in the + * EditText. Hence, the EditText is labelled by the TextView. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root A root whose virtual descendant labels this node's source. + * @param virtualDescendantId The id of the virtual descendant. + */ + @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY) + public void addLabeledBy(@NonNull View root, int virtualDescendantId) { + enforceNotSealed(); + Preconditions.checkNotNull(root, "%s must not be null", root); + if (mLabeledByIds == null) { + mLabeledByIds = new LongArray(); + } + mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId); + mLabeledByIds.add(mLabeledById); + } + + /** + * Gets the list of node infos which serve as the labels of the view represented by + * this info for accessibility purposes. + * + * @return The list of labels in the order that they were added. + */ + @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY) + public @NonNull List<AccessibilityNodeInfo> getLabeledByList() { + enforceSealed(); + List<AccessibilityNodeInfo> labels = new ArrayList<>(); + if (mLabeledByIds == null) { + return labels; + } + for (int i = 0; i < mLabeledByIds.size(); i++) { + labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i))); + } + return labels; + } + + /** + * Removes a label. If the label was not previously added to the node, + * calling this method has no effect. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param label The node which serves as this node's label. + * @return true if the label was present + * @see #addLabeledBy(View) + */ + @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY) + public boolean removeLabeledBy(@NonNull View label) { + return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID); + } + + /** + * Removes a virtual label which is a descendant of the given + * <code>root</code>. If the label was not previously added to the node, + * calling this method has no effect. + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual node which serves as this node's label. + * @return true if the label was present + * @see #addLabeledBy(View, int) + */ + @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY) + public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) { + enforceNotSealed(); + final LongArray labeledByIds = mLabeledByIds; + if (labeledByIds == null) { + return false; + } + final int rootAccessibilityViewId = + (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + if (mLabeledById == labeledById) { + mLabeledById = UNDEFINED_NODE_ID; + } + final int index = labeledByIds.indexOf(labeledById); + if (index < 0) { + return false; + } + labeledByIds.remove(index); + return true; + } + + /** * Sets the view which serves as the label of the view represented by * this info for accessibility purposes. * @@ -3631,7 +3757,17 @@ public class AccessibilityNodeInfo implements Parcelable { enforceNotSealed(); final int rootAccessibilityViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + if (Flags.supportMultipleLabeledby()) { + if (mLabeledByIds == null) { + mLabeledByIds = new LongArray(); + } else { + mLabeledByIds.clear(); + } + } mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + if (Flags.supportMultipleLabeledby()) { + mLabeledByIds.add(mLabeledById); + } } /** @@ -4242,6 +4378,12 @@ public class AccessibilityNodeInfo implements Parcelable { fieldIndex++; if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex); fieldIndex++; + if (Flags.supportMultipleLabeledby()) { + if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) { + nonDefaultFields |= bitAt(fieldIndex); + } + fieldIndex++; + } if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex); fieldIndex++; if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex); @@ -4383,6 +4525,20 @@ public class AccessibilityNodeInfo implements Parcelable { if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId); if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId); if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById); + if (Flags.supportMultipleLabeledby()) { + if (isBitSet(nonDefaultFields, fieldIndex++)) { + final LongArray labeledByIds = mLabeledByIds; + if (labeledByIds == null) { + parcel.writeInt(0); + } else { + final int labeledByIdsSize = labeledByIds.size(); + parcel.writeInt(labeledByIdsSize); + for (int i = 0; i < labeledByIdsSize; i++) { + parcel.writeLong(labeledByIds.get(i)); + } + } + } + } if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore); if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter); if (isBitSet(nonDefaultFields, fieldIndex++)) { @@ -4550,6 +4706,9 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = other.mParentNodeId; mLabelForId = other.mLabelForId; mLabeledById = other.mLabeledById; + if (Flags.supportMultipleLabeledby()) { + mLabeledByIds = other.mLabeledByIds; + } mTraversalBefore = other.mTraversalBefore; mTraversalAfter = other.mTraversalAfter; mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges; @@ -4656,6 +4815,20 @@ public class AccessibilityNodeInfo implements Parcelable { if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong(); if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong(); if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong(); + if (Flags.supportMultipleLabeledby()) { + if (isBitSet(nonDefaultFields, fieldIndex++)) { + final int labeledByIdsSize = parcel.readInt(); + if (labeledByIdsSize <= 0) { + mLabeledByIds = null; + } else { + mLabeledByIds = new LongArray(labeledByIdsSize); + for (int i = 0; i < labeledByIdsSize; i++) { + final long labeledById = parcel.readLong(); + mLabeledByIds.add(labeledById); + } + } + } + } if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong(); if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong(); if (isBitSet(nonDefaultFields, fieldIndex++)) { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 44c1acc34273..ed2bf79c51f4 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -184,6 +184,13 @@ flag { } flag { + name: "support_multiple_labeledby" + namespace: "accessibility" + description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api" + bug: "333780959" +} + +flag { name: "support_system_pinch_zoom_opt_out_apis" namespace: "accessibility" description: "Feature flag for declaring system pinch zoom opt-out apis" diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 5b1c7d54ddb7..0ab51e45a951 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -563,7 +563,7 @@ public class AutofillFeatureFlags { return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_ENABLE_RELAYOUT, - true); + false); } /** @hide */ diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 9512347b0143..0dadbe374aa4 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -7438,8 +7438,7 @@ public class RemoteViews implements Parcelable, Filter { // If the user interacts with a visible element it is safe to assume they consent that // something is going to start. opts.setPendingIntentBackgroundActivityStartMode( - ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); - opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); return Pair.create(intent, opts); } } diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 4c8bad6d0aff..205f1defccd1 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -412,8 +412,7 @@ public class SnapshotDrawerUtils { final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; - final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; - if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { + if (attrs == null || mainWindowParams == null) { Log.w(TAG, "unable to create taskSnapshot surface "); return null; } @@ -456,7 +455,10 @@ public class SnapshotDrawerUtils { return layoutParams; } - static Rect getSystemBarInsets(Rect frame, InsetsState state) { + static Rect getSystemBarInsets(Rect frame, @Nullable InsetsState state) { + if (state == null) { + return new Rect(); + } return state.calculateInsets(frame, WindowInsets.Type.systemBars(), false /* ignoreVisibility */).toRect(); } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 48fb2b3ab129..f739622a1b92 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -98,6 +98,13 @@ flag { } flag { + name: "scrolling_from_letterbox" + namespace: "large_screen_experiences_app_compat" + description: "Whether to enable app scrolling from gestures from letterbox area" + bug: "353697519" +} + +flag { name: "app_compat_refactoring" namespace: "large_screen_experiences_app_compat" description: "Whether the changes about app compat refactoring are enabled./n" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ae9d757e1e0b..13d465f183c9 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -3,16 +3,6 @@ container: "system" # Project link: https://gantry.corp.google.com/projects/android_platform_windowing_sdk/changes -# Using a fixed read only flag because there are ClientTransaction scheduling before -# WindowManagerService creation. -flag { - namespace: "windowing_sdk" - name: "bundle_client_transaction_flag" - description: "To bundle multiple ClientTransactionItems into one ClientTransaction" - bug: "260873529" - is_fixed_read_only: true -} - flag { namespace: "windowing_sdk" name: "activity_embedding_overlay_presentation_flag" diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 75ddb58590a1..f9c294758bf0 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -48,6 +48,7 @@ import android.os.Handler; import android.os.UserHandle; import android.os.Vibrator; import android.provider.Settings; +import android.provider.SettingsStringUtil; import android.speech.tts.TextToSpeech; import android.speech.tts.Voice; import android.text.TextUtils; @@ -151,7 +152,8 @@ public class AccessibilityShortcutController { * info for toggling a framework feature */ public static Map<ComponentName, FrameworkFeatureInfo> - getFrameworkShortcutFeaturesMap() { + getFrameworkShortcutFeaturesMap() { + if (sFrameworkShortcutFeaturesMap == null) { Map<ComponentName, FrameworkFeatureInfo> featuresMap = new ArrayMap<>(4); featuresMap.put(COLOR_INVERSION_COMPONENT_NAME, @@ -172,7 +174,7 @@ public class AccessibilityShortcutController { R.string.one_handed_mode_feature_name)); } featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME, - new ToggleableFrameworkFeatureInfo( + new ExtraDimFrameworkFeatureInfo( Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, "1" /* Value to enable */, "0" /* Value to disable */, R.string.reduce_bright_colors_feature_name)); @@ -828,6 +830,44 @@ public class AccessibilityShortcutController { } } + + public static class ExtraDimFrameworkFeatureInfo extends FrameworkFeatureInfo { + ExtraDimFrameworkFeatureInfo(String settingKey, String settingOnValue, + String settingOffValue, int labelStringResourceId) { + super(settingKey, settingOnValue, settingOffValue, labelStringResourceId); + } + + /** + * Perform shortcut action. + * + * @return True if the accessibility service is enabled, false otherwise. + */ + public boolean activateShortcut(Context context, int userId) { + if (com.android.server.display.feature.flags.Flags.evenDimmer() + && context.getResources().getBoolean( + com.android.internal.R.bool.config_evenDimmerEnabled)) { + launchExtraDimDialog(); + return true; + } else { + // Assuming that the default state will be to have the feature off + final SettingsStringUtil.SettingStringHelper + setting = new SettingsStringUtil.SettingStringHelper( + context.getContentResolver(), getSettingKey(), userId); + if (!TextUtils.equals(getSettingOnValue(), setting.read())) { + setting.write(getSettingOnValue()); + return true; + } else { + setting.write(getSettingOffValue()); + return false; + } + } + } + + private void launchExtraDimDialog() { + // TODO: launch Extra dim dialog for feature migration + } + } + // Class to allow mocking of static framework calls public static class FrameworkObjectProvider { public AccessibilityManager getAccessibilityManagerInstance(Context context) { diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 3e6f18e8e24b..69d1cb34005d 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -161,12 +161,12 @@ public class Cuj { public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106; /** - * Track entering desktop mode interaction via app handle drag. + * Track app handle drag and hold interaction. * * <p>Tracking starts when the app handle is dragged and - * finishes when the window animation to desktop ends after app handle release. + * finishes immediately after app handle release, before starting a new transition. */ - public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG = 107; + public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD = 107; /** Track exiting desktop mode interaction. */ public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108; @@ -197,8 +197,21 @@ public class Cuj { /** Track launching an app through the Launcher Keyboard Quick Switch View */ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115; + /** + * Track entering desktop mode interaction via app handle drag release. + * + * <p>Tracking starts when the app handle is released and + * finishes when one of the three possible animations end: + * <ul> + * <li>release to desktop</li> + * <li>release to split-screen</li> + * <li>release to back to full-screen</li> + * </ul>. + */ + public static final int CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE = 116; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH; + @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE; /** @hide */ @IntDef({ @@ -297,7 +310,7 @@ public class Cuj { CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, CUJ_FOLD_ANIM, CUJ_DESKTOP_MODE_RESIZE_WINDOW, - CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU, CUJ_DESKTOP_MODE_EXIT_MODE, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, @@ -305,7 +318,8 @@ public class Cuj { CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP, CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN, CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE, - CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH + CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -414,7 +428,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW; @@ -423,6 +437,7 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE; } private Cuj() { @@ -631,8 +646,8 @@ public class Cuj { return "FOLD_ANIM"; case CUJ_DESKTOP_MODE_RESIZE_WINDOW: return "DESKTOP_MODE_RESIZE_WINDOW"; - case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG: - return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_DRAG"; + case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD: + return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD"; case CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU: return "DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU"; case CUJ_DESKTOP_MODE_EXIT_MODE: @@ -649,6 +664,8 @@ public class Cuj { return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE"; case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH: return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH"; + case CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE: + return "DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 24971f51aabf..488e06f9a1ad 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -580,10 +580,15 @@ public final class PowerStats { } PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { + String formattedStats = uidStatsFormatter.format(uidStats.valueAt(i)); + if (formattedStats.isBlank()) { + continue; + } + pw.print("UID "); pw.print(UserHandle.formatUid(uidStats.keyAt(i))); pw.print(": "); - pw.print(uidStatsFormatter.format(uidStats.valueAt(i))); + pw.print(formattedStats); pw.println(); } pw.decreaseIndent(); diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index d552e0b8c643..ae43acfdedb6 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -18,7 +18,7 @@ "Kernel[^/]*\\.java", "[^/]*Power[^/]*\\.java" ], - "name": "BatteryUsageStatsProtoTests" + "name": "PowerStatsTests" }, { "file_patterns": [ diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 7e2a5ace7e64..020b27e82bea 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -47,13 +47,12 @@ using namespace std; */ extern int register_android_os_Binder(JNIEnv* env); -extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env); +extern int register_libcore_util_NativeAllocationRegistry(JNIEnv* env); typedef void (*FreeFunction)(void*); -static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, jclass, - jlong freeFunction, - jlong ptr) { +static void NativeAllocationRegistry_applyFreeFunction(JNIEnv*, jclass, jlong freeFunction, + jlong ptr) { void* nativePtr = reinterpret_cast<void*>(static_cast<uintptr_t>(ptr)); FreeFunction nativeFreeFunction = reinterpret_cast<FreeFunction>(static_cast<uintptr_t>(freeFunction)); @@ -61,11 +60,11 @@ static void NativeAllocationRegistry_Delegate_nativeApplyFreeFunction(JNIEnv*, j } static JNINativeMethod gMethods[] = { - NATIVE_METHOD(NativeAllocationRegistry_Delegate, nativeApplyFreeFunction, "(JJ)V"), + NATIVE_METHOD(NativeAllocationRegistry, applyFreeFunction, "(JJ)V"), }; -int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) { - return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry_Delegate", gMethods, +int register_libcore_util_NativeAllocationRegistry(JNIEnv* env) { + return jniRegisterNativeMethods(env, "libcore/util/NativeAllocationRegistry", gMethods, NELEM(gMethods)); } @@ -147,8 +146,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)}, {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)}, - {"libcore.util.NativeAllocationRegistry_Delegate", - REG_JNI(register_libcore_util_NativeAllocationRegistry_Delegate)}, + {"libcore.util.NativeAllocationRegistry", + REG_JNI(register_libcore_util_NativeAllocationRegistry)}, }; static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap, diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index 5a4d6dbdc085..12804d43b04e 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -147,6 +147,7 @@ message VibrationProto { IGNORED_ON_WIRELESS_CHARGER = 27; IGNORED_MISSING_PERMISSION = 28; CANCELLED_BY_APP_OPS = 29; + CANCELLED_BY_FOREGROUND_USER = 30; reserved 17; // prev IGNORED_UNKNOWN_VIBRATION } } diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java index 9d7d71d8d539..616c72ec6078 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import android.graphics.Matrix; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java index d7b911dda672..d0a4141eb9ed 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java @@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull; import android.graphics.RectF; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java index 4839dd27b283..013117e89c3e 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java @@ -45,8 +45,8 @@ import android.util.StringBuilderPrinter; import android.view.MotionEvent; import android.view.autofill.AutofillId; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java index ce85a76f478d..61bf1378fc36 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -28,9 +28,9 @@ import android.os.Bundle; import android.os.Parcel; import android.platform.test.flag.junit.SetFlagsRule; -import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.frameworks.inputmethodcoretests.R; @@ -135,7 +135,7 @@ public class InputMethodInfoTest { private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes) throws Exception { - final Context context = InstrumentationRegistry.getContext(); + final Context context = InstrumentationRegistry.getInstrumentation().getContext(); final ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.applicationInfo = context.getApplicationInfo(); serviceInfo.packageName = context.getPackageName(); diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java index d70572444128..812b3f5cd153 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java @@ -24,9 +24,9 @@ import static org.junit.Assert.assertNotNull; import android.content.Context; import android.hardware.display.DisplayManager; -import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java index e7b1110f898a..73ff30408a7b 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java @@ -26,8 +26,8 @@ import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java index 5095cad1b607..4c76992cbcac 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java @@ -27,8 +27,8 @@ import android.annotation.Nullable; import android.os.Parcel; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java index 47a724d36038..608dd4d9fca1 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java @@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull; import android.graphics.PointF; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java index a94f8772fcd0..bb6a944ba17f 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java @@ -24,8 +24,8 @@ import android.os.CancellationSignal; import android.os.CancellationSignalBeamer; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java index b2eb07c0a9e7..4cbd7ab1ef65 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java @@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull; import android.graphics.RectF; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java index df63a4aaaefe..c1e2197b8d85 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java @@ -22,8 +22,8 @@ import static org.junit.Assert.assertNotNull; import android.graphics.RectF; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.ApiTest; diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java index f264cc630dc5..724d729c950f 100644 --- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java +++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java @@ -25,8 +25,8 @@ import android.graphics.RectF; import android.os.Parcel; import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java index a6262944e8b0..9eb5dd1a2422 100644 --- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java @@ -22,8 +22,8 @@ import android.platform.test.annotations.Presubmit; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionWrapper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java index 32bfdcb7b217..6ac36398ad55 100644 --- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java @@ -20,8 +20,8 @@ import static org.junit.Assert.assertEquals; import android.view.WindowManager.LayoutParams; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java index ba6390808151..7b0a5dacbea0 100644 --- a/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java +++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java @@ -18,8 +18,8 @@ package com.android.internal.inputmethod; import static org.junit.Assert.assertEquals; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp deleted file mode 100644 index 1fb5f2c0789b..000000000000 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/Android.bp +++ /dev/null @@ -1,30 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "BatteryUsageStatsProtoTests", - srcs: ["src/**/*.java"], - - static_libs: [ - "androidx.test.rules", - "junit", - "mockito-target-minus-junit4", - "platform-test-annotations", - "platformprotosnano", - "statsdprotolite", - "truth", - ], - - libs: ["android.test.runner"], - - platform_apis: true, - certificate: "platform", - - test_suites: ["device-tests"], -} diff --git a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java index cb3f99c37a4f..33a46d0fde17 100644 --- a/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java +++ b/core/tests/coretests/src/android/animation/AnimatorSetCallsTest.java @@ -35,6 +35,8 @@ import org.junit.Test; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + @MediumTest public class AnimatorSetCallsTest { @@ -447,6 +449,43 @@ public class AnimatorSetCallsTest { mActivity.runOnUiThread(() -> {}); } + @Test + public void startAfterSeek() throws Throwable { + ArrayList<Float> values = new ArrayList<>(); + AtomicReference<CountDownLatch> drawLatch = new AtomicReference<>(new CountDownLatch(1)); + + mActivity.runOnUiThread(() -> { + mAnimator.setDuration(300); + mAnimator.setInterpolator(null); + View view = (View) mAnimator.getTarget(); + view.getViewTreeObserver().addOnDrawListener(() -> { + values.add(view.getTranslationX()); + drawLatch.get().countDown(); + }); + mSet1.setCurrentPlayTime(150); + }); + + assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS)); + drawLatch.set(new CountDownLatch(1)); + + mActivity.runOnUiThread(() -> { + assertEquals(1, values.size()); + assertEquals(50f, values.get(0), 0.01f); + mSet1.start(); + }); + + assertTrue(drawLatch.get().await(1, TimeUnit.SECONDS)); + + mActivity.runOnUiThread(() -> { + assertTrue(values.size() >= 2); + float lastValue = values.get(0); + for (int i = 1; i < values.size(); i++) { + assertTrue(values.get(i) >= lastValue); + lastValue = values.get(i); + } + }); + } + private void waitForOnUiThread(PollingCheck.PollingCheckCondition condition) { final boolean[] value = new boolean[1]; PollingCheck.waitFor(() -> { diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index e8a0762f70c0..294352e7f928 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -90,7 +90,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -117,6 +118,9 @@ public class ActivityThreadTest { // few sequence numbers the framework used to launch the test activity. private static final int BASE_SEQ = 10000000; + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule(order = 0) public final ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */, @@ -133,8 +137,6 @@ public class ActivityThreadTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); - // Keep track of the original controller, so that it can be used to restore in tearDown() // when there is override in some test cases. mOriginalWindowTokenClientController = WindowTokenClientController.getInstance(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java index c7060adc1ca1..72c4639277bb 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java @@ -47,10 +47,12 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Tests for subtypes of {@link ClientTransactionItem}. @@ -63,6 +65,9 @@ import org.mockito.MockitoAnnotations; @Presubmit public class ClientTransactionItemTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Mock private ClientTransactionHandler mHandler; @Mock @@ -89,7 +94,6 @@ public class ClientTransactionItemTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mGlobalConfig = new Configuration(); mConfiguration = new Configuration(); mActivitiesToBeDestroyed = new ArrayMap<>(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index d2a444f0d0df..f9609fcad8a1 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -59,7 +59,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.concurrent.RejectedExecutionException; import java.util.function.BiConsumer; @@ -76,6 +77,8 @@ import java.util.function.BiConsumer; public class ClientTransactionListenerControllerTest { @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock @@ -100,7 +103,6 @@ public class ClientTransactionListenerControllerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mDisplayManager = new DisplayManagerGlobal(mIDisplayManager); mHandler = getInstrumentation().getContext().getMainThreadHandler(); mController = spy(ClientTransactionListenerController diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 32e611cf0a1e..e429cfc687bb 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -21,12 +21,7 @@ import static android.app.servertransaction.TestUtils.mergedConfig; import static android.app.servertransaction.TestUtils.referrerIntentList; import static android.app.servertransaction.TestUtils.resultInfoList; -import static com.android.window.flags.Flags.FLAG_DISABLE_OBJECT_POOL; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; import android.annotation.NonNull; import android.app.ActivityOptions; @@ -41,27 +36,20 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.platform.test.annotations.Presubmit; -import android.platform.test.flag.junit.FlagsParameterization; -import android.platform.test.flag.junit.SetFlagsRule; import android.window.ActivityWindowInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.window.flags.Flags; - -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; -import java.util.List; import java.util.function.Supplier; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - /** * Tests for {@link ObjectPool}. * @@ -71,33 +59,19 @@ import platform.test.runner.parameterized.Parameters; * <p>This test class is a part of Window Manager Service tests and specified in * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}. */ -@RunWith(ParameterizedAndroidJunit4.class) +@RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class ObjectPoolTests { - @Parameters(name = "{0}") - public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf(FLAG_DISABLE_OBJECT_POOL); - } - @Rule - public SetFlagsRule mSetFlagsRule; + public final MockitoRule mocks = MockitoJUnit.rule(); @Mock private IApplicationThread mApplicationThread; @Mock private IBinder mActivityToken; - public ObjectPoolTests(FlagsParameterization flags) { - mSetFlagsRule = new SetFlagsRule(flags); - } - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - // 1. Check if two obtained objects from pool are not the same. // 2. Check if the state of the object is cleared after recycling. // 3. Check if the same object is obtained from pool after recycling. @@ -221,30 +195,11 @@ public class ObjectPoolTests { item.recycle(); final ObjectPoolItem item2 = obtain.get(); - if (Flags.disableObjectPool()) { - assertNotSame(item, item2); // Different instance. - } else { - assertSame(item, item2); - } + assertNotSame(item, item2); // Different instance. // Create new object when the pool is empty. final ObjectPoolItem item3 = obtain.get(); assertNotSame(item, item3); - if (Flags.disableObjectPool()) { - // Skip recycle if flag enabled, compare unnecessary. - return; - } - assertEquals(item, item3); - - // Reset fields after recycle. - item.recycle(); - - assertNotEquals(item, item3); - - // Recycled objects are equal. - item3.recycle(); - - assertEquals(item, item3); } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index 73b7447a31c0..eb69b9c8b6e1 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -57,11 +57,13 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.Collections; @@ -83,6 +85,9 @@ import java.util.stream.Collectors; @Presubmit public class TransactionExecutorTests { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + @Mock private ClientTransactionHandler mTransactionHandler; @Mock @@ -98,8 +103,6 @@ public class TransactionExecutorTests { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mClientRecord = new ActivityClientRecord(); when(mTransactionHandler.getActivityClient(any())).thenReturn(mClientRecord); diff --git a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java index 6998c32978c1..f5e9cc6953fb 100644 --- a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java +++ b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java @@ -26,8 +26,8 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.platform.test.annotations.RequiresFlagsEnabled; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java index 71c068d7ad46..9750de386271 100644 --- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java @@ -35,6 +35,7 @@ import static android.text.format.DateUtils.FORMAT_SHOW_YEAR; import static android.text.format.DateUtils.FORMAT_UTC; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.icu.util.Calendar; import android.icu.util.TimeZone; @@ -683,4 +684,12 @@ public class DateIntervalFormatTest { assertEquals("February 27\u2009\u2013\u2009March 1, 2004", fmt.apply(1077840000000L, 1078185600000L)); } + + @Test + public void testIsLibcoreVFlagEnabled() { + // This flag has been fully ramped. It should never be false. + assertTrue(DateIntervalFormat.isLibcoreVFlagEnabled()); + // Call a Android V API in libcore. + assertEquals("\ud840\udc2b", Character.toString(0x2002B)); + } } diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index b68ff78107ca..62291d405a60 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -237,8 +237,8 @@ public class ViewFrameRateTest { return; } waitForFrameRateCategoryToSettle(); - assertEquals(FRAME_RATE_CATEGORY_LOW, - mViewRoot.getLastPreferredFrameRateCategory()); + assertTrue(mViewRoot.getLastPreferredFrameRateCategory() + < FRAME_RATE_CATEGORY_HIGH_HINT); int width = mMovingView.getWidth(); int height = mMovingView.getHeight(); diff --git a/core/tests/coretests/src/android/view/WindowInfoTest.java b/core/tests/coretests/src/android/view/WindowInfoTest.java index d927f0632273..43e678fb634c 100644 --- a/core/tests/coretests/src/android/view/WindowInfoTest.java +++ b/core/tests/coretests/src/android/view/WindowInfoTest.java @@ -34,8 +34,8 @@ import android.platform.test.annotations.Presubmit; import android.text.TextUtils; import android.view.accessibility.AccessibilityNodeInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index ab4543cb8001..ba1204b9cc23 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -17,6 +17,7 @@ package android.view; import static android.view.WindowInsets.Type.SIZE; +import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.systemBars; import static org.junit.Assert.assertEquals; @@ -26,12 +27,14 @@ import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit @@ -68,4 +71,17 @@ public class WindowInsetsTest { true /* compatIgnoreVisibility */, null, null, 0, 0); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); } + + @Test + public void testSetBoundingRectsInBuilder_noInsets_preservedInWindowInsets() { + final List<Rect> rects = List.of(new Rect(0, 0, 50, 100)); + final WindowInsets insets = + new WindowInsets.Builder() + .setBoundingRects(captionBar(), rects) + .setBoundingRectsIgnoringVisibility(captionBar(), rects) + .build(); + + assertEquals(rects, insets.getBoundingRects(captionBar())); + assertEquals(rects, insets.getBoundingRectsIgnoringVisibility(captionBar())); + } } diff --git a/core/tests/coretests/src/android/view/WindowManagerTests.java b/core/tests/coretests/src/android/view/WindowManagerTests.java index c5a9d480771e..211d768dde93 100644 --- a/core/tests/coretests/src/android/view/WindowManagerTests.java +++ b/core/tests/coretests/src/android/view/WindowManagerTests.java @@ -25,8 +25,8 @@ import static org.junit.Assume.assumeTrue; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java index 39ea8af6fed9..f3ddfa68489f 100644 --- a/core/tests/coretests/src/android/view/WindowMetricsTest.java +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -27,9 +27,9 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 3d4918b1bd42..2d82d231e279 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java @@ -46,7 +46,7 @@ public class AccessibilityNodeInfoTest { // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest: // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo, // and assertAccessibilityNodeInfoCleared in that class. - private static final int NUM_MARSHALLED_PROPERTIES = 43; + private static final int NUM_MARSHALLED_PROPERTIES = 44; /** * The number of properties that are purposely not marshalled diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 3147eac30705..8db13c845b38 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -30,8 +30,8 @@ import static android.window.SystemPerformanceHinter.HINT_SF_FRAME_RATE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -42,14 +42,16 @@ import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.HashMap; @@ -63,6 +65,9 @@ import java.util.HashMap; @RunWith(AndroidJUnit4.class) public class SystemPerformanceHinterTests { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + private static final int DEFAULT_DISPLAY_ID = android.view.Display.DEFAULT_DISPLAY; private static final int SECONDARY_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1; private static final int NO_ROOT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2; @@ -83,8 +88,6 @@ public class SystemPerformanceHinterTests { @Before public void setUpOnce() { - MockitoAnnotations.initMocks(this); - mDefaultDisplayRoot = new SurfaceControl(); mSecondaryDisplayRoot = new SurfaceControl(); mRootProvider = new SystemPerformanceHinterTests.RootProvider(); diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java index 2dadb20d8f2c..4589607adb82 100644 --- a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java +++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java @@ -24,9 +24,9 @@ import android.content.Context; import android.platform.test.annotations.Presubmit; import android.view.WindowManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index 30c0f2b682db..1f60b3121192 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -34,14 +34,16 @@ import static org.mockito.Mockito.verify; import android.os.Binder; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Tests for {@link WindowContextController} @@ -56,6 +58,10 @@ import org.mockito.MockitoAnnotations; @SmallTest @Presubmit public class WindowContextControllerTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + private WindowContextController mController; @Mock private WindowTokenClientController mWindowTokenClientController; @@ -64,7 +70,6 @@ public class WindowContextControllerTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); mController = spy(new WindowContextController(mMockToken)); doReturn(mWindowTokenClientController).when(mController).getWindowTokenClientController(); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index b2a404449de7..f1fbd559b8f8 100644 --- a/core/tests/coretests/src/android/window/WindowContextTest.java +++ b/core/tests/coretests/src/android/window/WindowContextTest.java @@ -53,10 +53,10 @@ import android.view.WindowManagerGlobal; import android.view.WindowManagerImpl; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; diff --git a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java index 7cbb6b44ea57..accc0206e6e0 100644 --- a/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java +++ b/core/tests/coretests/src/android/window/WindowMetricsHelperTest.java @@ -31,9 +31,9 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.WindowInsets; import android.view.WindowMetrics; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import com.android.window.flags.Flags; diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 9ae96a0fc9d8..d153edd9da39 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -44,18 +44,20 @@ import android.view.IWindowSession; import android.view.ImeBackAnimationController; import android.view.MotionEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -70,6 +72,10 @@ import java.util.List; @SmallTest @Presubmit public class WindowOnBackInvokedDispatcherTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock private IWindowSession mWindowSession; @Mock @@ -106,8 +112,6 @@ public class WindowOnBackInvokedDispatcherTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - doReturn(true).when(mApplicationInfo).isOnBackInvokedCallbackEnabled(); doReturn(mApplicationInfo).when(mContext).getApplicationInfo(); diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java index a21c91782214..a3725af04ba6 100644 --- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java @@ -39,9 +39,11 @@ import android.view.IWindowManager; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; /** * Tests for {@link WindowTokenClientController}. @@ -53,6 +55,9 @@ import org.mockito.MockitoAnnotations; @Presubmit public class WindowTokenClientControllerTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + @Mock private IWindowManager mWindowManagerService; @Mock @@ -67,7 +72,6 @@ public class WindowTokenClientControllerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); mController = spy(WindowTokenClientController.createInstanceForTesting()); doReturn(mWindowManagerService).when(mController).getWindowManagerService(); mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY); diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java index 9292f667b27c..aa4c28a55865 100644 --- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java +++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java @@ -20,8 +20,8 @@ import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag; import android.platform.test.annotations.Presubmit; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index df95a91d72d7..b83931fa0615 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -35,6 +35,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; import android.os.LocaleList; +import android.text.ClientFlags; import android.text.GraphicsOperations; import android.text.SpannableString; import android.text.SpannedString; @@ -1540,8 +1541,21 @@ public class Paint { * @return typeface */ public Typeface setTypeface(Typeface typeface) { + return setTypefaceInternal(typeface, true); + } + + private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) { final long typefaceNative = typeface == null ? 0 : typeface.native_instance; nSetTypeface(mNativePaint, typefaceNative); + + if (ClientFlags.clearFontVariationSettings()) { + if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) { + // We cannot call setFontVariationSetting with empty string or null because it calls + // setTypeface method. To avoid recursive setTypeface call, manually resetting + // mFontVariationSettings. + mFontVariationSettings = null; + } + } mTypeface = typeface; return typeface; } @@ -2037,6 +2051,14 @@ public class Paint { * </li> * </ul> * + * Note: This method replaces the Typeface previously set to this instance. + * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of + * {@link #setTypeface(Typeface)} should call this method with empty settings, then call + * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings. + * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the + * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of + * {@link #setTypeface(Typeface)} should call this method again for applying variation settings. + * * @param fontVariationSettings font variation settings. You can pass null or empty string as * no variation settings. * @@ -2059,8 +2081,8 @@ public class Paint { if (settings == null || settings.length() == 0) { mFontVariationSettings = null; - setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, - Collections.emptyList())); + setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface, + Collections.emptyList()), false); return true; } @@ -2078,7 +2100,8 @@ public class Paint { return false; } mFontVariationSettings = settings; - setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes)); + setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes), + false); return true; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java index 8906e6d3d02e..88264f383153 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java @@ -34,7 +34,7 @@ import java.util.Optional; import java.util.function.Consumer; /** - * Implementation of {@link androidx.window.util.DataProducer} that produces a + * Implementation of {@link androidx.window.util.BaseDataProducer} that produces a * {@link String} that can be parsed to a {@link CommonFoldingFeature}. * {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in * settings where the {@link String} property is saved with the key diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java index ecf47209a802..7f11feaa585e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java @@ -39,6 +39,8 @@ import androidx.window.extensions.embedding.SplitController; import androidx.window.extensions.layout.WindowLayoutComponent; import androidx.window.extensions.layout.WindowLayoutComponentImpl; +import com.android.window.flags.Flags; + import java.util.Objects; @@ -55,11 +57,9 @@ class WindowExtensionsImpl implements WindowExtensions { */ private static final int NO_LEVEL_OVERRIDE = -1; - /** - * The min version of the WM Extensions that must be supported in the current platform version. - */ - @VisibleForTesting - static final int EXTENSIONS_VERSION_CURRENT_PLATFORM = 6; + private static final int EXTENSIONS_VERSION_V7 = 7; + + private static final int EXTENSIONS_VERSION_V6 = 6; private final Object mLock = new Object(); private volatile DeviceStateManagerFoldingFeatureProducer mFoldingFeatureProducer; @@ -67,7 +67,6 @@ class WindowExtensionsImpl implements WindowExtensions { private volatile SplitController mSplitController; private volatile WindowAreaComponent mWindowAreaComponent; - private final int mVersion = EXTENSIONS_VERSION_CURRENT_PLATFORM; private final boolean mIsActivityEmbeddingEnabled; WindowExtensionsImpl() { @@ -76,9 +75,22 @@ class WindowExtensionsImpl implements WindowExtensions { Log.i(TAG, generateLogMessage()); } + /** + * The min version of the WM Extensions that must be supported in the current platform version. + */ + @VisibleForTesting + static int getExtensionsVersionCurrentPlatform() { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + // Activity Embedding animation customization is the only major feature for v7. + return EXTENSIONS_VERSION_V7; + } else { + return EXTENSIONS_VERSION_V6; + } + } + private String generateLogMessage() { final StringBuilder logBuilder = new StringBuilder("Initializing Window Extensions, " - + "vendor API level=" + mVersion); + + "vendor API level=" + getExtensionsVersionCurrentPlatform()); final int levelOverride = getLevelOverride(); if (levelOverride != NO_LEVEL_OVERRIDE) { logBuilder.append(", override to ").append(levelOverride); @@ -91,7 +103,12 @@ class WindowExtensionsImpl implements WindowExtensions { @Override public int getVendorApiLevel() { final int levelOverride = getLevelOverride(); - return (levelOverride != NO_LEVEL_OVERRIDE) ? levelOverride : mVersion; + return hasLevelOverride() ? levelOverride : getExtensionsVersionCurrentPlatform(); + } + + @VisibleForTesting + boolean hasLevelOverride() { + return getLevelOverride() != NO_LEVEL_OVERRIDE; } private int getLevelOverride() { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ea60b1531a3f..f1e7ef5ce123 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -1359,4 +1359,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration, windowLayoutInfo); } + + @VisibleForTesting + @NonNull + static String positionToString(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> "left"; + case CONTAINER_POSITION_TOP -> "top"; + case CONTAINER_POSITION_RIGHT -> "right"; + case CONTAINER_POSITION_BOTTOM -> "bottom"; + default -> "Unknown position:" + position; + }; + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java index fe60037483c4..63828ab2e62b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java @@ -23,7 +23,7 @@ import java.util.function.Consumer; /** * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should * only be used once when {@link BaseDataProducer#notifyDataChanged} is called. - * @param <T> The type of data this producer returns through {@link DataProducer#getData}. + * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}. */ public class AcceptOnceConsumer<T> implements Consumer<T> { private final Consumer<T> mCallback; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java index de52f0969fa8..cd26efd4fdb6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java @@ -26,13 +26,12 @@ import java.util.Set; import java.util.function.Consumer; /** - * Base class that provides the implementation for the callback mechanism of the - * {@link DataProducer} API. This class is thread safe for adding, removing, and notifying - * consumers. + * Base class that manages listeners when listening to a piece of data that changes. This class is + * thread safe for adding, removing, and notifying consumers. * - * @param <T> The type of data this producer returns through {@link DataProducer#getData}. + * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}. */ -public abstract class BaseDataProducer<T> implements DataProducer<T>, +public abstract class BaseDataProducer<T> implements AcceptOnceConsumer.AcceptOnceProducerCallback<T> { private final Object mLock = new Object(); @@ -42,12 +41,17 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>, private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>(); /** + * Emits the first available data at that point in time. + * @param dataConsumer a {@link Consumer} that will receive one value. + */ + public abstract void getData(@NonNull Consumer<T> dataConsumer); + + /** * Adds a callback to the set of callbacks listening for data. Data is delivered through * {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers * should ensure that callbacks are thread safe. * @param callback that will receive data from the producer. */ - @Override public final void addDataChangedCallback(@NonNull Consumer<T> callback) { synchronized (mLock) { mCallbacks.add(callback); @@ -63,7 +67,6 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>, * @param callback that was registered in * {@link BaseDataProducer#addDataChangedCallback(Consumer)}. */ - @Override public final void removeDataChangedCallback(@NonNull Consumer<T> callback) { synchronized (mLock) { mCallbacks.remove(callback); @@ -92,8 +95,8 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>, /** * Called to notify all registered consumers that the data provided - * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need - * to ensure thread safety. + * by {@link BaseDataProducer#getData} has changed. Calls to this are thread save but callbacks + * need to ensure thread safety. */ protected void notifyDataChanged(T value) { synchronized (mLock) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java deleted file mode 100644 index ec301dc34aaa..000000000000 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.window.util; - -import android.annotation.NonNull; - -import java.util.function.Consumer; - -/** - * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving - * a callback when the data managed by the produces has changed. - * - * @param <T> The type of data this producer returns through {@link DataProducer#getData}. - */ -public interface DataProducer<T> { - /** - * Emits the first available data at that point in time. - * @param dataConsumer a {@link Consumer} that will receive one value. - */ - void getData(@NonNull Consumer<T> dataConsumer); - - /** - * Adds a callback to be notified when the data returned - * from {@link DataProducer#getData} has changed. - */ - void addDataChangedCallback(@NonNull Consumer<T> callback); - - /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */ - void removeDataChangedCallback(@NonNull Consumer<T> callback); -} diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp index 61ea51a35f58..139ddda5af3c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp +++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp @@ -32,6 +32,7 @@ android_test { ], static_libs: [ + "TestParameterInjector", "androidx.window.extensions", "androidx.window.extensions.core_core", "junit", diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index c5aaddc4e0ed..92f48141b607 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -16,7 +16,7 @@ package androidx.window.extensions; -import static androidx.window.extensions.WindowExtensionsImpl.EXTENSIONS_VERSION_CURRENT_PLATFORM; +import static androidx.window.extensions.WindowExtensionsImpl.getExtensionsVersionCurrentPlatform; import static com.google.common.truth.Truth.assertThat; @@ -59,7 +59,8 @@ public class WindowExtensionsTest { @Test public void testGetVendorApiLevel_extensionsEnabled_matchesCurrentVersion() { assumeTrue(WindowManager.hasWindowExtensionsEnabled()); - assertThat(mVersion).isEqualTo(EXTENSIONS_VERSION_CURRENT_PLATFORM); + assumeFalse(((WindowExtensionsImpl) mExtensions).hasLevelOverride()); + assertThat(mVersion).isEqualTo(getExtensionsVersionCurrentPlatform()); } @Test diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 325750243744..1c4c8870b26f 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -35,6 +35,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition; +import static androidx.window.extensions.embedding.SplitPresenter.positionToString; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -78,7 +79,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.extensions.layout.WindowLayoutComponentImpl; @@ -86,6 +86,9 @@ import androidx.window.extensions.layout.WindowLayoutInfo; import com.android.window.flags.Flags; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -108,7 +111,7 @@ import java.util.List; @SuppressWarnings("GuardedBy") @Presubmit @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class OverlayPresentationTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); @@ -875,57 +878,70 @@ public class OverlayPresentationTest { eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } - // TODO(b/243518738): Rewrite with TestParameter. - @Test - public void testGetOverlayPosition() { - assertWithMessage("It must be position left for left overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top, - TASK_BOUNDS.right / 2, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); - assertWithMessage("It must be position left for shrunk left overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top + 20, - TASK_BOUNDS.right / 2, - TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); - assertWithMessage("It must be position left for top overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.top, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); - assertWithMessage("It must be position left for shrunk top overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left + 20, - TASK_BOUNDS.top, - TASK_BOUNDS.right - 20, - TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); - assertWithMessage("It must be position left for right overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.right / 2, - TASK_BOUNDS.top, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); - assertWithMessage("It must be position left for shrunk right overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.right / 2, - TASK_BOUNDS.top + 20, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); - assertWithMessage("It must be position left for bottom overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left, - TASK_BOUNDS.bottom / 2, - TASK_BOUNDS.right, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); - assertWithMessage("It must be position left for shrunk bottom overlay.") - .that(getOverlayPosition(new Rect( - TASK_BOUNDS.left + 20, - TASK_BOUNDS.bottom / 20, - TASK_BOUNDS.right - 20, - TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + @Test + public void testGetOverlayPosition(@TestParameter OverlayPositionTestParams params) { + final Rect taskBounds = new Rect(TASK_BOUNDS); + final Rect overlayBounds = params.toOverlayBounds(); + final int overlayPosition = getOverlayPosition(overlayBounds, taskBounds); + + assertWithMessage("The overlay position must be " + + positionToString(params.mPosition) + ", but is " + + positionToString(overlayPosition) + + ", parent bounds=" + taskBounds + ", overlay bounds=" + overlayBounds) + .that(overlayPosition).isEqualTo(params.mPosition); + } + + private enum OverlayPositionTestParams { + LEFT_OVERLAY(CONTAINER_POSITION_LEFT, false /* shouldBeShrunk */), + LEFT_SHRUNK_OVERLAY(CONTAINER_POSITION_LEFT, true /* shouldBeShrunk */), + TOP_OVERLAY(CONTAINER_POSITION_TOP, false /* shouldBeShrunk */), + TOP_SHRUNK_OVERLAY(CONTAINER_POSITION_TOP, true /* shouldBeShrunk */), + RIGHT_OVERLAY(CONTAINER_POSITION_RIGHT, false /* shouldBeShrunk */), + RIGHT_SHRUNK_OVERLAY(CONTAINER_POSITION_RIGHT, true /* shouldBeShrunk */), + BOTTOM_OVERLAY(CONTAINER_POSITION_BOTTOM, false /* shouldBeShrunk */), + BOTTOM_SHRUNK_OVERLAY(CONTAINER_POSITION_BOTTOM, true /* shouldBeShrunk */); + + @SplitPresenter.ContainerPosition + private final int mPosition; + + private final boolean mShouldBeShrunk; + + OverlayPositionTestParams( + @SplitPresenter.ContainerPosition int position, boolean shouldBeShrunk) { + mPosition = position; + mShouldBeShrunk = shouldBeShrunk; + } + + @NonNull + private Rect toOverlayBounds() { + Rect r = new Rect(TASK_BOUNDS); + final int offset = mShouldBeShrunk ? 20 : 0; + switch (mPosition) { + case CONTAINER_POSITION_LEFT: + r.top += offset; + r.right /= 2; + r.bottom -= offset; + break; + case CONTAINER_POSITION_TOP: + r.left += offset; + r.right -= offset; + r.bottom /= 2; + break; + case CONTAINER_POSITION_RIGHT: + r.left = r.right / 2; + r.top += offset; + r.bottom -= offset; + break; + case CONTAINER_POSITION_BOTTOM: + r.left += offset; + r.right -= offset; + r.top = r.bottom / 2; + break; + default: + throw new IllegalArgumentException("Invalid position: " + mPosition); + } + return r; + } } /** diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt index f0d80a02243a..d3fc49bcb766 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -39,8 +39,6 @@ enum class DesktopModeFlags( /** * Determines state of flag based on the actual flag and desktop mode developer option overrides. - * - * Note, this method makes sure that a constant developer toggle overrides is read until reboot. */ fun isEnabled(context: Context): Boolean = if (!Flags.showDesktopWindowingDevOption() || @@ -65,7 +63,7 @@ enum class DesktopModeFlags( ?: run { val override = getToggleOverrideFromSystem(context) // Cache toggle override the first time we encounter context. Override does not change - // with context, as context is just used to fetch System Property and Settings.Global + // with context, as context is just used to fetch Settings.Global cachedToggleOverride = override Log.d(TAG, "Toggle override initialized to: $override") override @@ -74,29 +72,13 @@ enum class DesktopModeFlags( return override } - private fun getToggleOverrideFromSystem(context: Context): ToggleOverride { - // A non-persistent System Property is used to store override to ensure it remains - // constant till reboot. - val overrideFromSystemProperties: ToggleOverride? = - System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null).convertToToggleOverride() - return overrideFromSystemProperties - ?: run { - // Read Setting Global if System Property is not present (just after reboot) - // or not valid (user manually changed the value) - val overrideFromSettingsGlobal = - convertToToggleOverrideWithFallback( - Settings.Global.getInt( - context.contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.setting), - ToggleOverride.OVERRIDE_UNSET) - // Initialize System Property - System.setProperty( - SYSTEM_PROPERTY_OVERRIDE_KEY, overrideFromSettingsGlobal.setting.toString()) - - overrideFromSettingsGlobal - } - } + private fun getToggleOverrideFromSystem(context: Context): ToggleOverride = + convertToToggleOverrideWithFallback( + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, + ToggleOverride.OVERRIDE_UNSET.setting), + ToggleOverride.OVERRIDE_UNSET) /** * Override state of desktop mode developer option toggle. @@ -113,27 +95,12 @@ enum class DesktopModeFlags( OVERRIDE_ON(1) } - private fun String?.convertToToggleOverride(): ToggleOverride? { - val intValue = this?.toIntOrNull() ?: return null - return settingToToggleOverrideMap[intValue] - ?: run { - Log.w(TAG, "Unknown toggleOverride int $intValue") - null - } - } - companion object { private const val TAG = "DesktopModeFlags" /** - * Key for non-persistent System Property which is used to store desktop windowing developer - * option overrides. - */ - private const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" - - /** * Local cache for toggle override, which is initialized once on its first access. It needs to - * be refreshed only on reboots as overridden state takes effect on reboots. + * be refreshed only on reboots as overridden state is expected to take effect on reboots. */ private var cachedToggleOverride: ToggleOverride? = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 64a1b0c804da..140d7765e5c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -30,6 +30,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.util.ArraySet; import android.util.Size; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; @@ -42,9 +43,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -69,26 +68,36 @@ public class PipBoundsState { @Retention(RetentionPolicy.SOURCE) public @interface StashType {} + public static final int NAMED_KCA_LAUNCHER_SHELF = 0; + public static final int NAMED_KCA_TABLETOP_MODE = 1; + + @IntDef(prefix = { "NAMED_KCA_" }, value = { + NAMED_KCA_LAUNCHER_SHELF, + NAMED_KCA_TABLETOP_MODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NamedKca {} + private static final String TAG = PipBoundsState.class.getSimpleName(); - private final @NonNull Rect mBounds = new Rect(); - private final @NonNull Rect mMovementBounds = new Rect(); - private final @NonNull Rect mNormalBounds = new Rect(); - private final @NonNull Rect mExpandedBounds = new Rect(); - private final @NonNull Rect mNormalMovementBounds = new Rect(); - private final @NonNull Rect mExpandedMovementBounds = new Rect(); - private final @NonNull PipDisplayLayoutState mPipDisplayLayoutState; + @NonNull private final Rect mBounds = new Rect(); + @NonNull private final Rect mMovementBounds = new Rect(); + @NonNull private final Rect mNormalBounds = new Rect(); + @NonNull private final Rect mExpandedBounds = new Rect(); + @NonNull private final Rect mNormalMovementBounds = new Rect(); + @NonNull private final Rect mExpandedMovementBounds = new Rect(); + @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState; private final Point mMaxSize = new Point(); private final Point mMinSize = new Point(); - private final @NonNull Context mContext; + @NonNull private final Context mContext; private float mAspectRatio; private int mStashedState = STASH_TYPE_NONE; private int mStashOffset; - private @Nullable PipReentryState mPipReentryState; + @Nullable private PipReentryState mPipReentryState; private final LauncherState mLauncherState = new LauncherState(); - private final @NonNull SizeSpecSource mSizeSpecSource; - private @Nullable ComponentName mLastPipComponentName; - private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); + @NonNull private final SizeSpecSource mSizeSpecSource; + @Nullable private ComponentName mLastPipComponentName; + @NonNull private final MotionBoundsState mMotionBoundsState = new MotionBoundsState(); private boolean mIsImeShowing; private int mImeHeight; private boolean mIsShelfShowing; @@ -120,12 +129,18 @@ public class PipBoundsState { * as unrestricted keep clear area. Values in this map would be appended to * {@link #getUnrestrictedKeepClearAreas()} and this is meant for internal usage only. */ - private final Map<String, Rect> mNamedUnrestrictedKeepClearAreas = new HashMap<>(); + private final SparseArray<Rect> mNamedUnrestrictedKeepClearAreas = new SparseArray<>(); - private @Nullable Runnable mOnMinimalSizeChangeCallback; - private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; - private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); - private List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>(); + @Nullable private Runnable mOnMinimalSizeChangeCallback; + @Nullable private TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; + private final List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + private final List<Consumer<Float>> mOnAspectRatioChangedCallbacks = new ArrayList<>(); + + /** + * This is used to set the launcher shelf height ahead of non-auto-enter-pip animation, + * to avoid the race condition. See also {@link #NAMED_KCA_LAUNCHER_SHELF}. + */ + public final Rect mCachedLauncherShelfHeightKeepClearArea = new Rect(); // the size of the current bounds relative to the max size spec private float mBoundsScale; @@ -430,17 +445,32 @@ public class PipBoundsState { mUnrestrictedKeepClearAreas.addAll(unrestrictedAreas); } - /** Add a named unrestricted keep clear area. */ - public void addNamedUnrestrictedKeepClearArea(@NonNull String name, Rect unrestrictedArea) { - mNamedUnrestrictedKeepClearAreas.put(name, unrestrictedArea); + /** Set a named unrestricted keep clear area. */ + public void setNamedUnrestrictedKeepClearArea( + @NamedKca int tag, @Nullable Rect unrestrictedArea) { + if (unrestrictedArea == null) { + mNamedUnrestrictedKeepClearAreas.remove(tag); + } else { + mNamedUnrestrictedKeepClearAreas.put(tag, unrestrictedArea); + if (tag == NAMED_KCA_LAUNCHER_SHELF) { + mCachedLauncherShelfHeightKeepClearArea.set(unrestrictedArea); + } + } } - /** Remove a named unrestricted keep clear area. */ - public void removeNamedUnrestrictedKeepClearArea(@NonNull String name) { - mNamedUnrestrictedKeepClearAreas.remove(name); + /** + * Forcefully set the keep-clear-area for launcher shelf height if applicable. + * This is used for entering PiP in button navigation mode to make sure the destination bounds + * calculation includes the shelf height, to avoid race conditions that such callback is sent + * from Launcher after the entering animation is started. + */ + public void mayUseCachedLauncherShelfHeight() { + if (!mCachedLauncherShelfHeightKeepClearArea.isEmpty()) { + setNamedUnrestrictedKeepClearArea( + NAMED_KCA_LAUNCHER_SHELF, mCachedLauncherShelfHeightKeepClearArea); + } } - /** * @return restricted keep clear areas. */ @@ -454,9 +484,12 @@ public class PipBoundsState { */ @NonNull public Set<Rect> getUnrestrictedKeepClearAreas() { - if (mNamedUnrestrictedKeepClearAreas.isEmpty()) return mUnrestrictedKeepClearAreas; + if (mNamedUnrestrictedKeepClearAreas.size() == 0) return mUnrestrictedKeepClearAreas; final Set<Rect> unrestrictedAreas = new ArraySet<>(mUnrestrictedKeepClearAreas); - unrestrictedAreas.addAll(mNamedUnrestrictedKeepClearAreas.values()); + for (int i = 0; i < mNamedUnrestrictedKeepClearAreas.size(); i++) { + final int key = mNamedUnrestrictedKeepClearAreas.keyAt(i); + unrestrictedAreas.add(mNamedUnrestrictedKeepClearAreas.get(key)); + } return unrestrictedAreas; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 80f6a637ba1c..700742acfb43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -534,7 +534,8 @@ public abstract class WMShellModule { MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<RecentTasksController> recentTasksController) { + Optional<RecentTasksController> recentTasksController, + InteractionJankMonitor interactionJankMonitor) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler, @@ -542,7 +543,8 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, - mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null)); + mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null), + interactionJankMonitor); } @WMSingleton @@ -568,9 +570,10 @@ public abstract class WMShellModule { Context context, Transitions transitions, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - Optional<DesktopTasksLimiter> desktopTasksLimiter) { + Optional<DesktopTasksLimiter> desktopTasksLimiter, + InteractionJankMonitor interactionJankMonitor) { return new DragToDesktopTransitionHandler(context, transitions, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, interactionJankMonitor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 1a9c304e2aee..037fbb235bd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -206,12 +206,13 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipMotionHelper providePipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, Optional<PipPerfHintController> pipPerfHintControllerOptional) { - return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, + return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer, menuController, pipSnapAlgorithm, pipTransitionController, floatingContentCoordinator, pipPerfHintControllerOptional); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index 066b5ad39d0f..73aa7ceea68d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -347,7 +347,7 @@ class DesktopModeLoggerTransitionObserver( else -> { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "Unknown enter reason for transition type ${transitionInfo.type}", + "Unknown enter reason for transition type: %s", transitionInfo.type ) EnterReason.UNKNOWN_ENTER @@ -368,7 +368,7 @@ class DesktopModeLoggerTransitionObserver( else -> { ProtoLog.w( WM_SHELL_DESKTOP_MODE, - "Unknown exit reason for transition type ${transitionInfo.type}", + "Unknown exit reason for transition type: %s", transitionInfo.type ) ExitReason.UNKNOWN_EXIT 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 de901b5dabd8..9fd2c274df2d 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 @@ -49,6 +49,9 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread import com.android.internal.annotations.VisibleForTesting +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags @@ -123,7 +126,8 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, - private val recentTasksController: RecentTasksController? + private val recentTasksController: RecentTasksController?, + private val interactionJankMonitor: InteractionJankMonitor ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -378,12 +382,15 @@ class DesktopTasksController( fun startDragToDesktop( taskInfo: RunningTaskInfo, dragToDesktopValueAnimator: MoveToDesktopAnimator, + taskSurface: SurfaceControl, ) { ProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: startDragToDesktop taskId=%d", taskInfo.taskId ) + interactionJankMonitor.begin(taskSurface, context, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo.taskId, dragToDesktopValueAnimator @@ -1340,13 +1347,19 @@ class DesktopTasksController( fun onDragPositioningEndThroughStatusBar( inputCoordinates: PointF, taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, ): IndicatorType { + // End the drag_hold CUJ interaction. + interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return IndicatorType.NO_INDICATOR + // Start a new jank interaction for the drag release to desktop window animation. + interactionJankMonitor.begin(taskSurface, context, + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") if (Flags.enableWindowingDynamicInitialBounds()) { finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) } else { 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 ddee8fac8f44..9e79eddb0e59 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 @@ -30,6 +30,9 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.internal.protolog.ProtoLog +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD +import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE +import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT @@ -57,17 +60,20 @@ class DragToDesktopTransitionHandler( private val context: Context, private val transitions: Transitions, private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - private val transactionSupplier: Supplier<SurfaceControl.Transaction> + private val interactionJankMonitor: InteractionJankMonitor, + private val transactionSupplier: Supplier<SurfaceControl.Transaction>, ) : TransitionHandler { constructor( context: Context, transitions: Transitions, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + interactionJankMonitor: InteractionJankMonitor ) : this( context, transitions, rootTaskDisplayAreaOrganizer, + interactionJankMonitor, Supplier { SurfaceControl.Transaction() } ) @@ -567,6 +573,8 @@ class DragToDesktopTransitionHandler( onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(null /* null */) clearState() + interactionJankMonitor.end( + CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) } } ) @@ -604,6 +612,10 @@ class DragToDesktopTransitionHandler( "DragToDesktop: onTransitionConsumed() start transition aborted" ) state.startAborted = true + // Cancel CUJ interaction if the transition is aborted. + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) + } else if (state.cancelTransitionToken != transition) { + interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index a749019046f8..b27c428f1693 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -16,10 +16,12 @@ package com.android.wm.shell.pip; +import android.annotation.NonNull; import android.graphics.Rect; import com.android.wm.shell.shared.annotations.ExternalThread; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -69,9 +71,10 @@ public interface Pip { default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } /** - * @return {@link PipTransitionController} instance. + * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition + * started / finished callbacks. */ - default PipTransitionController getPipTransitionController() { - return null; - } + default void registerPipTransitionCallback( + @NonNull PipTransitionController.PipTransitionCallback callback, + @NonNull Executor executor) { } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index a8346a9b3b48..852382ddfba1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -230,6 +230,7 @@ public class PipAnimationController { /** * Quietly cancel the animator by removing the listeners first. + * TODO(b/275003573): deprecate this, cancelling without the proper callbacks is problematic. */ static void quietCancel(@NonNull ValueAnimator animator) { animator.removeAllUpdateListeners(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 8d63ff2a3a5d..723a53128bd0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -423,7 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback( + mPipTransitionCallback, mMainExecutor); } } @@ -495,7 +496,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState); mPipTransitionState.setInSwipePipToHomeTransition(true); - sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + if (!ENABLE_SHELL_TRANSITIONS) { + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + } setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo); return mPipBoundsAlgorithm.getEntryDestinationBounds(); } @@ -2023,7 +2026,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, removeContentOverlay(mPipOverlay, null /* callback */); } if (animator != null) { - PipAnimationController.quietCancel(animator); + animator.cancel(); mPipAnimationController.resetAnimatorState(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index e5633de2a3a2..a52141c5f9d6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -1020,6 +1020,9 @@ public class PipTransition extends PipTransitionController { mPipMenuController.attach(leash); } + // Make sure we have the launcher shelf into destination bounds calculation + // before the animator starts. + mPipBoundsState.mayUseCachedLauncherShelfHeight(); final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = pipChange.getStartAbsBounds(); @@ -1173,7 +1176,13 @@ public class PipTransition extends PipTransitionController { .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); } - final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); + sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP); + // Both Shell and Launcher calculate their own "adjusted" source-rect-hint values based on + // appBounds being source bounds when entering PiP. + final Rect sourceBounds = swipePipToHomeOverlay == null + ? pipTaskInfo.configuration.windowConfiguration.getBounds() + : mPipOrganizer.mAppBounds; + final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index b1dd4f1c1395..fc9e2becf98c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -53,8 +53,9 @@ import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; /** * Responsible supplying PiP Transitions. @@ -66,7 +67,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected final ShellTaskOrganizer mShellTaskOrganizer; protected final PipMenuController mPipMenuController; protected final Transitions mTransitions; - private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); + private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>(); protected PipTaskOrganizer mPipOrganizer; protected DefaultMixedHandler mMixedHandler; @@ -183,16 +184,20 @@ public abstract class PipTransitionController implements Transitions.TransitionH /** * Registers {@link PipTransitionCallback} to receive transition callbacks. */ - public void registerPipTransitionCallback(PipTransitionCallback callback) { - mPipTransitionCallbacks.add(callback); + public void registerPipTransitionCallback( + @NonNull PipTransitionCallback callback, @NonNull Executor executor) { + mPipTransitionCallbacks.put(callback, executor); } protected void sendOnPipTransitionStarted( @PipAnimationController.TransitionDirection int direction) { final Rect pipBounds = mPipBoundsState.getBounds(); - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionStarted(direction, pipBounds); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "sendOnPipTransitionStarted direction=%d, bounds=%s", direction, pipBounds); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionStarted(direction, pipBounds)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -209,9 +214,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionFinished( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionFinished(direction); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "sendOnPipTransitionFinished direction=%d", direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionFinished(direction)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -228,9 +236,12 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionCancelled( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionCanceled(direction); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "sendOnPipTransitionCancelled direction=%d", direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionCanceled(direction)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 26b7e58bc602..7451d2251588 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -106,6 +106,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -478,7 +479,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mShellCommandHandler.addDumpCallback(this::dump, this); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), INPUT_CONSUMER_PIP, mMainExecutor); - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> { mPipDisplayLayoutState.setDisplayId(displayId); onDisplayChanged(mDisplayController.getDisplayLayout(displayId), @@ -645,9 +646,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb }); mTabletopModeController.registerOnTabletopModeChangedListener((isInTabletopMode) -> { - final String tag = "tabletop-mode"; if (!isInTabletopMode) { - mPipBoundsState.removeNamedUnrestrictedKeepClearArea(tag); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, null); return; } @@ -656,14 +657,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb if (mTabletopModeController.getPreferredHalfInTabletopMode() == TabletopModeController.PREFERRED_TABLETOP_HALF_TOP) { // Prefer top, avoid the bottom half of the display. - mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect( - displayBounds.left, displayBounds.centerY(), - displayBounds.right, displayBounds.bottom)); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect( + displayBounds.left, displayBounds.centerY(), + displayBounds.right, displayBounds.bottom)); } else { // Prefer bottom, avoid the top half of the display. - mPipBoundsState.addNamedUnrestrictedKeepClearArea(tag, new Rect( - displayBounds.left, displayBounds.top, - displayBounds.right, displayBounds.centerY())); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_TABLETOP_MODE, new Rect( + displayBounds.left, displayBounds.top, + displayBounds.right, displayBounds.centerY())); } // Try to move the PiP window if we have entered PiP mode. @@ -915,10 +918,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb 0, mPipBoundsState.getDisplayBounds().bottom - height, mPipBoundsState.getDisplayBounds().right, mPipBoundsState.getDisplayBounds().bottom); - mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); updatePipPositionForKeepClearAreas(); } else { - mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); // postpone moving in response to hide of Launcher in case there's another change mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback); mMainExecutor.executeDelayed( @@ -967,8 +972,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb int launcherRotation, Rect hotseatKeepClearArea) { // preemptively add the keep clear area for Hotseat, so that it is taken into account // when calculating the entry destination bounds of PiP window - mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, - hotseatKeepClearArea); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, hotseatKeepClearArea); onDisplayRotationChangedNotInPip(mContext, launcherRotation); // cache current min/max size Point minSize = mPipBoundsState.getMinSize(); @@ -1220,8 +1225,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public PipTransitionController getPipTransitionController() { - return mPipTransitionController; + public void registerPipTransitionCallback( + PipTransitionController.PipTransitionCallback callback, + Executor executor) { + mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback( + callback, executor)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index e8d6576bab3b..df3803d54d9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -38,6 +38,7 @@ import com.android.internal.protolog.ProtoLog; 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.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsState; @@ -47,6 +48,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; +import com.android.wm.shell.shared.annotations.ShellMainThread; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -171,7 +173,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public void onPipTransitionCanceled(int direction) {} }; - public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, + public PipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, + @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, @@ -183,7 +187,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { mPipTaskOrganizer.scheduleUserResizePip(getBounds(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 62c0944f230b..0ed5079b7fba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -257,7 +257,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private void onInit() { - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); reloadResources(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java new file mode 100644 index 000000000000..8a9302bcfc98 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterExitAnimator.java @@ -0,0 +1,150 @@ +/* + * 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.wm.shell.pip2.animation; + +import android.animation.Animator; +import android.animation.RectEvaluator; +import android.animation.ValueAnimator; +import android.annotation.IntDef; +import android.content.Context; +import android.graphics.Rect; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Animator that handles bounds animations for entering / exiting PIP. + */ +public class PipEnterExitAnimator extends ValueAnimator + implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { + @IntDef(prefix = {"BOUNDS_"}, value = { + BOUNDS_ENTER, + BOUNDS_EXIT + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface BOUNDS {} + + public static final int BOUNDS_ENTER = 0; + public static final int BOUNDS_EXIT = 1; + + @NonNull private final SurfaceControl mLeash; + private final SurfaceControl.Transaction mStartTransaction; + private final int mEnterAnimationDuration; + private final @BOUNDS int mDirection; + + // optional callbacks for tracking animation start and end + @Nullable private Runnable mAnimationStartCallback; + @Nullable private Runnable mAnimationEndCallback; + + private final Rect mBaseBounds = new Rect(); + private final Rect mStartBounds = new Rect(); + private final Rect mEndBounds = new Rect(); + + // Bounds updated by the evaluator as animator is running. + private final Rect mAnimatedRect = new Rect(); + + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + private final RectEvaluator mRectEvaluator; + private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper; + + public PipEnterExitAnimator(Context context, + @NonNull SurfaceControl leash, + SurfaceControl.Transaction startTransaction, + @NonNull Rect baseBounds, + @NonNull Rect startBounds, + @NonNull Rect endBounds, + @BOUNDS int direction) { + mLeash = leash; + mStartTransaction = startTransaction; + mBaseBounds.set(baseBounds); + mStartBounds.set(startBounds); + mAnimatedRect.set(startBounds); + mEndBounds.set(endBounds); + mRectEvaluator = new RectEvaluator(mAnimatedRect); + mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context); + mDirection = direction; + + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + mEnterAnimationDuration = context.getResources() + .getInteger(R.integer.config_pipEnterAnimationDuration); + + setDuration(mEnterAnimationDuration); + setEvaluator(mRectEvaluator); + addListener(this); + addUpdateListener(this); + } + + public void setAnimationStartCallback(@NonNull Runnable runnable) { + mAnimationStartCallback = runnable; + } + + public void setAnimationEndCallback(@NonNull Runnable runnable) { + mAnimationEndCallback = runnable; + } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + if (mAnimationStartCallback != null) { + mAnimationStartCallback.run(); + } + if (mStartTransaction != null) { + mStartTransaction.apply(); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + if (mAnimationEndCallback != null) { + mAnimationEndCallback.run(); + } + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + final float fraction = getAnimatedFraction(); + // TODO (b/350801661): implement fixed rotation + + mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, null, + mBaseBounds, mAnimatedRect, null, isInPipDirection(), fraction) + .round(tx, mLeash, isInPipDirection()) + .shadow(tx, mLeash, isInPipDirection()); + tx.apply(); + } + + private boolean isInPipDirection() { + return mDirection == BOUNDS_ENTER; + } + + // no-ops + + @Override + public void onAnimationCancel(@NonNull Animator animation) {} + + @Override + public void onAnimationRepeat(@NonNull Animator animation) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 683d30d59b33..33703ad95a9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -53,6 +53,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipContentOverlay; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; +import com.android.wm.shell.pip2.animation.PipEnterExitAnimator; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -378,12 +379,34 @@ public class PipTransition extends PipTransitionController implements if (pipChange == null) { return false; } - // cache the PiP task token and leash + WindowContainerToken pipTaskToken = pipChange.getContainer(); + if (pipTaskToken == null) { + return false; + } - startTransaction.apply(); - // TODO: b/275910498 Use a new implementation of the PiP animator here. - finishCallback.onTransitionFinished(null); + WindowContainerTransaction finishWct = new WindowContainerTransaction(); + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + + Rect startBounds = pipChange.getStartAbsBounds(); + Rect endBounds = pipChange.getEndAbsBounds(); + SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition."); + + PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, + startTransaction, startBounds, startBounds, endBounds, + PipEnterExitAnimator.BOUNDS_ENTER); + + tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), + this::onClientDrawAtTransitionEnd); + finishWct.setBoundsChangeTransaction(pipTaskToken, tx); + + animator.setAnimationEndCallback(() -> { + mPipTransitionState.setState(PipTransitionState.ENTERED_PIP); + finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); + }); + + animator.start(); return true; } @@ -421,10 +444,25 @@ public class PipTransition extends PipTransitionController implements @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - startTransaction.apply(); - // TODO: b/275910498 Use a new implementation of the PiP animator here. - finishCallback.onTransitionFinished(null); - mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + TransitionInfo.Change pipChange = getPipChange(info); + if (pipChange == null) { + return false; + } + + Rect startBounds = pipChange.getStartAbsBounds(); + Rect endBounds = pipChange.getEndAbsBounds(); + SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition."); + + PipEnterExitAnimator animator = new PipEnterExitAnimator(mContext, pipLeash, + startTransaction, startBounds, startBounds, endBounds, + PipEnterExitAnimator.BOUNDS_EXIT); + animator.setAnimationEndCallback(() -> { + finishCallback.onTransitionFinished(null); + mPipTransitionState.setState(PipTransitionState.EXITED_PIP); + }); + + animator.start(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index b3dab8527617..48d17ec6963f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -68,6 +68,7 @@ class SplitScreenTransitions { DismissSession mPendingDismiss = null; EnterSession mPendingEnter = null; TransitSession mPendingResize = null; + TransitSession mPendingRemotePassthrough = null; private IBinder mAnimatingTransition = null; private OneShotRemoteHandler mActiveRemoteHandler = null; @@ -320,6 +321,11 @@ class SplitScreenTransitions { return mPendingResize != null && mPendingResize.mTransition == transition; } + boolean isPendingPassThrough(IBinder transition) { + return mPendingRemotePassthrough != null && + mPendingRemotePassthrough.mTransition == transition; + } + @Nullable private TransitSession getPendingTransition(IBinder transition) { if (isPendingEnter(transition)) { @@ -331,6 +337,9 @@ class SplitScreenTransitions { } else if (isPendingResize(transition)) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition"); return mPendingResize; + } else if (isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition"); + return mPendingRemotePassthrough; } return null; } @@ -378,6 +387,19 @@ class SplitScreenTransitions { extraTransitType, resizeAnim); } + /** Sets a transition to enter split. */ + void setRemotePassThroughTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition) { + mPendingRemotePassthrough = new TransitSession( + transition, null, null, + remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH); + + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced remote passthrough split screen"); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s", + Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition); + } + /** Starts a transition to dismiss split. */ IBinder startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @@ -474,6 +496,12 @@ class SplitScreenTransitions { mPendingResize.onConsumed(aborted); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition"); + } else if (isPendingPassThrough(transition)) { + mPendingRemotePassthrough.onConsumed(aborted); + mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted, + finishT); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition"); } // TODO: handle transition consumed for active remote handler @@ -495,6 +523,10 @@ class SplitScreenTransitions { mPendingResize.onFinished(wct, mFinishTransaction); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition"); + } else if (isPendingPassThrough(mAnimatingTransition)) { + mPendingRemotePassthrough.onFinished(wct, mFinishTransaction); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition"); } mActiveRemoteHandler = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index a4f32c45c0a9..d7ee563b562c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2710,7 +2710,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - if (isSplitScreenVisible()) { + if (isSplitActive()) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d display rotation", request.getDebugId()); // Check if the display is rotating. @@ -2720,6 +2720,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, && displayChange.getStartRotation() != displayChange.getEndRotation()) { mSplitLayout.setFreezeDividerWindow(true); } + if (request.getRemoteTransition() != null) { + mSplitTransitions.setRemotePassThroughTransition(transition, + request.getRemoteTransition()); + } // Still want to monitor everything while in split-screen, so return non-null. return new WindowContainerTransaction(); } else { @@ -3046,6 +3050,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, notifySplitAnimationFinished(); return true; } + } else if (mSplitTransitions.isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startAnimation: passThrough transition=%d", info.getDebugId()); + mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition, + info, startTransaction, finishTransaction, finishCallback); + notifySplitAnimationFinished(); + return true; } return startPendingAnimation(transition, info, startTransaction, finishTransaction, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 5c814dcc9b16..bad5baf24651 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -73,7 +73,7 @@ class WindowlessSnapshotWindowCreator { final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId); final StartingSurfaceDrawer.WindowlessStartingWindow wlw = new StartingSurfaceDrawer.WindowlessStartingWindow( - runningTaskInfo.configuration, rootSurface); + mContext.getResources().getConfiguration(), rootSurface); final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost( mContext, display, wlw, "WindowlessSnapshotWindowCreator"); final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java index 98a803128587..f3725579bf48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java @@ -76,7 +76,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } final StartingSurfaceDrawer.WindowlessStartingWindow wlw = new StartingSurfaceDrawer.WindowlessStartingWindow( - taskInfo.configuration, rootSurface); + mContext.getResources().getConfiguration(), rootSurface); final SurfaceControlViewHost viewHost = new SurfaceControlViewHost( myContext, display, wlw, "WindowlessSplashWindowCreator"); final String title = "Windowless Splash " + taskInfo.taskId; @@ -95,7 +95,7 @@ class WindowlessSplashWindowCreator extends AbsSplashWindowCreator { } final FrameLayout rootLayout = new FrameLayout( - mSplashscreenContentDrawer.createViewContextWrapper(mContext)); + mSplashscreenContentDrawer.createViewContextWrapper(myContext)); viewHost.setView(rootLayout, lp); final int bgColor = taskDescription.getBackgroundColor(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index fc8b1d27ad02..874cca523ad1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -190,6 +190,9 @@ public class Transitions implements RemoteCallable<Transitions>, // TRANSIT_FIRST_CUSTOM + 17 TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; + /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */ + public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18; + /** Transition type for desktop mode transitions. */ public static final int TRANSIT_DESKTOP_MODE_TYPES = WindowManager.TRANSIT_FIRST_CUSTOM + 100; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index d1ec1c84c125..de1948331f40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -964,7 +964,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.updateHoverAndPressStatus(ev); DesktopModeVisualIndicator.IndicatorType resultType = mDesktopTasksController.onDragPositioningEndThroughStatusBar( - new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo); + new PointF(ev.getRawX(), ev.getRawY()), + relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); // If we are entering split select, handle will no longer be visible and // should not be receiving any input. if (resultType == TO_SPLIT_LEFT_INDICATOR @@ -1004,7 +1006,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mContext, mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo, - mMoveToDesktopAnimator); + mMoveToDesktopAnimator, relevantDecor.mTaskSurface); } } if (mMoveToDesktopAnimator != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index da268988bac7..3fd3656ccbc5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -19,7 +19,11 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_ENTER; +import static android.view.MotionEvent.ACTION_HOVER_EXIT; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; +import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import android.graphics.PointF; @@ -43,7 +47,7 @@ class DragDetector { private final PointF mInputDownPoint = new PointF(); private int mTouchSlop; private boolean mIsDragEvent; - private int mDragPointerId; + private int mDragPointerId = -1; private boolean mResultOfDownAction; @@ -67,7 +71,7 @@ class DragDetector { * * @return the result returned by {@link #mEventHandler}, or the result when * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed - */ + */ boolean onMotionEvent(View v, MotionEvent ev) { final boolean isTouchScreen = (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; @@ -86,10 +90,14 @@ class DragDetector { return mResultOfDownAction; } case ACTION_MOVE: { - if (ev.findPointerIndex(mDragPointerId) == -1) { - mDragPointerId = ev.getPointerId(0); + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; } final int dragPointerIndex = ev.findPointerIndex(mDragPointerId); + if (dragPointerIndex == -1) { + throw new IllegalStateException("Failed to find primary pointer!"); + } if (!mIsDragEvent) { float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; @@ -99,22 +107,52 @@ class DragDetector { } // The event handler should only be notified about 'move' events if a drag has been // detected. - if (mIsDragEvent) { - return mEventHandler.handleMotionEvent(v, ev); - } else { + if (!mIsDragEvent) { return mResultOfDownAction; } + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_HOVER_ENTER: + case ACTION_HOVER_MOVE: + case ACTION_HOVER_EXIT: { + return mEventHandler.handleMotionEvent(v, + getSinglePointerEvent(ev, mDragPointerId)); + } + case ACTION_POINTER_UP: { + if (mDragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + if (mDragPointerId != ev.getPointerId(ev.getActionIndex())) { + // Ignore a secondary pointer being lifted. + return mResultOfDownAction; + } + // The primary pointer is being lifted. + final int dragPointerId = mDragPointerId; + mDragPointerId = -1; + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } case ACTION_UP: case ACTION_CANCEL: { + final int dragPointerId = mDragPointerId; resetState(); - return mEventHandler.handleMotionEvent(v, ev); + if (dragPointerId == -1) { + // The primary pointer was lifted, ignore the rest of the gesture. + return mResultOfDownAction; + } + return mEventHandler.handleMotionEvent(v, getSinglePointerEvent(ev, dragPointerId)); } default: - return mEventHandler.handleMotionEvent(v, ev); + // Ignore other events. + return mResultOfDownAction; } } + private static MotionEvent getSinglePointerEvent(MotionEvent ev, int pointerId) { + return ev.getPointerCount() > 1 ? ev.split(1 << pointerId) : ev; + } + void setTouchSlop(int touchSlop) { mTouchSlop = touchSlop; } @@ -129,4 +167,4 @@ class DragDetector { interface MotionEventHandler { boolean handleMotionEvent(@Nullable View v, MotionEvent ev); } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt new file mode 100644 index 000000000000..b4cadf4f300b --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/SwitchToOverviewFromDesktop.kt @@ -0,0 +1,70 @@ +/* + * 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.wm.shell.flicker.service.desktopmode.scenarios + +import android.app.Instrumentation +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.flicker.service.common.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +/** +* Base test for opening recent apps overview from desktop mode. +* +* Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation. +*/ +@Ignore("Base Test Class") +abstract class SwitchToOverviewFromDesktop +@JvmOverloads +constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + + @Rule @JvmField val testSetupRule = Utils.testSetupRule(navigationMode, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun switchToOverview() { + tapl.getLaunchedAppState().switchToOverview() + } + + @After + fun teardown() { + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 6b6954289a34..a0408652a29b 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -37,6 +37,7 @@ android_test { ], static_libs: [ + "TestParameterInjector", "WindowManager-Shell", "junit", "flag-junit", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 55b6bd278f20..bba9418db66a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -46,12 +46,14 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.window.TransitionInfo; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.window.flags.Flags; import com.android.wm.shell.transition.TransitionInfoBuilder; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -59,6 +61,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; +import java.util.Arrays; /** * Tests for {@link ActivityEmbeddingAnimationRunner}. @@ -67,7 +70,7 @@ import java.util.ArrayList; * atest WMShellUnitTests:ActivityEmbeddingAnimationRunnerTests */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnimationTestBase { @Rule @@ -204,15 +207,13 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim // TODO(b/243518738): Rewrite with TestParameter @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) @Test - public void testCalculateParentBounds_flagEnabled() { + public void testCalculateParentBounds_flagEnabled_emptyParentSize() { TransitionInfo.Change change; final TransitionInfo.Change stubChange = createChange(0 /* flags */); final Rect actualParentBounds = new Rect(); - Rect parentBounds = new Rect(0, 0, 2000, 2000); - Rect endAbsBounds = new Rect(0, 0, 2000, 2000); change = prepareChangeForParentBoundsCalculationTest( new Point(0, 0) /* endRelOffset */, - endAbsBounds, + new Rect(0, 0, 2000, 2000), new Point() /* endParentSize */ ); @@ -220,69 +221,80 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim assertTrue("Parent bounds must be empty because end parent size is not set.", actualParentBounds.isEmpty()); + } - String testString = "Parent start with (0, 0)"; - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Container not start with (0, 0)"; - parentBounds = new Rect(0, 0, 2000, 2000); - endAbsBounds = new Rect(1000, 500, 2000, 1500); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container on the right"; - parentBounds = new Rect(1000, 0, 2000, 2000); - endAbsBounds = new Rect(1000, 500, 1500, 1500); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); - - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container on the bottom"; - parentBounds = new Rect(0, 1000, 2000, 2000); - endAbsBounds = new Rect(500, 1500, 1500, 2000); - change = prepareChangeForParentBoundsCalculationTest( + @EnableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG) + @Test + public void testCalculateParentBounds_flagEnabled( + @TestParameter ParentBoundsTestParameters params) { + final TransitionInfo.Change stubChange = createChange(0 /*flags*/); + final Rect parentBounds = params.getParentBounds(); + final Rect endAbsBounds = params.getEndAbsBounds(); + final TransitionInfo.Change change = prepareChangeForParentBoundsCalculationTest( new Point(endAbsBounds.left - parentBounds.left, endAbsBounds.top - parentBounds.top), endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); + final Rect actualParentBounds = new Rect(); calculateParentBounds(change, stubChange, actualParentBounds); - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); - - testString = "Parent container in the middle"; - parentBounds = new Rect(500, 500, 1500, 1500); - endAbsBounds = new Rect(1000, 500, 1500, 1000); - change = prepareChangeForParentBoundsCalculationTest( - new Point(endAbsBounds.left - parentBounds.left, - endAbsBounds.top - parentBounds.top), - endAbsBounds, new Point(parentBounds.width(), parentBounds.height())); - - calculateParentBounds(change, stubChange, actualParentBounds); + assertEquals(parentBounds, actualParentBounds); + } - assertEquals(testString + ": Parent bounds must be " + parentBounds, parentBounds, - actualParentBounds); + private enum ParentBoundsTestParameters { + PARENT_START_WITH_0_0( + new int[]{0, 0, 2000, 2000}, + new int[]{0, 0, 2000, 2000}), + CONTAINER_NOT_START_WITH_0_0( + new int[] {0, 0, 2000, 2000}, + new int[] {1000, 500, 1500, 1500}), + PARENT_ON_THE_RIGHT( + new int[] {1000, 0, 2000, 2000}, + new int[] {1000, 500, 1500, 1500}), + PARENT_ON_THE_BOTTOM( + new int[] {0, 1000, 2000, 2000}, + new int[] {500, 1500, 1500, 2000}), + PARENT_IN_THE_MIDDLE( + new int[] {500, 500, 1500, 1500}, + new int[] {1000, 500, 1500, 1000}); + + /** + * An int array to present {left, top, right, bottom} of the parent {@link Rect bounds}. + */ + @NonNull + private final int[] mParentBounds; + + /** + * An int array to present {left, top, right, bottom} of the absolute container + * {@link Rect bounds} after the transition finishes. + */ + @NonNull + private final int[] mEndAbsBounds; + + ParentBoundsTestParameters( + @NonNull int[] parentBounds, @NonNull int[] endAbsBounds) { + mParentBounds = parentBounds; + mEndAbsBounds = endAbsBounds; + } + + @NonNull + private Rect getParentBounds() { + return asRect(mParentBounds); + } + + @NonNull + private Rect getEndAbsBounds() { + return asRect(mEndAbsBounds); + } + + @NonNull + private static Rect asRect(@NonNull int[] bounds) { + if (bounds.length != 4) { + throw new IllegalArgumentException("There must be exactly 4 elements in bounds, " + + "but found " + bounds.length + ": " + Arrays.toString(bounds)); + } + return new Rect(bounds[0], bounds[1], bounds[2], bounds[3]); + } } @Test 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 8421365e594d..37510ef4ba21 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 @@ -66,6 +66,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE import com.android.wm.shell.MockToken @@ -166,6 +167,9 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator @Mock lateinit var recentTasksController: RecentTasksController + @Mock + private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + @Mock private lateinit var mockSurface: SurfaceControl private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -248,7 +252,8 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), - recentTasksController) + recentTasksController, + mockInteractionJankMonitor) } @After @@ -2016,7 +2021,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask() setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2032,7 +2037,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2049,7 +2054,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) } @@ -2066,7 +2071,7 @@ class DesktopTasksControllerTest : ShellTestCase() { setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @@ -2086,7 +2091,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpLandscapeDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) } @@ -2102,7 +2107,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2121,7 +2126,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2141,7 +2146,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) } @@ -2161,7 +2166,7 @@ class DesktopTasksControllerTest : ShellTestCase() { screenOrientation = SCREEN_ORIENTATION_PORTRAIT) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @@ -2182,7 +2187,7 @@ class DesktopTasksControllerTest : ShellTestCase() { shouldLetterbox = true) setUpPortraitDisplay() - spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task) + spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface) val wct = getLatestDragToDesktopWct() assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) } 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 bbf523bc40d2..e4e2bd216c94 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 @@ -15,6 +15,7 @@ import android.window.TransitionInfo import android.window.TransitionInfo.FLAG_IS_WALLPAPER import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -51,6 +52,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var splitScreenController: SplitScreenController @Mock private lateinit var dragAnimator: MoveToDesktopAnimator + @Mock + private lateinit var mockInteractionJankMonitor: InteractionJankMonitor private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -63,7 +66,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { context, transitions, taskDisplayAreaOrganizer, - transactionSupplier + mockInteractionJankMonitor, + transactionSupplier, ) .apply { setSplitScreenController(splitScreenController) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 6888de5472e2..75d21457b60b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -182,7 +182,7 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registersPipTransitionCallback() { - verify(mMockPipTransitionController).registerPipTransitionCallback(any()); + verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index ace09a82d71c..66f8c0b9558d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -114,8 +114,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState, mSizeSpecSource); - final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, + final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 92762fa68550..6d18e3696f84 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -116,8 +116,8 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource); - PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, + PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt index b1d62f485a2a..dd19d76d88cf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt @@ -184,108 +184,6 @@ class DesktopModeFlagsTest : ShellTestCase() { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideOn_featureFlagOff_returnsTrueAndStoresPropertyOn() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_ON.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideUnset_featureFlagOn_returnsTrueAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_UNSET.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noSystemProperty_overrideUnset_featureFlagOff_returnsFalseAndStoresPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - setOverride(OVERRIDE_UNSET.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyNotInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc") - setOverride(OVERRIDE_OFF.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyInvalidInteger_overrideOff_featureFlagOn_returnsFalseAndStoresPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2") - setOverride(OVERRIDE_OFF.setting) - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_systemPropertyOff_overrideOn_featureFlagOn_returnsFalseAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_OFF.setting.toString()) - setOverride(OVERRIDE_ON.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_OFF.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_systemPropertyOn_overrideOff_featureFlagOff_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_ON.setting.toString()) - setOverride(OVERRIDE_OFF.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_ON.setting.toString()) - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @Suppress("ktlint:standard:max-line-length") - fun isEnabled_systemPropertyUnset_overrideOff_featureFlagOn_returnsTrueAndDoesNotUpdateProperty() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, OVERRIDE_UNSET.setting.toString()) - setOverride(OVERRIDE_OFF.setting) - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(OVERRIDE_UNSET.setting.toString()) - } - - @Test @EnableFlags( FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, @@ -445,12 +343,5 @@ class DesktopModeFlagsTest : ShellTestCase() { DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") cachedToggleOverride.isAccessible = true cachedToggleOverride.set(null, null) - - // Clear override cache stored in System property - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY) - } - - private companion object { - const val SYSTEM_PROPERTY_OVERRIDE_KEY = "sys.wmshell.desktopmode.dev_toggle_override" } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 37ef7881bde7..22b408cda0af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -51,8 +51,10 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteException; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.IRemoteTransition; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -328,6 +330,32 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest + public void testRemotePassThroughInvoked() throws RemoteException { + RemoteTransition remoteWrapper = mock(RemoteTransition.class); + IRemoteTransition remoteTransition = mock(IRemoteTransition.class); + IBinder remoteBinder = mock(IBinder.class); + doReturn(remoteBinder).when(remoteTransition).asBinder(); + doReturn(remoteTransition).when(remoteWrapper).getRemoteTransition(); + + TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_CHANGE, null, + remoteWrapper); + IBinder transition = mock(IBinder.class); + mMainStage.activate(new WindowContainerTransaction(), false); + mStageCoordinator.handleRequest(transition, request); + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .build(); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + + verify(remoteTransition, times(1)).startAnimation(any(), + any(), any(), any()); + } + + @Test + @UiThreadTest public void testEnterRecentsAndRestore() { enterSplit(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt index 3fbab0f9e2bb..56224b4b4025 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -85,6 +85,23 @@ class DragDetectorTest { } @Test + fun testNoMove_mouse_passesDownAndUp() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_UP, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testMoveInSlop_touch_passesDownAndUp() { `when`(eventHandler.handleMotionEvent(any(), argThat { return@argThat it.action == MotionEvent.ACTION_DOWN @@ -166,6 +183,52 @@ class DragDetectorTest { } @Test + fun testDownMoveDown_shouldIgnoreTheSecondDownMotion() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testDownMouseMoveDownTouch_shouldIgnoreTheTouchDownMotion() { + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(any(), argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test fun testPassesHoverEnter() { `when`(eventHandler.handleMotionEvent(any(), argThat { it.action == MotionEvent.ACTION_HOVER_ENTER diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index ad963dd913cf..93118aeafaaf 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -40,6 +40,7 @@ namespace android { namespace uirenderer { +std::mutex TestUtils::sMutex; std::unordered_map<int, TestUtils::CallCounts> TestUtils::sMockFunctorCounts{}; SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 0ede902b1b95..8ab2b16601c3 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -305,22 +305,26 @@ public: .onSync = [](int functor, void* client_data, const WebViewSyncData& data) { expectOnRenderThread("onSync"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].sync++; }, .onContextDestroyed = [](int functor, void* client_data) { expectOnRenderThread("onContextDestroyed"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].contextDestroyed++; }, .onDestroyed = [](int functor, void* client_data) { expectOnRenderThread("onDestroyed"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].destroyed++; }, .removeOverlays = [](int functor, void* data, void (*mergeTransaction)(ASurfaceTransaction*)) { expectOnRenderThread("removeOverlays"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].removeOverlays++; }, }; @@ -329,6 +333,7 @@ public: callbacks.gles.draw = [](int functor, void* client_data, const DrawGlInfo& params, const WebViewOverlayData& overlay_params) { expectOnRenderThread("draw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].glesDraw++; }; break; @@ -336,15 +341,18 @@ public: callbacks.vk.initialize = [](int functor, void* data, const VkFunctorInitParams& params) { expectOnRenderThread("initialize"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkInitialize++; }; callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params, const WebViewOverlayData& overlayParams) { expectOnRenderThread("draw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkDraw++; }; callbacks.vk.postDraw = [](int functor, void* data) { expectOnRenderThread("postDraw"); + std::scoped_lock lock(sMutex); sMockFunctorCounts[functor].vkPostDraw++; }; break; @@ -352,11 +360,16 @@ public: return callbacks; } - static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; } + static CallCounts copyCountsForFunctor(int functor) { + std::scoped_lock lock(sMutex); + return sMockFunctorCounts[functor]; + } static SkFont defaultFont(); private: + // guards sMockFunctorCounts + static std::mutex sMutex; static std::unordered_map<int, CallCounts> sMockFunctorCounts; static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index e727ea899098..690a60a470bc 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -239,19 +239,21 @@ TEST(RenderNode, releasedCallback) { TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(0, counts.destroyed); TestUtils::recordNode(*node, [&](Canvas& canvas) { canvas.drawWebViewFunctor(functor); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(0, counts.destroyed); TestUtils::runOnRenderThreadUnmanaged([&] (RenderThread&) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(0, counts.destroyed); @@ -265,6 +267,7 @@ TEST(RenderNode, releasedCallback) { }); // Fence on any remaining post'd work TestUtils::runOnRenderThreadUnmanaged([] (RenderThread&) {}); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(1, counts.destroyed); } diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 064d42ec8941..26b47290d149 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -101,7 +101,7 @@ TEST(SkiaDisplayList, syncContexts) { SkCanvas dummyCanvas; int functor1 = TestUtils::createMockFunctor(); - auto& counts = TestUtils::countsForFunctor(functor1); + auto counts = TestUtils::copyCountsForFunctor(functor1); skiaDL.mChildFunctors.push_back( skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas)); WebViewFunctor_release(functor1); @@ -118,6 +118,7 @@ TEST(SkiaDisplayList, syncContexts) { }); }); + counts = TestUtils::copyCountsForFunctor(functor1); EXPECT_EQ(counts.sync, 1); EXPECT_EQ(counts.destroyed, 0); EXPECT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); @@ -126,6 +127,7 @@ TEST(SkiaDisplayList, syncContexts) { TestUtils::runOnRenderThread([](auto&) { // Fence }); + counts = TestUtils::copyCountsForFunctor(functor1); EXPECT_EQ(counts.destroyed, 1); } diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp index 5e8f13d261c7..09ce98a2e53d 100644 --- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp +++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp @@ -40,7 +40,7 @@ TEST(WebViewFunctor, createDestroyGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // Empty, don't care }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); // We never initialized, so contextDestroyed == 0 EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(1, counts.destroyed); @@ -59,7 +59,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); - auto& counts = TestUtils::countsForFunctor(functor); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(0, counts.sync); EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(0, counts.destroyed); @@ -69,6 +69,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { handle->sync(syncData); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); TestUtils::runOnRenderThreadUnmanaged([&](auto&) { @@ -76,6 +77,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { handle->sync(syncData); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); handle.clear(); @@ -84,6 +86,7 @@ TEST(WebViewFunctor, createSyncHandleGLES) { // fence }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(0, counts.contextDestroyed); EXPECT_EQ(1, counts.destroyed); @@ -98,7 +101,6 @@ TEST(WebViewFunctor, createSyncDrawGLES) { auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); WebViewFunctor_release(functor); - auto& counts = TestUtils::countsForFunctor(functor); for (int i = 0; i < 5; i++) { TestUtils::runOnRenderThreadUnmanaged([&](auto&) { WebViewSyncData syncData; @@ -112,6 +114,7 @@ TEST(WebViewFunctor, createSyncDrawGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(5, counts.sync); EXPECT_EQ(10, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -127,13 +130,13 @@ TEST(WebViewFunctor, contextDestroyedGLES) { auto handle = WebViewFunctorManager::instance().handleFor(functor); ASSERT_TRUE(handle); WebViewFunctor_release(functor); - auto& counts = TestUtils::countsForFunctor(functor); TestUtils::runOnRenderThreadUnmanaged([&](auto&) { WebViewSyncData syncData; handle->sync(syncData); DrawGlInfo drawInfo; handle->drawGl(drawInfo); }); + auto counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(1, counts.glesDraw); EXPECT_EQ(0, counts.contextDestroyed); @@ -141,6 +144,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { TestUtils::runOnRenderThreadUnmanaged([](auto& rt) { rt.destroyRenderingContext(); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(1, counts.sync); EXPECT_EQ(1, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -151,6 +155,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { DrawGlInfo drawInfo; handle->drawGl(drawInfo); }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(2, counts.glesDraw); EXPECT_EQ(1, counts.contextDestroyed); @@ -159,6 +164,7 @@ TEST(WebViewFunctor, contextDestroyedGLES) { TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) { // fence }); + counts = TestUtils::copyCountsForFunctor(functor); EXPECT_EQ(2, counts.sync); EXPECT_EQ(2, counts.glesDraw); EXPECT_EQ(2, counts.contextDestroyed); diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index acfe473c260c..0edaaefbd75d 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -2,6 +2,20 @@ package: "android.location.flags" container: "system" flag { + name: "keep_gnss_stationary_throttling" + namespace: "location" + description: "Keeps stationary throttling for the GNSS provider even if the disable_stationary_throttling flag is true." + bug: "354000147" +} + +flag { + name: "disable_stationary_throttling" + namespace: "location" + description: "Disables stationary throttling for all providers" + bug: "354000147" +} + +flag { name: "new_geocoder" namespace: "location" description: "Flag for new Geocoder APIs" diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index d6345cec7c30..f36344aa4846 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-beta02" + extra["jetpackComposeVersion"] = "1.7.0-beta05" } subprojects { diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index a842009a09bb..1cca73af9a42 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.5.0" +agp = "8.5.1" compose-compiler = "1.5.11" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip Binary files differindex 77e6ad3f2117..9a97e4674448 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.8-bin.zip +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.9-bin.zip diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar Binary files differindex e6441136f3d4..2c3521197d7c 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index 91d2a3ab217d..9f29c77d55f6 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,6 +16,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=gradle-8.8-bin.zip +distributionUrl=gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew index b740cf13397a..f5feea6d6b11 100755 --- a/packages/SettingsLib/Spa/gradlew +++ b/packages/SettingsLib/Spa/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp index 6df0e9956f27..ac44a1be4cff 100644 --- a/packages/SettingsLib/Spa/spa/Android.bp +++ b/packages/SettingsLib/Spa/spa/Android.bp @@ -25,9 +25,6 @@ android_library { use_resource_processor: true, static_libs: [ "SettingsLibColor", - "androidx.slice_slice-builders", - "androidx.slice_slice-core", - "androidx.slice_slice-view", "androidx.compose.animation_animation", "androidx.compose.material3_material3", "androidx.compose.material_material-icons-extended", diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 9b8ecf773661..ce3d96e2d388 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -53,17 +53,14 @@ android { dependencies { api(project(":SettingsLibColor")) - api("androidx.appcompat:appcompat:1.7.0-rc01") - api("androidx.slice:slice-builders:1.1.0-alpha02") - api("androidx.slice:slice-core:1.1.0-alpha02") - api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.3.0-beta02") + api("androidx.appcompat:appcompat:1.7.0") + api("androidx.compose.material3:material3:1.3.0-beta04") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-beta02") + api("androidx.navigation:navigation-compose:2.8.0-beta05") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") @@ -97,10 +94,6 @@ tasks.register<JacocoReport>("coverageReport") { // Excludes debug functions "com/android/settingslib/spa/framework/compose/TimeMeasurer*", - - // Excludes slice demo presenter & provider - "com/android/settingslib/spa/slice/presenter/Demo*", - "com/android/settingslib/spa/slice/provider/Demo*", ) ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt index 085c3c6e026b..ddb571d1392f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryBuilder.kt @@ -55,7 +55,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings toPage = toPage, // attributes - // TODO: set isEnabled & (isAllowSearch, hasSliceSupport) separately isAllowSearch = isEnabled && isAllowSearch, isSearchDataDynamic = isSearchDataDynamic, hasMutableStatus = hasMutableStatus, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt index 95c7d2371b3d..cc5351a3b81d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt @@ -57,8 +57,8 @@ interface SettingsPageProvider { /** * The API to indicate whether the page is enabled or not. * During SPA page migration, one can use it to enable certain pages in one release. - * When the page is disabled, all its related functionalities, such as browsing, search, - * slice provider, are disabled as well. + * When the page is disabled, all its related functionalities, such as browsing and search, + * are disabled as well. */ fun isEnabled(arguments: Bundle?): Boolean = true diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt index d8c35a36d061..9e8ca0cd71a4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt @@ -25,7 +25,6 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory const val SESSION_UNKNOWN = "unknown" const val SESSION_BROWSE = "browse" const val SESSION_SEARCH = "search" -const val SESSION_SLICE = "slice" const val SESSION_EXTERNAL = "external" const val KEY_DESTINATION = "spaActivityDestination" diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index cfd74d4b9f42..ce997bf15b91 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1010,10 +1010,8 @@ <!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] --> <string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string> - <!-- UI debug setting: enable legacy freeform window support [CHAR LIMIT=50] --> - <string name="enable_freeform_support">Enable freeform windows (legacy)</string> - <!-- UI debug setting: enable legacy freeform window support summary [CHAR LIMIT=150] --> - <string name="enable_freeform_support_summary">Enable support for experimental legacy freeform windows.</string> + <!-- Title for a toggle that enables support for windows to be in freeform (apps run in resizable windows). [CHAR LIMIT=50] --> + <string name="enable_freeform_support">Enable freeform window support</string> <!-- Local (desktop) backup password menu title [CHAR LIMIT=25] --> <string name="local_backup_password_title">Desktop backup password</string> diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java index 1e75014d2017..3906749ae494 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java @@ -24,9 +24,9 @@ import android.os.UserHandle; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; -import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; @@ -109,7 +109,7 @@ public class InputMethodPreferenceTest { final boolean systemIme, final String name) { return new InputMethodPreference( - InstrumentationRegistry.getTargetContext(), + InstrumentationRegistry.getInstrumentation().getTargetContext(), createInputMethodInfo(systemIme, name), title, true /* isAllowedByOrganization */, @@ -119,7 +119,8 @@ public class InputMethodPreferenceTest { private static InputMethodInfo createInputMethodInfo( final boolean systemIme, final String name) { - final Context targetContext = InstrumentationRegistry.getTargetContext(); + final Context targetContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); final Locale systemLocale = targetContext .getResources() .getConfiguration() diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java index f1c0beae0dc0..2c3478d6e235 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java @@ -18,9 +18,9 @@ package com.android.settingslib.inputmethod; import android.text.TextUtils; -import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; @@ -104,7 +104,7 @@ public class InputMethodSubtypePreferenceTest { final Locale subtypeLocale = TextUtils.isEmpty(subtypeLanguageTag) ? null : Locale.forLanguageTag(subtypeLanguageTag); return new InputMethodSubtypePreference( - InstrumentationRegistry.getTargetContext(), + InstrumentationRegistry.getInstrumentation().getTargetContext(), key, subtypeName, subtypeLocale, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 5f236516785d..2b8b23e3dba8 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -282,5 +282,6 @@ public class SecureSettings { Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, Settings.Secure.MANDATORY_BIOMETRICS, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index c8da8afc25c3..cc5302bdd99a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -441,5 +441,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_MOUSE_KEYS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.MANDATORY_BIOMETRICS, new InclusiveIntegerRangeValidator(0, 1)); + VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + new InclusiveIntegerRangeValidator(0, 1)); } } diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig index afcd8a9624c8..f2b5efa6a571 100644 --- a/packages/SystemUI/aconfig/communal.aconfig +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -8,12 +8,3 @@ flag { bug: "304584416" } -flag { - name: "enable_widget_picker_size_filter" - namespace: "communal" - description: "Enables passing a size filter to the widget picker" - bug: "345482907" - metadata { - purpose: PURPOSE_BUGFIX - } -} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7032c73f798f..02e042327d66 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -988,6 +988,16 @@ flag { } flag { + name: "communal_scene_ktf_refactor" + namespace: "systemui" + description: "refactors the syncing mechanism between communal STL and KTF state." + bug: "327225415" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "app_clips_backlinks" namespace: "systemui" description: "Enables Backlinks improvement feature in App Clips" @@ -1224,3 +1234,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "lockscreen_preview_renderer_create_on_main_thread" + namespace: "systemui" + description: "Force preview renderer to be created on the main thread" + bug: "343732179" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 1c02d3f7662b..68e968fdfb4e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1004,6 +1004,7 @@ private fun WidgetContent( } .thenIf(viewModel.isEditMode) { Modifier.semantics { + onClick(clickActionLabel, null) contentDescription = accessibilityLabel val deleteAction = CustomAccessibilityAction(removeWidgetActionLabel) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt index 620892adc286..b4c1a2e85daf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp @@ -102,8 +101,6 @@ constructor( val interactionSource = remember { MutableInteractionSource() } val focusRequester = remember { FocusRequester() } - val context = LocalContext.current - LaunchedEffect(Unit) { // Adding a delay to ensure the animation completes before requesting focus delay(250) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index ea740a8d8de4..82c85d1fa38b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.launch * the currently running transition, if there is one. */ internal fun CoroutineScope.animateToScene( - layoutState: BaseSceneTransitionLayoutState, + layoutState: MutableSceneTransitionLayoutStateImpl, target: SceneKey, transitionKey: TransitionKey?, ): TransitionState.Transition? { @@ -154,7 +154,7 @@ internal fun CoroutineScope.animateToScene( } private fun CoroutineScope.animate( - layoutState: BaseSceneTransitionLayoutState, + layoutState: MutableSceneTransitionLayoutStateImpl, targetScene: SceneKey, transitionKey: TransitionKey?, isInitiatedByUserInput: Boolean, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 78ba7defe77c..5b328b8cdd9c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -52,11 +52,19 @@ internal interface DraggableHandler { * and [onStop] methods. */ internal interface DragController { - /** Drag the current scene by [delta] pixels. */ - fun onDrag(delta: Float) + /** + * Drag the current scene by [delta] pixels. + * + * @return the consumed [delta] + */ + fun onDrag(delta: Float): Float - /** Starts a transition to a target scene. */ - fun onStop(velocity: Float, canChangeScene: Boolean) + /** + * Starts a transition to a target scene. + * + * @return the consumed [velocity] + */ + fun onStop(velocity: Float, canChangeScene: Boolean): Float } internal class DraggableHandlerImpl( @@ -272,8 +280,10 @@ private class DragControllerImpl( * * @return the consumed delta */ - override fun onDrag(delta: Float) { - if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return + override fun onDrag(delta: Float): Float { + if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) { + return 0f + } swipeTransition.dragOffset += delta val (fromScene, acceleratedOffset) = @@ -289,7 +299,7 @@ private class DragControllerImpl( if (result == null) { onStop(velocity = delta, canChangeScene = true) - return + return 0f } if ( @@ -314,6 +324,8 @@ private class DragControllerImpl( updateTransition(swipeTransition) } + + return delta } /** @@ -351,10 +363,10 @@ private class DragControllerImpl( } } - override fun onStop(velocity: Float, canChangeScene: Boolean) { + override fun onStop(velocity: Float, canChangeScene: Boolean): Float { // The state was changed since the drag started; don't do anything. if (!isDrivingTransition || swipeTransition.isFinishing) { - return + return 0f } // Important: Make sure that all the code here references the current transition when @@ -370,9 +382,6 @@ private class DragControllerImpl( // immediately go back B => A. if (targetScene != swipeTransition._currentScene) { swipeTransition._currentScene = targetScene - with(draggableHandler.layoutImpl.state) { - draggableHandler.coroutineScope.onChangeScene(targetScene.key) - } } swipeTransition.animateOffset( @@ -443,7 +452,7 @@ private class DragControllerImpl( if (result == null) { // We will not animate swipeTransition.snapToScene(fromScene.key) - return + return 0f } val newSwipeTransition = @@ -465,6 +474,9 @@ private class DragControllerImpl( animateTo(targetScene = fromScene, targetOffset = 0f) } } + + // The onStop animation consumes any remaining velocity. + return velocity } /** @@ -512,7 +524,7 @@ private class DragControllerImpl( } private fun SwipeTransition( - layoutState: BaseSceneTransitionLayoutState, + layoutState: MutableSceneTransitionLayoutStateImpl, coroutineScope: CoroutineScope, fromScene: Scene, result: UserActionResult, @@ -567,7 +579,7 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { private class SwipeTransition( val layoutImpl: SceneTransitionLayoutImpl, - val layoutState: BaseSceneTransitionLayoutState, + val layoutState: MutableSceneTransitionLayoutStateImpl, val coroutineScope: CoroutineScope, override val key: TransitionKey?, val _fromScene: Scene, @@ -1084,17 +1096,13 @@ internal class NestedScrollHandlerImpl( // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) - - offsetAvailable }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") - controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) - - dragController = null - // The onDragStopped animation consumes any remaining velocity. - velocityAvailable + controller + .onStop(velocity = velocityAvailable, canChangeScene = canChangeScene) + .also { dragController = null } }, ) } @@ -1109,7 +1117,7 @@ internal class NestedScrollHandlerImpl( internal const val OffsetVisibilityThreshold = 0.5f private object NoOpDragController : DragController { - override fun onDrag(delta: Float) {} + override fun onDrag(delta: Float) = 0f - override fun onStop(velocity: Float, canChangeScene: Boolean) {} + override fun onStop(velocity: Float, canChangeScene: Boolean) = 0f } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt index 734241e2faf6..d3e2a1cfbb3f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt @@ -35,7 +35,7 @@ import kotlinx.coroutines.launch @Composable internal fun PredictiveBackHandler( - state: BaseSceneTransitionLayoutState, + state: MutableSceneTransitionLayoutStateImpl, coroutineScope: CoroutineScope, targetSceneForBack: SceneKey? = null, ) { @@ -65,7 +65,7 @@ internal fun PredictiveBackHandler( } private class PredictiveBackTransition( - val state: BaseSceneTransitionLayoutState, + val state: MutableSceneTransitionLayoutStateImpl, val coroutineScope: CoroutineScope, fromScene: SceneKey, toScene: SceneKey, 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 82275a9ac0a6..2fc4526b31f2 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 @@ -514,7 +514,7 @@ internal fun SceneTransitionLayoutForTesting( val coroutineScope = rememberCoroutineScope() val layoutImpl = remember { SceneTransitionLayoutImpl( - state = state as BaseSceneTransitionLayoutState, + state = state as MutableSceneTransitionLayoutStateImpl, density = density, layoutDirection = layoutDirection, swipeSourceDetector = swipeSourceDetector, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 3e48c429ba7d..32db0b7cd9fe 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -44,7 +44,7 @@ internal typealias MovableElementContent = @Composable (@Composable () -> Unit) @Stable internal class SceneTransitionLayoutImpl( - internal val state: BaseSceneTransitionLayoutState, + internal val state: MutableSceneTransitionLayoutStateImpl, internal var density: Density, internal var layoutDirection: LayoutDirection, internal var swipeSourceDetector: SwipeSourceDetector, 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 56c8752eb53c..48bc251f1f87 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 @@ -200,7 +200,7 @@ sealed interface TransitionState { * transition. * * Important: These will be set exactly once, when this transition is - * [started][BaseSceneTransitionLayoutState.startTransition]. + * [started][MutableSceneTransitionLayoutStateImpl.startTransition]. */ internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty private var fromOverscrollSpec: OverscrollSpecImpl? = null @@ -332,13 +332,16 @@ sealed interface TransitionState { } } -internal abstract class BaseSceneTransitionLayoutState( +/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */ +internal class MutableSceneTransitionLayoutStateImpl( initialScene: SceneKey, - protected var stateLinks: List<StateLink>, + override var transitions: SceneTransitions = transitions {}, + internal val canChangeScene: (SceneKey) -> Boolean = { true }, + private val stateLinks: List<StateLink> = emptyList(), // TODO(b/290930950): Remove this flag. - internal var enableInterruptions: Boolean, -) : SceneTransitionLayoutState { + internal val enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, +) : MutableSceneTransitionLayoutState { private val creationThread: Thread = Thread.currentThread() /** @@ -374,17 +377,6 @@ internal abstract class BaseSceneTransitionLayoutState( @VisibleForTesting internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>() - /** Whether we can transition to the given [scene]. */ - internal abstract fun canChangeScene(scene: SceneKey): Boolean - - /** - * Called when the [current scene][TransitionState.currentScene] should be changed to [scene]. - * - * When this is called, the source of truth for the current scene should be changed so that - * [transitionState] will animate and settle to [scene]. - */ - internal abstract fun CoroutineScope.onChangeScene(scene: SceneKey) - internal fun checkThread() { val current = Thread.currentThread() if (current !== creationThread) { @@ -409,6 +401,20 @@ internal abstract class BaseSceneTransitionLayoutState( return transition.isTransitioningBetween(scene, other) } + override fun setTargetScene( + targetScene: SceneKey, + coroutineScope: CoroutineScope, + transitionKey: TransitionKey?, + ): TransitionState.Transition? { + checkThread() + + return coroutineScope.animateToScene( + layoutState = this@MutableSceneTransitionLayoutStateImpl, + target = targetScene, + transitionKey = transitionKey, + ) + } + /** * Start a new [transition]. * @@ -600,7 +606,7 @@ internal abstract class BaseSceneTransitionLayoutState( } } - fun snapToScene(scene: SceneKey) { + override fun snapToScene(scene: SceneKey) { checkThread() // Force finish all transitions. @@ -674,37 +680,6 @@ internal abstract class BaseSceneTransitionLayoutState( } } -/** A [MutableSceneTransitionLayoutState] that holds the value for the current scene. */ -internal class MutableSceneTransitionLayoutStateImpl( - initialScene: SceneKey, - override var transitions: SceneTransitions = transitions {}, - private val canChangeScene: (SceneKey) -> Boolean = { true }, - stateLinks: List<StateLink> = emptyList(), - enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, -) : - MutableSceneTransitionLayoutState, - BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) { - override fun setTargetScene( - targetScene: SceneKey, - coroutineScope: CoroutineScope, - transitionKey: TransitionKey?, - ): TransitionState.Transition? { - checkThread() - - return coroutineScope.animateToScene( - layoutState = this@MutableSceneTransitionLayoutStateImpl, - target = targetScene, - transitionKey = transitionKey, - ) - } - - override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene) - - override fun CoroutineScope.onChangeScene(scene: SceneKey) { - setTargetScene(scene, coroutineScope = this) - } -} - private const val TAG = "SceneTransitionLayoutState" /** Whether support for interruptions in enabled by default. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt index 6c299463f978..2018d6e37b57 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt @@ -16,7 +16,7 @@ package com.android.compose.animation.scene.transition.link -import com.android.compose.animation.scene.BaseSceneTransitionLayoutState +import com.android.compose.animation.scene.MutableSceneTransitionLayoutStateImpl import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayoutState import com.android.compose.animation.scene.TransitionKey @@ -25,7 +25,7 @@ import com.android.compose.animation.scene.TransitionState /** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */ class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) { - internal val target = target as BaseSceneTransitionLayoutState + internal val target = target as MutableSceneTransitionLayoutStateImpl /** * Links two transitions (source and target) together. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 7a5a84e2c3f1..c8bbb149a042 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -212,7 +212,8 @@ class DraggableHandlerTest { draggableHandler: DraggableHandler, startedPosition: Offset = Offset.Zero, overSlop: Float = 0f, - pointersDown: Int = 1 + pointersDown: Int = 1, + expectedConsumed: Boolean = true, ): DragController { val dragController = draggableHandler.onDragStarted( @@ -222,17 +223,23 @@ class DraggableHandlerTest { ) // MultiPointerDraggable will always call onDelta with the initial overSlop right after - dragController.onDragDelta(pixels = overSlop) + dragController.onDragDelta(pixels = overSlop, expectedConsumed = expectedConsumed) return dragController } - fun DragController.onDragDelta(pixels: Float) { - onDrag(delta = pixels) + fun DragController.onDragDelta(pixels: Float, expectedConsumed: Boolean = true) { + val consumed = onDrag(delta = pixels) + assertThat(consumed).isEqualTo(if (expectedConsumed) pixels else 0f) } - fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) { - onStop(velocity, canChangeScene) + fun DragController.onDragStopped( + velocity: Float, + canChangeScene: Boolean = true, + expectedConsumed: Boolean = true + ) { + val consumed = onStop(velocity, canChangeScene) + assertThat(consumed).isEqualTo(if (expectedConsumed) velocity else 0f) } fun NestedScrollConnection.scroll( @@ -360,10 +367,18 @@ class DraggableHandlerTest { @Test fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest { - onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f)) + onDragStarted( + horizontalDraggableHandler, + overSlop = up(fractionOfScreen = 0.3f), + expectedConsumed = false, + ) assertIdle(currentScene = SceneA) - onDragStarted(horizontalDraggableHandler, overSlop = down(fractionOfScreen = 0.3f)) + onDragStarted( + horizontalDraggableHandler, + overSlop = down(fractionOfScreen = 0.3f), + expectedConsumed = false, + ) assertIdle(currentScene = SceneA) } @@ -489,19 +504,19 @@ class DraggableHandlerTest { // start accelaratedScroll and scroll over to B -> null val dragController2 = onDragStartedImmediately() - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may // still be called. Make sure that they don't crash or change the scene - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) advanceUntilIdle() assertIdle(SceneB) // These events can still come in after the animation has settled - dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f)) + dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f), expectedConsumed = false) dragController2.onDragStopped(velocity = 0f) assertIdle(SceneB) } @@ -845,7 +860,7 @@ class DraggableHandlerTest { assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! - dragController.onDragStopped(-velocityThreshold) + dragController.onDragStopped(-velocityThreshold, expectedConsumed = false) assertTransition(currentScene = SceneA) nestedScroll.scroll(available = -offsetY10) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index ecafb170535d..b98400a70ea4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -49,6 +49,21 @@ import org.junit.runner.RunWith class MultiPointerDraggableTest { @get:Rule val rule = createComposeRule() + private class SimpleDragController( + val onDrag: () -> Unit, + val onStop: () -> Unit, + ) : DragController { + override fun onDrag(delta: Float): Float { + onDrag() + return delta + } + + override fun onStop(velocity: Float, canChangeScene: Boolean): Float { + onStop() + return velocity + } + } + @Test fun cancellingPointerCallsOnDragStopped() { val size = 200f @@ -70,15 +85,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) @@ -142,15 +152,10 @@ class MultiPointerDraggableTest { startDragImmediately = { true }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) .pointerInput(Unit) { @@ -218,15 +223,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) { @@ -341,15 +341,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) { - dragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - stopped = true - } - } + SimpleDragController( + onDrag = { dragged = true }, + onStop = { stopped = true }, + ) }, ) ) { @@ -447,15 +442,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> verticalStarted = true - object : DragController { - override fun onDrag(delta: Float) { - verticalDragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - verticalStopped = true - } - } + SimpleDragController( + onDrag = { verticalDragged = true }, + onStop = { verticalStopped = true }, + ) }, ) .multiPointerDraggable( @@ -464,15 +454,10 @@ class MultiPointerDraggableTest { startDragImmediately = { false }, onDragStarted = { _, _, _ -> horizontalStarted = true - object : DragController { - override fun onDrag(delta: Float) { - horizontalDragged = true - } - - override fun onStop(velocity: Float, canChangeScene: Boolean) { - horizontalStopped = true - } - } + SimpleDragController( + onDrag = { horizontalDragged = true }, + onStop = { horizontalStopped = true }, + ) }, ) ) @@ -567,11 +552,10 @@ class MultiPointerDraggableTest { }, onDragStarted = { _, _, _ -> started = true - object : DragController { - override fun onDrag(delta: Float) {} - - override fun onStop(velocity: Float, canChangeScene: Boolean) {} - } + SimpleDragController( + onDrag = { /* do nothing */ }, + onStop = { /* do nothing */ }, + ) }, ) ) {} 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 41bf630e9ae4..52cceecadbf9 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 @@ -153,7 +153,7 @@ class SceneTransitionLayoutStateTest { sourceTo: SceneKey? = SceneB, targetFrom: SceneKey? = SceneC, targetTo: SceneKey = SceneD - ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> { + ): Pair<MutableSceneTransitionLayoutStateImpl, MutableSceneTransitionLayoutStateImpl> { val parentState = MutableSceneTransitionLayoutState(parentInitialScene) val link = listOf( @@ -164,8 +164,8 @@ class SceneTransitionLayoutStateTest { ) val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link) return Pair( - parentState as BaseSceneTransitionLayoutState, - childState as BaseSceneTransitionLayoutState + parentState as MutableSceneTransitionLayoutStateImpl, + childState as MutableSceneTransitionLayoutStateImpl ) } @@ -187,7 +187,7 @@ class SceneTransitionLayoutStateTest { @Test fun linkedTransition_transitiveLink() { val parentParentState = - MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState + MutableSceneTransitionLayoutState(SceneB) as MutableSceneTransitionLayoutStateImpl val parentLink = listOf( StateLink( @@ -197,7 +197,7 @@ class SceneTransitionLayoutStateTest { ) val parentState = MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink) - as BaseSceneTransitionLayoutState + as MutableSceneTransitionLayoutStateImpl val link = listOf( StateLink( @@ -207,7 +207,7 @@ class SceneTransitionLayoutStateTest { ) val childState = MutableSceneTransitionLayoutState(SceneA, stateLinks = link) - as BaseSceneTransitionLayoutState + as MutableSceneTransitionLayoutStateImpl val childTransition = transition(SceneA, SceneB) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt new file mode 100644 index 000000000000..f7f70c154ce6 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt @@ -0,0 +1,685 @@ +/* + * 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.communal.domain.interactor + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.ObservableTransitionState.Idle +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +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) +@EnableFlags(FLAG_COMMUNAL_HUB, FLAG_COMMUNAL_SCENE_KTF_REFACTOR) +@DisableSceneContainer +class CommunalSceneTransitionInteractorTest : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository } + private val testScope = kosmos.testScope + + private val underTest by lazy { kosmos.communalSceneTransitionInteractor } + private val keyguardTransitionRepository by lazy { kosmos.realKeyguardTransitionRepository } + + private val ownerName = CommunalSceneTransitionInteractor::class.java.simpleName + private val progress = MutableSharedFlow<Float>() + + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>(Idle(CommunalScenes.Blank)) + + private val blankToHub = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Blank), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + private val hubToBlank = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Communal), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + @Before + fun setup() { + kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) + underTest.start() + kosmos.communalSceneRepository.setTransitionState(sceneTransitions) + testScope.launch { keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN) } + } + + /** Transition from blank to glanceable hub. This is the default case. */ + @Test + fun transition_from_blank_end_in_hub() = + testScope.runTest { + sceneTransitions.value = blankToHub + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + progress.emit(1f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 1f, + ownerName = ownerName, + ) + ) + + sceneTransitions.value = Idle(CommunalScenes.Communal) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = FINISHED, + value = 1f, + ownerName = ownerName, + ) + ) + } + + /** Transition from hub to lockscreen. */ + @Test + fun transition_from_hub_end_in_lockscreen() = + testScope.runTest { + sceneTransitions.value = hubToBlank + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + sceneTransitions.value = Idle(CommunalScenes.Blank) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = FINISHED, + value = 1f, + ownerName = ownerName, + ) + ) + } + + /** Transition from hub to dream. */ + @Test + fun transition_from_hub_end_in_dream() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setDreaming(true) + runCurrent() + + sceneTransitions.value = hubToBlank + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = DREAMING, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = DREAMING, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + sceneTransitions.value = Idle(CommunalScenes.Blank) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = DREAMING, + transitionState = FINISHED, + value = 1f, + ownerName = ownerName, + ) + ) + } + + /** Transition from blank to hub, then settle back in blank. */ + @Test + fun transition_from_blank_end_in_blank() = + testScope.runTest { + sceneTransitions.value = blankToHub + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + val allSteps by collectValues(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + val numToDrop = allSteps.size + // Settle back in blank + sceneTransitions.value = Idle(CommunalScenes.Blank) + + // Assert that KTF reversed transition back to lockscreen. + assertThat(allSteps.drop(numToDrop)) + .containsExactly( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = CANCELED, + value = 0.4f, + ownerName = ownerName, + ), + // Transition back to lockscreen + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = STARTED, + value = 0.6f, + ownerName = ownerName, + ), + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = FINISHED, + value = 1f, + ownerName = ownerName, + ), + ) + .inOrder() + } + + @Test + fun transition_to_occluded_with_changed_scene_respected_just_once() = + testScope.runTest { + underTest.onSceneAboutToChange(CommunalScenes.Blank, OCCLUDED) + runCurrent() + sceneTransitions.value = hubToBlank + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + sceneTransitions.value = blankToHub + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = OCCLUDED, + to = GLANCEABLE_HUB, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + sceneTransitions.value = hubToBlank + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + } + + @Test + fun transition_from_blank_interrupted() = + testScope.runTest { + sceneTransitions.value = blankToHub + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + val allSteps by collectValues(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + val numToDrop = allSteps.size + // Transition back from hub to blank, interrupting + // the current transition. + sceneTransitions.value = hubToBlank + + assertThat(allSteps.drop(numToDrop)) + .containsExactly( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + value = 1f, + transitionState = FINISHED, + ownerName = ownerName, + ), + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + value = 0f, + transitionState = STARTED, + ownerName = ownerName, + ), + ) + .inOrder() + + progress.emit(0.1f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = RUNNING, + value = 0.1f, + ownerName = ownerName, + ) + ) + } + + /** + * Blank -> Hub transition interrupted by a new Blank -> Hub transition. KTF state should not be + * updated in this case. + */ + @Test + fun transition_to_hub_duplicate_does_not_change_ktf() = + testScope.runTest { + sceneTransitions.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Blank), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + val allSteps by collectValues(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + val sizeBefore = allSteps.size + val newProgress = MutableSharedFlow<Float>() + sceneTransitions.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Blank), + progress = newProgress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + ) + + // No new KTF steps emitted as a result of the new transition. + assertThat(allSteps).hasSize(sizeBefore) + + // Progress is now tracked by the new flow. + newProgress.emit(0.1f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = GLANCEABLE_HUB, + transitionState = RUNNING, + value = 0.1f, + ownerName = ownerName, + ) + ) + } + + /** + * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL still finishes in Blank. + * After a KTF transition is started (GLANCEABLE_HUB -> LOCKSCREEN) KTF immediately considers + * the active scene to be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active + * and may start a new transition LOCKSCREEN -> *. Here we test LOCKSCREEN -> OCCLUDED. + * + * KTF is allowed to already start and play the other transition, while the STL transition may + * finish later (gesture completes much later). When we eventually settle the STL transition in + * Blank we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for + * this scenario the settle can be ignored. + */ + @Test + fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_blank() = + testScope.runTest { + sceneTransitions.value = hubToBlank + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + // Start another transition externally while our scene + // transition is happening. + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = "external", + from = LOCKSCREEN, + to = OCCLUDED, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = "external", + ) + ) + + // Scene progress should not affect KTF transition anymore + progress.emit(0.7f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = "external", + ) + ) + + // Scene transition still finishes but should not impact KTF transition + sceneTransitions.value = Idle(CommunalScenes.Blank) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = "external", + ) + ) + } + + /** + * STL: Hub -> Blank, then interrupt in KTF LS -> OCCLUDED, then STL finishes back in Hub. + * + * This is similar to the previous scenario but the gesture may have been interrupted by any + * other transition. KTF needs to immediately finish in GLANCEABLE_HUB (there is a jump cut). + */ + @Test + fun transition_to_blank_interrupted_by_ktf_transition_then_finish_in_hub() = + testScope.runTest { + sceneTransitions.value = hubToBlank + + val currentStep by collectLastValue(keyguardTransitionRepository.transitions) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = STARTED, + value = 0f, + ownerName = ownerName, + ) + ) + + progress.emit(0.4f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = GLANCEABLE_HUB, + to = LOCKSCREEN, + transitionState = RUNNING, + value = 0.4f, + ownerName = ownerName, + ) + ) + + // Start another transition externally while our scene + // transition is happening. + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = "external", + from = LOCKSCREEN, + to = OCCLUDED, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = "external", + ) + ) + + // Scene progress should not affect KTF transition anymore + progress.emit(0.7f) + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = LOCKSCREEN, + to = OCCLUDED, + transitionState = STARTED, + value = 0f, + ownerName = "external", + ) + ) + + // We land back in communal. + sceneTransitions.value = Idle(CommunalScenes.Communal) + + assertThat(currentStep) + .isEqualTo( + TransitionStep( + from = OCCLUDED, + to = GLANCEABLE_HUB, + transitionState = FINISHED, + value = 1f, + ownerName = ownerName, + ) + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt index ac50db483301..e36fd75445e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt @@ -14,17 +14,20 @@ * limitations under the License. */ +package com.android.systemui.communal.widgets + import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -34,6 +37,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.verify +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { @@ -66,7 +70,7 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { } @Test - fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() { + fun animationCancelled_launchingWidgetStateIsCleared() { with(kosmos) { testScope.runTest { val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) @@ -81,9 +85,12 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { assertTrue(launching!!) verify(controller).onIntentStarted(willAnimate = true) + underTest.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertTrue(launching!!) + verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true) + underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true) assertFalse(launching!!) - Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true) } } @@ -105,6 +112,12 @@ class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { assertTrue(launching!!) verify(controller).onIntentStarted(willAnimate = true) + underTest.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertTrue(launching!!) + verify(controller).onTransitionAnimationStart(isExpandingFullyAbove = true) + + testScope.advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration) + underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true) assertFalse(launching!!) Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 693fcdabea58..18839e6acb96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -22,6 +22,7 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.classifier.falsingManager import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.testScope @@ -52,6 +53,7 @@ class QSLongPressEffectTest : SysuiTestCase() { private val vibratorHelper = kosmos.vibratorHelper private val qsTile = kosmos.qsTileFactory.createTile("Test Tile") @Mock private lateinit var callback: QSLongPressEffect.Callback + @Mock private lateinit var controller: ActivityTransitionAnimator.Controller private val effectDuration = 400 private val lowTickDuration = 12 @@ -218,8 +220,9 @@ class QSLongPressEffectTest : SysuiTestCase() { // GIVEN that the animation completes longPressEffect.handleAnimationComplete() - // THEN the effect ends in the idle state. + // THEN the effect ends in the idle state and the reversed callback is used. assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + verify(callback, times(1)).onEffectFinishedReversing() } @Test @@ -348,6 +351,23 @@ class QSLongPressEffectTest : SysuiTestCase() { assertThat(clickState).isEqualTo(QSLongPressEffect.State.IDLE) } + @Test + fun onLongClickTransitionCancelled_whileInLongClickState_reversesEffect() = + testWhileInState(QSLongPressEffect.State.LONG_CLICKED) { + // GIVEN a transition controller delegate + val delegate = longPressEffect.createTransitionControllerDelegate(controller) + + // WHEN the activity launch animation is cancelled + val newOccludedState = false + delegate.onTransitionAnimationCancelled(newOccludedState) + + // THEN the effect reverses and ends in RUNNING_BACKWARDS_FROM_CANCEL + assertThat(longPressEffect.state) + .isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) + verify(callback, times(1)).onReverseAnimator(false) + verify(controller).onTransitionAnimationCancelled(newOccludedState) + } + private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index a3959d2109c8..42cd5ec69466 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest @@ -25,9 +26,13 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.BrokenWithSceneContainer @@ -122,15 +127,22 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest private val fromGlanceableHubTransitionInteractor by lazy { kosmos.fromGlanceableHubTransitionInteractor } + private val communalSceneTransitionInteractor by lazy { + kosmos.communalSceneTransitionInteractor + } private val powerInteractor by lazy { kosmos.powerInteractor } private val communalInteractor by lazy { kosmos.communalInteractor } + private val communalSceneInteractor by lazy { kosmos.communalSceneInteractor } companion object { @JvmStatic @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() + return FlagsParameterization.allCombinationsOf( + FLAG_COMMUNAL_SCENE_KTF_REFACTOR, + ) + .andSceneContainer() } } @@ -163,6 +175,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest fromOccludedTransitionInteractor.start() fromAlternateBouncerTransitionInteractor.start() fromGlanceableHubTransitionInteractor.start() + communalSceneTransitionInteractor.start() } @Test @@ -636,6 +649,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun dozingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -770,6 +784,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun goneToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -799,6 +814,29 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun goneToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GONE + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + + // WHEN the glanceable hub is shown + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + to = KeyguardState.GLANCEABLE_HUB, + from = KeyguardState.GONE, + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + animatorAssertion = { it.isNull() } + ) + + coroutineContext.cancelChildren() + } + + @Test @DisableSceneContainer fun alternateBouncerToPrimaryBouncer() = testScope.runTest { @@ -941,6 +979,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test fun alternateBouncerToGlanceableHub() = testScope.runTest { + // GIVEN the device is idle on the glanceable hub + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( @@ -951,19 +994,11 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() - // WHEN the alternateBouncer stops showing bouncerRepository.setAlternateVisible(false) advanceTimeBy(200L) - // THEN a transition to LOCKSCREEN should occur + // THEN a transition to GLANCEABLE_HUB should occur assertThat(transitionRepository) .startedTransition( ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName, @@ -1063,18 +1098,17 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @DisableSceneContainer fun primaryBouncerToGlanceableHub() = testScope.runTest { - // GIVEN a prior transition has run to PRIMARY_BOUNCER - bouncerRepository.setPrimaryShow(true) - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) + communalSceneInteractor.changeScene(CommunalScenes.Communal) runCurrent() + // GIVEN a prior transition has run to PRIMARY_BOUNCER + bouncerRepository.setPrimaryShow(true) + runTransitionAndSetWakefulness( + KeyguardState.GLANCEABLE_HUB, + KeyguardState.PRIMARY_BOUNCER + ) + // WHEN the primaryBouncer stops showing bouncerRepository.setPrimaryShow(false) runCurrent() @@ -1095,27 +1129,26 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @DisableSceneContainer fun primaryBouncerToGlanceableHubWhileDreaming() = testScope.runTest { + // GIVEN the device is idle on the glanceable hub + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) - runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) + runTransitionAndSetWakefulness( + KeyguardState.GLANCEABLE_HUB, + KeyguardState.PRIMARY_BOUNCER + ) // GIVEN that we are dreaming and occluded keyguardRepository.setDreaming(true) keyguardRepository.setKeyguardOccluded(true) - // GIVEN the device is idle on the glanceable hub - val idleTransitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) - ) - communalInteractor.setTransitionState(idleTransitionState) - runCurrent() - // WHEN the primaryBouncer stops showing bouncerRepository.setPrimaryShow(false) runCurrent() - // THEN a transition to LOCKSCREEN should occur + // THEN a transition to GLANCEABLE_HUB should occur assertThat(transitionRepository) .startedTransition( ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName, @@ -1219,6 +1252,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun occludedToGlanceableHub() = testScope.runTest { // GIVEN a device on lockscreen @@ -1256,6 +1290,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun occludedToGlanceableHubWhenInitiallyOnHub() = testScope.runTest { // GIVEN a device on lockscreen and communal is available @@ -1293,6 +1328,37 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun occludedToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a device on lockscreen and communal is available + keyguardRepository.setKeyguardShowing(true) + kosmos.setCommunalAvailable(true) + runCurrent() + + // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB + runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // THEN a transition to GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test fun occludedToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to OCCLUDED @@ -1511,6 +1577,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun dreamingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1550,6 +1617,47 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun dreamingToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN a transition to the glanceable hub starts + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.DREAMING, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded() = testScope.runTest { @@ -1679,6 +1787,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun lockscreenToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1737,6 +1846,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun lockscreenToGlanceableHub_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.AOD, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN a glanceable hub transition starts + val currentScene = CommunalScenes.Blank + val targetScene = CommunalScenes.Communal + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1792,6 +1943,48 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToLockscreen_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN a transition away from glanceable hub starts + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + progress.value = .1f + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToDozing() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1814,6 +2007,31 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToDozing_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN the device begins to sleep + powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DOZING, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer fun glanceableHubToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1858,6 +2076,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @BrokenWithSceneContainer(339465026) + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToOccluded() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1888,7 +2107,33 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToOccluded_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToGone() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1911,6 +2156,32 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest @Test @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToGone_communalKtfRefactor() = + testScope.runTest { + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN keyguard goes away + keyguardRepository.setKeyguardGoingAway(true) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.GONE, + animatorAssertion = { it.isNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) fun glanceableHubToDreaming() = testScope.runTest { // GIVEN that we are dreaming and not dozing @@ -1939,7 +2210,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest isUserInputOngoing = flowOf(false), ) ) - communalInteractor.setTransitionState(transitionState) + communalSceneInteractor.setTransitionState(transitionState) runCurrent() assertThat(transitionRepository) @@ -1953,6 +2224,52 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest coroutineContext.cancelChildren() } + @Test + @DisableSceneContainer + @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR) + fun glanceableHubToDreaming_communalKtfRefactor() = + testScope.runTest { + // GIVEN that we are dreaming and not dozing + keyguardRepository.setDreaming(true) + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH) + ) + runCurrent() + + // GIVEN a prior transition has run to GLANCEABLE_HUB + communalSceneInteractor.changeScene(CommunalScenes.Communal) + runCurrent() + clearInvocations(transitionRepository) + + // WHEN a transition away from glanceable hub starts + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = flowOf(0f, 0.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + ownerName = CommunalSceneTransitionInteractor::class.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNull() }, // transition should be manually animated + ) + + coroutineContext.cancelChildren() + } + private suspend fun TestScope.runTransitionAndSetWakefulness( from: KeyguardState, to: KeyguardState diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index ce134e64bf57..75ecb2c5307c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -41,8 +41,12 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.shared.model.KeyguardState; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeAnimationRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -91,6 +95,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Mock private HeadsUpManager mHeadsUpManager; @Mock private VisibilityLocationProvider mVisibilityLocationProvider; @Mock private VisualStabilityProvider mVisualStabilityProvider; + @Mock private VisualStabilityCoordinatorLogger mLogger; @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor; @@ -128,7 +133,9 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mVisibilityLocationProvider, mVisualStabilityProvider, mWakefulnessLifecycle, - mKosmos.getCommunalInteractor()); + mKosmos.getCommunalInteractor(), + mKosmos.getKeyguardTransitionInteractor(), + mLogger); mCoordinator.attach(mNotifPipeline); mTestScope.getTestScheduler().runCurrent(); @@ -241,6 +248,38 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } @Test + public void testLockscreenPartlyShowing_groupAndSectionChangesNotAllowed() { + // GIVEN the panel true expanded and device isn't pulsing + setFullyDozed(false); + setSleepy(false); + setLockscreenShowing(0.5f); + setPulsing(false); + + // THEN group changes are NOT allowed + assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); + + // THEN section changes are NOT allowed + assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); + } + + @Test + public void testLockscreenFullyShowing_groupAndSectionChangesNotAllowed() { + // GIVEN the panel true expanded and device isn't pulsing + setFullyDozed(false); + setSleepy(false); + setLockscreenShowing(1.0f); + setPulsing(false); + + // THEN group changes are NOT allowed + assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry)); + assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry)); + + // THEN section changes are NOT allowed + assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); + } + + @Test public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() { // GIVEN the device is pulsing and screen is off setFullyDozed(true); @@ -614,7 +653,37 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { } private void setPanelExpanded(boolean expanded) { - mStatusBarStateListener.onExpandedChanged(expanded); + setPanelExpandedAndLockscreenShowing(expanded, /* lockscreenShowing = */ 0.0f); } + private void setLockscreenShowing(float lockscreenShowing) { + setPanelExpandedAndLockscreenShowing(/* panelExpanded = */ false, lockscreenShowing); + } + + private void setPanelExpandedAndLockscreenShowing(boolean panelExpanded, + float lockscreenShowing) { + if (SceneContainerFlag.isEnabled()) { + mStatusBarStateListener.onExpandedChanged(panelExpanded); + mKosmos.getKeyguardTransitionRepository().sendTransitionStepJava( + mTestScope, + makeLockscreenTransitionStep(lockscreenShowing), + /* validateStep = */ false); + } else { + mStatusBarStateListener.onExpandedChanged(panelExpanded || lockscreenShowing > 0.0f); + } + } + + private TransitionStep makeLockscreenTransitionStep(float value) { + if (value <= 0.0f) { + return new TransitionStep(KeyguardState.GONE); + } else if (value >= 1.0f) { + return new TransitionStep(KeyguardState.LOCKSCREEN); + } else { + return new TransitionStep( + KeyguardState.GONE, + KeyguardState.LOCKSCREEN, + value, + TransitionState.RUNNING); + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index d82b9dbfbae8..179799503ac0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -147,6 +147,48 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { } @Test + fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runAfterKeyguardGone() { + val intent = mock(Intent::class.java) + `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(keyguardStateController.isOccluded).thenReturn(true) + `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true)) + `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(false) + + underTest.startActivityDismissingKeyguard(intent, dismissShade = true) + mainExecutor.runAllReady() + + val actionCaptor = argumentCaptor<OnDismissAction>() + verify(statusBarKeyguardViewManager) + .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null)) + actionCaptor.firstValue.onDismiss() + mainExecutor.runAllReady() + + verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any()) + } + + @Test + fun startActivityDismissingKeyguard_dismissShadeWhenOccluded_runImmediately() { + val intent = mock(Intent::class.java) + `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(keyguardStateController.isOccluded).thenReturn(true) + `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true)) + `when`(communalSettingsInteractor.isCommunalFlagEnabled()).thenReturn(true) + + underTest.startActivityDismissingKeyguard(intent, dismissShade = true) + mainExecutor.runAllReady() + + val actionCaptor = argumentCaptor<OnDismissAction>() + verify(statusBarKeyguardViewManager) + .dismissWithAction(actionCaptor.capture(), any(), anyBoolean(), eq(null)) + actionCaptor.firstValue.onDismiss() + mainExecutor.runAllReady() + + verify(statusBarKeyguardViewManager, never()).addAfterKeyguardGoneRunnable(any()) + verify(activityTransitionAnimator) + .startIntentWithAnimation(eq(null), eq(false), eq(null), eq(false), any()) + } + + @Test fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() { val pendingIntent = mock(PendingIntent::class.java) `when`(pendingIntent.isActivity).thenReturn(true) @@ -342,7 +384,6 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) } - @EnableFlags(Flags.FLAG_COMMUNAL_HUB) @Test fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() { val parent = FrameLayout(context) @@ -389,7 +430,6 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) } - @DisableFlags(Flags.FLAG_COMMUNAL_HUB) @Test fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() { val parent = FrameLayout(context) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt index 5dadc4caf0f6..b91bde4fb417 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.policy import android.content.Context import android.os.Handler +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest @@ -239,6 +240,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager } @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) fun testShowNotification_reorderNotAllowed_notPulsing_seenInShadeTrue() { whenever(mVSProvider.isReorderingAllowed).thenReturn(false) val hmp = createHeadsUpManagerPhone() @@ -253,6 +255,7 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager } @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) fun testShowNotification_reorderAllowed_notPulsing_seenInShadeFalse() { whenever(mVSProvider.isReorderingAllowed).thenReturn(true) val hmp = createHeadsUpManagerPhone() diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml index 9128dca2080b..2b987e2d1fb1 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/AndroidManifest.xml +++ b/packages/SystemUI/res/drawable/checkbox_circle_shape.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ 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. @@ -14,15 +14,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.core.batteryusagestatsprototests"> - - <uses-permission android:name="android.permission.BATTERY_STATS"/> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.frameworks.core.batteryusagestatsprototests" - android:label="BatteryUsageStats Proto Tests" /> - -</manifest> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:bottom="12dp" + android:left="12dp" + android:right="12dp" + android:top="12dp"> + <selector> + <item + android:drawable="@drawable/ic_check_circle_filled_24dp" + android:state_checked="true" /> + <item + android:drawable="@drawable/ic_circle_outline_24dp" + android:state_checked="false" /> + </selector> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml new file mode 100644 index 000000000000..16e2a3db2e95 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_check_circle_filled_24dp.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?androidprv:attr/materialColorPrimary" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml new file mode 100644 index 000000000000..82fa4f08d0c8 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_circle_outline_24dp.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?androidprv:attr/materialColorPrimary" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" /> +</vector> diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index 5191895549b6..d7b94ec015ac 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -58,9 +58,10 @@ android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginStart="16dp" + android:button="@drawable/checkbox_circle_shape" android:checked="true" android:text="@string/backlinks_include_link" - android:textColor="?android:textColorSecondary" + android:textColor="?androidprv:attr/materialColorOnBackground" android:visibility="gone" app:layout_constraintBottom_toTopOf="@id/preview" app:layout_constraintStart_toEndOf="@id/cancel" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 68c83c747d73..acc12d7fdaf0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1431,10 +1431,10 @@ <string name="no_unseen_notif_text">No new notifications</string> <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] --> - <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string> + <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string> <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] --> - <string name="adaptive_notification_edu_hun_text">Your device now lowers the volume and reduces pop-ups on the screen for up to two minutes when you receive many notifications in a short time span.</string> + <string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string> <!-- Action label for going to adaptive notification settings [CHAR LIMIT=20] --> <string name="go_to_adaptive_notification_settings">Turn off</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 484e758bf973..4ef1f93481f7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -124,6 +124,8 @@ public class QuickStepContract { public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32; // Touchpad gestures are disabled public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33; + // PiP animation is running + public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34; // Communal hub is showing public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35; @@ -175,6 +177,7 @@ public class QuickStepContract { SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, SYSUI_STATE_SHORTCUT_HELPER_SHOWING, SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, + SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, SYSUI_STATE_COMMUNAL_HUB_SHOWING, }) public @interface SystemUiStateFlags {} @@ -280,6 +283,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) { str.add("touchpad_gestures_disabled"); } + if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) { + str.add("disable_gesture_pip_animating"); + } if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) { str.add("communal_hub_showing"); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java index 6e5e44e45548..e9c9bc7799a5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java @@ -363,6 +363,16 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks } @MainThread + void updateSettingsButtonStatus(int displayId, + @WindowMagnificationSettings.MagnificationSize int index) { + final MagnificationSettingsController magnificationSettingsController = + mMagnificationSettingsSupplier.get(displayId); + if (magnificationSettingsController != null) { + magnificationSettingsController.updateSettingsButtonStatusOnRestore(index); + } + } + + @MainThread void toggleSettingsPanelVisibility(int displayId) { final MagnificationSettingsController magnificationSettingsController = mMagnificationSettingsSupplier.get(displayId); @@ -446,6 +456,11 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks @VisibleForTesting final WindowMagnifierCallback mWindowMagnifierCallback = new WindowMagnifierCallback() { @Override + public void onWindowMagnifierBoundsRestored(int displayId, int index) { + mHandler.post(() -> updateSettingsButtonStatus(displayId, index)); + } + + @Override public void onWindowMagnifierBoundsChanged(int displayId, Rect frame) { if (mMagnificationConnectionImpl != null) { mMagnificationConnectionImpl.onWindowMagnifierBoundsChanged(displayId, frame); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java index ed7062b5a335..caf55174b6b5 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java @@ -100,6 +100,10 @@ public class MagnificationSettingsController implements ComponentCallbacks { mWindowMagnificationSettings.toggleSettingsPanelVisibility(); } + void updateSettingsButtonStatusOnRestore(@MagnificationSize int index) { + mWindowMagnificationSettings.updateSelectedButton(index); + } + void closeMagnificationSettings() { mContext.unregisterComponentCallbacks(this); mWindowMagnificationSettings.hideSettingPanel(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index b37ba8994e44..3828f9f66857 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -127,6 +127,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final WindowManager mWm; private float mScale; + private int mSettingsButtonIndex = MagnificationSize.DEFAULT; /** * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained @@ -436,6 +437,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!mMagnificationSizeScaleOptions.contains(index)) { return; } + mSettingsButtonIndex = index; int size = getMagnificationWindowSizeFromIndex(index); setWindowSize(size, size); } @@ -446,6 +448,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return (int) (initSize * scale) - (int) (initSize * scale) % 2; } + int getMagnificationFrameSizeFromIndex(@MagnificationSize int index) { + return getMagnificationWindowSizeFromIndex(index) - 2 * mMirrorSurfaceMargin; + } + void setEditMagnifierSizeMode(boolean enable) { mEditSizeEnable = enable; applyResourcesValues(); @@ -457,8 +463,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!enable) { // Keep the magnifier size when exiting edit mode - mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity( + mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity( + mSettingsButtonIndex, new Size(mMagnificationFrame.width(), mMagnificationFrame.height())); + } else { + mSettingsButtonIndex = MagnificationSize.CUSTOM; } } @@ -944,7 +953,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private void setMagnificationFrame(int width, int height, int centerX, int centerY) { - mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height)); + mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity( + mSettingsButtonIndex, new Size(width, height)); // Sets the initial frame area for the mirror and place it to the given center on the // display. @@ -954,6 +964,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private Size restoreMagnificationWindowFrameSizeIfPossible() { + if (Flags.saveAndRestoreMagnificationSettingsButtons()) { + return restoreMagnificationWindowFrameIndexAndSizeIfPossible(); + } + if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) { return getDefaultMagnificationWindowFrameSize(); } @@ -961,8 +975,37 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity(); } + private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() { + if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) { + notifyWindowSizeRestored(MagnificationSize.DEFAULT); + return getDefaultMagnificationWindowFrameSize(); + } + + // This will return DEFAULT index if the stored preference is in an invalid format. + // Therefore, except CUSTOM, we would like to calculate the window width and height based + // on the restored MagnificationSize index. + int restoredIndex = mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity(); + notifyWindowSizeRestored(restoredIndex); + if (restoredIndex == MagnificationSize.CUSTOM) { + return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity(); + } + + int restoredSize = getMagnificationFrameSizeFromIndex(restoredIndex); + return new Size(restoredSize, restoredSize); + } + + private void notifyWindowSizeRestored(@MagnificationSize int index) { + mSettingsButtonIndex = index; + if (isActivated()) { + // Send the callback only if the window magnification is activated. The check is to + // avoid updating the settings panel in the cases that window magnification is not yet + // activated such as during the constructor initialization of this class. + mWindowMagnifierCallback.onWindowMagnifierBoundsRestored(mDisplayId, index); + } + } + private Size getDefaultMagnificationWindowFrameSize() { - final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM) + final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.DEFAULT) - 2 * mMirrorSurfaceMargin; return new Size(defaultSize, defaultSize); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java index e83e85e1af1a..ee36c6e8ad35 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java @@ -16,10 +16,14 @@ package com.android.systemui.accessibility; +import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize; + import android.content.Context; import android.content.SharedPreferences; import android.util.Size; +import com.android.systemui.Flags; + /** * Class to handle SharedPreference for window magnification size. */ @@ -47,9 +51,15 @@ final class WindowMagnificationFrameSizePrefs { /** * Saves the window frame size for current screen density. */ - public void saveSizeForCurrentDensity(Size size) { - mWindowMagnificationSizePreferences.edit() - .putString(getKey(), size.toString()).apply(); + public void saveIndexAndSizeForCurrentDensity(int index, Size size) { + if (Flags.saveAndRestoreMagnificationSettingsButtons()) { + mWindowMagnificationSizePreferences.edit() + .putString(getKey(), + WindowMagnificationFrameSpec.serialize(index, size)).apply(); + } else { + mWindowMagnificationSizePreferences.edit() + .putString(getKey(), size.toString()).apply(); + } } /** @@ -62,10 +72,32 @@ final class WindowMagnificationFrameSizePrefs { } /** + * Gets the index preference for current screen density. Returns DEFAULT if no preference + * is found. + */ + public @MagnificationSize int getIndexForCurrentDensity() { + final String spec = mWindowMagnificationSizePreferences.getString(getKey(), null); + if (spec == null) { + return MagnificationSize.DEFAULT; + } + try { + return WindowMagnificationFrameSpec.deserialize(spec).getIndex(); + } catch (NumberFormatException e) { + return MagnificationSize.DEFAULT; + } + } + + /** * Gets the size preference for current screen density. */ public Size getSizeForCurrentDensity() { - return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null)); + if (Flags.saveAndRestoreMagnificationSettingsButtons()) { + return WindowMagnificationFrameSpec + .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null)) + .getSize(); + } else { + return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null)); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt new file mode 100644 index 000000000000..c261a997aec5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSpec.kt @@ -0,0 +1,46 @@ +/* + * 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.accessibility + +import android.util.Size + +data class WindowMagnificationFrameSpec(val index: Int, val size: Size) { + + companion object { + private fun throwInvalidWindowMagnificationFrameSpec(s: String?): Nothing { + throw NumberFormatException("Invalid WindowMagnificationFrameSpec: \"$s\"") + } + + @JvmStatic fun serialize(index: Int, size: Size) = "$index,$size" + + @JvmStatic + fun deserialize(s: String): WindowMagnificationFrameSpec { + val separatorIndex = s.indexOf(',') + if (separatorIndex < 0) { + throwInvalidWindowMagnificationFrameSpec(s) + } + return try { + WindowMagnificationFrameSpec( + s.substring(0, separatorIndex).toInt(), + Size.parseSize(s.substring(separatorIndex + 1)) + ) + } catch (e: NumberFormatException) { + throwInvalidWindowMagnificationFrameSpec(s) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index 5f6f21a6845b..99d966dfd9aa 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java @@ -58,6 +58,7 @@ import android.widget.Switch; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.systemui.Flags; import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView; import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; @@ -98,7 +99,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest private Button mDoneButton; private Button mEditButton; private ImageButton mFullScreenButton; - private int mLastSelectedButtonIndex = MagnificationSize.NONE; + private int mLastSelectedButtonIndex = MagnificationSize.DEFAULT; private boolean mAllowDiagonalScrolling = false; @@ -115,19 +116,21 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest @Retention(RetentionPolicy.SOURCE) @IntDef({ - MagnificationSize.NONE, + MagnificationSize.CUSTOM, MagnificationSize.SMALL, MagnificationSize.MEDIUM, MagnificationSize.LARGE, - MagnificationSize.FULLSCREEN + MagnificationSize.FULLSCREEN, + MagnificationSize.DEFAULT }) /** Denotes the Magnification size type. */ public @interface MagnificationSize { - int NONE = 0; + int CUSTOM = 0; int SMALL = 1; int MEDIUM = 2; int LARGE = 3; int FULLSCREEN = 4; + int DEFAULT = MEDIUM; } @VisibleForTesting @@ -445,13 +448,20 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest private void updateUIControlsIfNeeded() { int capability = getMagnificationCapability(); int selectedButtonIndex = mLastSelectedButtonIndex; + WindowMagnificationFrameSizePrefs windowMagnificationFrameSizePrefs = + new WindowMagnificationFrameSizePrefs(mContext); switch (capability) { case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: mEditButton.setVisibility(View.VISIBLE); mAllowDiagonalScrollingView.setVisibility(View.VISIBLE); mFullScreenButton.setVisibility(View.GONE); if (selectedButtonIndex == MagnificationSize.FULLSCREEN) { - selectedButtonIndex = MagnificationSize.NONE; + if (Flags.saveAndRestoreMagnificationSettingsButtons()) { + selectedButtonIndex = + windowMagnificationFrameSizePrefs.getIndexForCurrentDensity(); + } else { + selectedButtonIndex = MagnificationSize.CUSTOM; + } } break; @@ -613,7 +623,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest public void editMagnifierSizeMode(boolean enable) { setEditMagnifierSizeMode(enable); - updateSelectedButton(MagnificationSize.NONE); + updateSelectedButton(MagnificationSize.CUSTOM); hideSettingPanel(); } @@ -621,7 +631,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest if (index == MagnificationSize.FULLSCREEN) { // transit to fullscreen magnifier if needed transitToMagnificationMode(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - } else if (index != MagnificationSize.NONE) { + } else if (index != MagnificationSize.CUSTOM) { // update the window magnifier size mCallback.onSetMagnifierSize(index); // transit to window magnifier if needed @@ -706,7 +716,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest }); } - private void updateSelectedButton(@MagnificationSize int index) { + void updateSelectedButton(@MagnificationSize int index) { // Clear the state of last selected button if (mLastSelectedButtonIndex == MagnificationSize.SMALL) { mSmallButton.setSelected(false); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java index a25e9a20f81c..b4a248239f46 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize; + import android.graphics.Rect; /** @@ -68,4 +70,9 @@ interface WindowMagnifierCallback { * @param displayId The logical display id. */ void onClickSettingsButton(int displayId); + + /** + * Called when restoring the magnification window size. + */ + void onWindowMagnifierBoundsRestored(int displayId, @MagnificationSize int index); } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 3d201a36417b..a44533555a04 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.dagger import android.content.Context +import com.android.systemui.CoreStartable import com.android.systemui.communal.data.backup.CommunalBackupUtils import com.android.systemui.communal.data.db.CommunalDatabaseModule import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule @@ -26,6 +27,7 @@ import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryM import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule +import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.util.CommunalColors import com.android.systemui.communal.util.CommunalColorsImpl @@ -40,6 +42,8 @@ import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import dagger.Binds import dagger.Module import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import kotlinx.coroutines.CoroutineScope @Module( @@ -69,6 +73,13 @@ interface CommunalModule { @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors + @Binds + @IntoMap + @ClassKey(CommunalSceneTransitionInteractor::class) + abstract fun bindCommunalSceneTransitionInteractor( + impl: CommunalSceneTransitionInteractor + ): CoreStartable + companion object { @Provides @Communal diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt index 7a4006d515f7..260dcbad6201 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt @@ -28,7 +28,6 @@ import com.android.systemui.scene.shared.model.SceneDataSource import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -52,7 +51,7 @@ interface CommunalSceneRepository { fun changeScene(toScene: SceneKey, transitionKey: TransitionKey? = null) /** Immediately snaps to the desired scene. */ - fun snapToScene(toScene: SceneKey, delayMillis: Long = 0) + fun snapToScene(toScene: SceneKey) /** * Updates the transition state of the hub [SceneTransitionLayout]. @@ -93,11 +92,10 @@ constructor( } } - override fun snapToScene(toScene: SceneKey, delayMillis: Long) { + override fun snapToScene(toScene: SceneKey) { applicationScope.launch { // SceneTransitionLayout state updates must be triggered on the thread the STL was // created on. - delay(delayMillis) sceneDataSource.snapToScene(toScene) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt new file mode 100644 index 000000000000..7d9e1df41e6f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepository.kt @@ -0,0 +1,32 @@ +/* + * 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.communal.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class CommunalSceneTransitionRepository @Inject constructor() { + /** + * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the + * next transition away from communal scene is started. It will be consumed exactly once and + * after that the state will be set back to null. + */ + val nextLockscreenTargetState: MutableStateFlow<KeyguardState?> = MutableStateFlow(null) +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index e13161f91f16..dbddc23d6146 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -121,9 +121,25 @@ constructor( private val _editModeOpen = MutableStateFlow(false) - /** Whether edit mode is currently open. */ + /** + * Whether edit mode is currently open. This will be true from onCreate to onDestroy in + * [EditWidgetsActivity] and thus does not correspond to whether or not the activity is visible. + * + * Note that since this is called in onDestroy, it's not guaranteed to ever be set to false when + * edit mode is closed, such as in the case that a user exits edit mode manually with a back + * gesture or navigation gesture. + */ val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow() + private val _editActivityShowing = MutableStateFlow(false) + + /** + * Whether the edit mode activity is currently showing. This is true from onStart to onStop in + * [EditWidgetsActivity] so may be false even when the user is in edit mode, such as when a + * widget's individual configuration activity has launched. + */ + val editActivityShowing: StateFlow<Boolean> = _editActivityShowing.asStateFlow() + /** Whether communal features are enabled. */ val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled @@ -316,6 +332,10 @@ constructor( _editModeOpen.value = isOpen } + fun setEditActivityShowing(isOpen: Boolean) { + _editActivityShowing.value = isOpen + } + /** Show the widget editor Activity. */ fun showWidgetEditor( preselectedKey: String? = null, diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 122f964713a9..45cfe3673f3d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.domain.interactor +import com.android.app.tracing.coroutines.launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey @@ -26,9 +27,11 @@ import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.shared.model.KeyguardState import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -48,7 +52,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val communalSceneRepository: CommunalSceneRepository, ) { - val _isLaunchingWidget = MutableStateFlow(false) + private val _isLaunchingWidget = MutableStateFlow(false) /** Whether a widget launch is currently in progress. */ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow() @@ -57,17 +61,48 @@ constructor( _isLaunchingWidget.value = launching } + fun interface OnSceneAboutToChangeListener { + /** Notifies that the scene is about to change to [toScene]. */ + fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?) + } + + private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>() + + /** Registers a listener which is called when the scene is about to change. */ + fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) { + onSceneAboutToChangeListener.add(processor) + } + /** * Asks for an asynchronous scene witch to [newScene], which will use the corresponding * installed transition or the one specified by [transitionKey], if provided. */ - fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) { - communalSceneRepository.changeScene(newScene, transitionKey) + fun changeScene( + newScene: SceneKey, + transitionKey: TransitionKey? = null, + keyguardState: KeyguardState? = null, + ) { + applicationScope.launch { + notifyListeners(newScene, keyguardState) + communalSceneRepository.changeScene(newScene, transitionKey) + } } /** Immediately snaps to the new scene. */ - fun snapToScene(newScene: SceneKey, delayMillis: Long = 0) { - communalSceneRepository.snapToScene(newScene, delayMillis) + fun snapToScene( + newScene: SceneKey, + delayMillis: Long = 0, + keyguardState: KeyguardState? = null + ) { + applicationScope.launch("$TAG#snapToScene") { + delay(delayMillis) + notifyListeners(newScene, keyguardState) + communalSceneRepository.snapToScene(newScene) + } + } + + private fun notifyListeners(newScene: SceneKey, keyguardState: KeyguardState?) { + onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) } } /** Changes to Blank scene when starting an activity after dismissing keyguard. */ @@ -164,4 +199,8 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = false, ) + + private companion object { + const val TAG = "CommunalSceneInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt new file mode 100644 index 000000000000..8351566fcae6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt @@ -0,0 +1,293 @@ +/* + * 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.communal.domain.interactor + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.systemui.CoreStartable +import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.data.repository.CommunalSceneTransitionRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.InternalKeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.util.kotlin.pairwise +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** + * This class listens to [SceneTransitionLayout] transitions and manages keyguard transition + * framework (KTF) states accordingly for communal states. + * + * There are a few rules: + * - There are only 2 communal scenes: [CommunalScenes.Communal] and [CommunalScenes.Blank] + * - When scene framework is on [CommunalScenes.Blank], KTF is allowed to change its scenes freely + * - When scene framework is on [CommunalScenes.Communal], KTF is locked into + * [KeyguardState.GLANCEABLE_HUB] + */ +@SysUISingleton +class CommunalSceneTransitionInteractor +@Inject +constructor( + val transitionInteractor: KeyguardTransitionInteractor, + val internalTransitionInteractor: InternalKeyguardTransitionInteractor, + private val settingsInteractor: CommunalSettingsInteractor, + @Application private val applicationScope: CoroutineScope, + private val sceneInteractor: CommunalSceneInteractor, + private val repository: CommunalSceneTransitionRepository, + keyguardInteractor: KeyguardInteractor, +) : CoreStartable, CommunalSceneInteractor.OnSceneAboutToChangeListener { + + private var currentTransitionId: UUID? = null + private var progressJob: Job? = null + + private val currentToState: KeyguardState + get() = internalTransitionInteractor.currentTransitionInfoInternal.value.to + + /** + * The next keyguard state to trigger when exiting [CommunalScenes.Communal]. This is only used + * if the state is changed by user gesture or not explicitly defined by the caller when changing + * scenes programmatically. + * + * This is needed because we do not always want to exit back to the KTF state we came from. For + * example, when going from HUB (Communal) -> OCCLUDED (Blank) -> HUB (Communal) and then + * closing the hub via gesture, we don't want to go back to OCCLUDED but instead either go to + * DREAM or LOCKSCREEN depending on if there is a dream showing. + */ + private val nextKeyguardStateInternal = + combine( + keyguardInteractor.isDreaming, + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.isKeyguardGoingAway, + ) { dreaming, occluded, keyguardGoingAway -> + if (keyguardGoingAway) { + KeyguardState.GONE + } else if (dreaming) { + KeyguardState.DREAMING + } else if (occluded) { + KeyguardState.OCCLUDED + } else { + KeyguardState.LOCKSCREEN + } + } + + private val nextKeyguardState: StateFlow<KeyguardState> = + combine( + repository.nextLockscreenTargetState, + nextKeyguardStateInternal, + ) { override, nextState -> + override ?: nextState + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = KeyguardState.LOCKSCREEN, + ) + + override fun start() { + if ( + communalSceneKtfRefactor() && + settingsInteractor.isCommunalFlagEnabled() && + !SceneContainerFlag.isEnabled + ) { + sceneInteractor.registerSceneStateProcessor(this) + listenForSceneTransitionProgress() + } + } + + /** + * Called when the scene is programmatically changed, allowing callers to specify which KTF + * state should be set when transitioning to [CommunalScenes.Blank] + */ + override fun onSceneAboutToChange(toScene: SceneKey, keyguardState: KeyguardState?) { + if (toScene != CommunalScenes.Blank || keyguardState == null) return + repository.nextLockscreenTargetState.value = keyguardState + } + + /** Monitors [SceneTransitionLayout] state and updates KTF state accordingly. */ + private fun listenForSceneTransitionProgress() { + applicationScope.launch { + sceneInteractor.transitionState + .pairwise(ObservableTransitionState.Idle(CommunalScenes.Blank)) + .collect { (prevTransition, transition) -> + when (transition) { + is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition) + is ObservableTransitionState.Transition -> + handleTransition(prevTransition, transition) + } + } + } + } + + private suspend fun handleIdle( + prevTransition: ObservableTransitionState, + idle: ObservableTransitionState.Idle + ) { + if ( + prevTransition is ObservableTransitionState.Transition && + currentTransitionId != null && + idle.currentScene == prevTransition.toScene + ) { + finishCurrentTransition() + } else { + // We may receive an Idle event without a corresponding Transition + // event, such as when snapping to a scene without an animation. + val targetState = + if (idle.currentScene == CommunalScenes.Blank) { + nextKeyguardState.value + } else { + KeyguardState.GLANCEABLE_HUB + } + transitionKtfTo(targetState) + repository.nextLockscreenTargetState.value = null + } + } + + private fun finishCurrentTransition() { + internalTransitionInteractor.updateTransition( + currentTransitionId!!, + 1f, + TransitionState.FINISHED + ) + resetTransitionData() + } + + private suspend fun finishReversedTransitionTo(state: KeyguardState) { + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = internalTransitionInteractor.currentTransitionInfoInternal.value.to, + to = state, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.REVERSE + ) + currentTransitionId = internalTransitionInteractor.startTransition(newTransition) + internalTransitionInteractor.updateTransition( + currentTransitionId!!, + 1f, + TransitionState.FINISHED + ) + resetTransitionData() + } + + private fun resetTransitionData() { + progressJob?.cancel() + progressJob = null + currentTransitionId = null + } + + private suspend fun handleTransition( + prevTransition: ObservableTransitionState, + transition: ObservableTransitionState.Transition + ) { + if (prevTransition.isTransitioning(from = transition.fromScene, to = transition.toScene)) { + // This is a new transition, but exactly the same as the previous state. Skip resetting + // KTF for this case and just collect the new progress instead. + collectProgress(transition) + } else if (transition.toScene == CommunalScenes.Communal) { + if (currentTransitionId != null) { + if (currentToState == KeyguardState.GLANCEABLE_HUB) { + transitionKtfTo(transitionInteractor.getStartedFromState()) + } + } + startTransitionToGlanceableHub() + collectProgress(transition) + } else if (transition.toScene == CommunalScenes.Blank) { + if (currentTransitionId != null) { + // Another transition started before this one is completed. Transition to the + // GLANCEABLE_HUB state so that we can properly transition away from it. + transitionKtfTo(KeyguardState.GLANCEABLE_HUB) + } + startTransitionFromGlanceableHub() + collectProgress(transition) + } + } + + private suspend fun transitionKtfTo(state: KeyguardState) { + val currentTransition = transitionInteractor.transitionState.value + if (currentTransition.isFinishedIn(state)) { + // This is already the state we want to be in + resetTransitionData() + } else if (currentTransition.isTransitioning(to = state)) { + finishCurrentTransition() + } else { + finishReversedTransitionTo(state) + } + } + + private fun collectProgress(transition: ObservableTransitionState.Transition) { + progressJob?.cancel() + progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } } + } + + private suspend fun startTransitionFromGlanceableHub() { + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = KeyguardState.GLANCEABLE_HUB, + to = nextKeyguardState.value, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET, + ) + repository.nextLockscreenTargetState.value = null + startTransition(newTransition) + } + + private suspend fun startTransitionToGlanceableHub() { + val currentState = internalTransitionInteractor.currentTransitionInfoInternal.value.to + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = currentState, + to = KeyguardState.GLANCEABLE_HUB, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET, + ) + startTransition(newTransition) + } + + private suspend fun startTransition(transitionInfo: TransitionInfo) { + if (currentTransitionId != null) { + resetTransitionData() + } + currentTransitionId = internalTransitionInteractor.startTransition(transitionInfo) + } + + private fun updateProgress(progress: Float) { + if (currentTransitionId == null) return + internalTransitionInteractor.updateTransition( + currentTransitionId!!, + progress.coerceIn(0f, 1f), + TransitionState.RUNNING + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index 19d7ceba2310..01ed2b71bea0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.EditModeState import com.android.systemui.communal.widgets.WidgetConfigurator +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.media.controls.ui.view.MediaHost import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -75,8 +76,16 @@ abstract class BaseCommunalViewModel( communalInteractor.signalUserInteraction() } - fun changeScene(scene: SceneKey, transitionKey: TransitionKey? = null) { - communalSceneInteractor.changeScene(scene, transitionKey) + /** + * Asks for an asynchronous scene witch to [newScene], which will use the corresponding + * installed transition or the one specified by [transitionKey], if provided. + */ + fun changeScene( + scene: SceneKey, + transitionKey: TransitionKey? = null, + keyguardState: KeyguardState? = null + ) { + communalSceneInteractor.changeScene(scene, transitionKey, keyguardState) } fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 0353d2c043e8..830f543fd06f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -24,7 +24,6 @@ import android.content.res.Resources import android.util.Log import androidx.activity.result.ActivityResultLauncher import com.android.internal.logging.UiEventLogger -import com.android.systemui.Flags.enableWidgetPickerSizeFilter import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor @@ -176,16 +175,14 @@ constructor( return Intent(Intent.ACTION_PICK).apply { setPackage(packageName) - if (enableWidgetPickerSizeFilter()) { - putExtra( - EXTRA_DESIRED_WIDGET_WIDTH, - resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width) - ) - putExtra( - EXTRA_DESIRED_WIDGET_HEIGHT, - resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height) - ) - } + putExtra( + EXTRA_DESIRED_WIDGET_WIDTH, + resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width) + ) + putExtra( + EXTRA_DESIRED_WIDGET_HEIGHT, + resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height) + ) putExtra( AppWidgetManager.EXTRA_CATEGORY_FILTER, CommunalWidgetCategories.defaultCategories @@ -217,6 +214,14 @@ constructor( /** Sets whether edit mode is currently open */ fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen) + /** + * Sets whether the edit mode activity is currently showing. + * + * See [CommunalInteractor.editActivityShowing] for more info. + */ + fun setEditActivityShowing(showing: Boolean) = + communalInteractor.setEditActivityShowing(showing) + /** Called when exiting the edit mode, before transitioning back to the communal scene. */ fun cleanupEditModeState() { communalSceneInteractor.setEditModeState(null) diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt index 4efaf878f33f..08444623f24d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt @@ -37,13 +37,21 @@ class CommunalTransitionAnimatorController( delegate.onIntentStarted(willAnimate) } + override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { + delegate.onTransitionAnimationStart(isExpandingFullyAbove) + // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay. + communalSceneInteractor.snapToScene( + CommunalScenes.Blank, + ActivityTransitionAnimator.TIMINGS.totalDuration + ) + } + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { communalSceneInteractor.setIsLaunchingWidget(false) delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - communalSceneInteractor.snapToScene(CommunalScenes.Blank) communalSceneInteractor.setIsLaunchingWidget(false) delegate.onTransitionAnimationEnd(isExpandingFullyAbove) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index 46f802fd2bce..08fe42ede5d3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -96,8 +96,7 @@ constructor( run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") } } } - } - ?: run { Log.w(TAG, "No data in result.") } + } ?: run { Log.w(TAG, "No data in result.") } } else -> Log.w( @@ -195,6 +194,8 @@ constructor( override fun onStart() { super.onStart() + communalViewModel.setEditActivityShowing(true) + if (shouldOpenWidgetPickerOnStart) { onOpenWidgetPicker() shouldOpenWidgetPickerOnStart = false @@ -206,6 +207,7 @@ constructor( override fun onStop() { super.onStop() + communalViewModel.setEditActivityShowing(false) logger.i("Stopping the communal widget editor activity") uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE) diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index c44eb471d460..491c73d0ae56 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -16,9 +16,15 @@ package com.android.systemui.haptics.qs +import android.content.ComponentName import android.os.VibrationEffect import android.service.quicksettings.Tile +import android.view.View import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.DelegateTransitionAnimatorController +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile @@ -58,6 +64,7 @@ constructor( /** The [QSTile] and [Expandable] used to perform a long-click and click actions */ var qsTile: QSTile? = null var expandable: Expandable? = null + private set /** Haptic effects */ private val durations = @@ -125,8 +132,10 @@ constructor( } fun handleAnimationStart() { - vibrate(longPressHint) - setState(State.RUNNING_FORWARD) + if (state == State.TIMEOUT_WAIT) { + vibrate(longPressHint) + setState(State.RUNNING_FORWARD) + } } /** This function is called both when an animator completes or gets cancelled */ @@ -147,7 +156,10 @@ constructor( setState(getStateForClick()) qsTile?.click(expandable) } - State.RUNNING_BACKWARDS_FROM_CANCEL -> setState(State.IDLE) + State.RUNNING_BACKWARDS_FROM_CANCEL -> { + callback?.onEffectFinishedReversing() + setState(State.IDLE) + } else -> {} } } @@ -222,13 +234,58 @@ constructor( fun resetState() = setState(State.IDLE) + fun createExpandableFromView(view: View) { + expandable = + object : Expandable { + override fun activityTransitionController( + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + component: ComponentName?, + returnCujType: Int?, + ): ActivityTransitionAnimator.Controller? { + val delegatedController = + ActivityTransitionAnimator.Controller.fromView( + view, + launchCujType, + cookie, + component, + returnCujType, + ) + return delegatedController?.let { createTransitionControllerDelegate(it) } + } + + override fun dialogTransitionController( + cuj: DialogCuj?, + ): DialogTransitionAnimator.Controller? = + DialogTransitionAnimator.Controller.fromView(view, cuj) + } + } + + @VisibleForTesting + fun createTransitionControllerDelegate( + controller: ActivityTransitionAnimator.Controller + ): DelegateTransitionAnimatorController { + val delegated = + object : DelegateTransitionAnimatorController(controller) { + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + if (state == State.LONG_CLICKED) { + setState(State.RUNNING_BACKWARDS_FROM_CANCEL) + callback?.onReverseAnimator(false) + } + delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) + } + } + return delegated + } + enum class State { IDLE, /* The effect is idle waiting for touch input */ TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */ RUNNING_FORWARD, /* The effect is running normally */ /* The effect was interrupted by an ACTION_UP and is now running backwards */ RUNNING_BACKWARDS_FROM_UP, - /* The effect was interrupted by an ACTION_CANCEL and is now running backwards */ + /* The effect was cancelled by an ACTION_CANCEL or a shade collapse and is now running + backwards */ RUNNING_BACKWARDS_FROM_CANCEL, CLICKED, /* The effect has ended with a click */ LONG_CLICKED, /* The effect has ended with a long-click */ @@ -247,7 +304,7 @@ constructor( fun onStartAnimator() /** Reverse the effect animator */ - fun onReverseAnimator() + fun onReverseAnimator(playHaptics: Boolean = true) /** Cancel the effect animator */ fun onCancelAnimator() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 59ec87a74980..e5ccc4a14003 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -119,7 +119,9 @@ constructor( // needed. Also, don't react to wake and unlock events, as we'll be // receiving a call to #dismissAod() shortly when the authentication // completes. - !maybeStartTransitionToOccludedOrInsecureCamera() && + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && !isWakeAndUnlock(biometricUnlockState.mode) && !primaryBouncerShowing } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 8f50b03eafec..8ef138eeb16a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -20,8 +20,11 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -166,7 +169,7 @@ constructor( } } else if (occluded) { startTransitionTo(KeyguardState.OCCLUDED) - } else if (isIdleOnCommunal) { + } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { @@ -183,7 +186,7 @@ constructor( if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is needed } else { - startTransitionTo(KeyguardState.GLANCEABLE_HUB) + transitionToGlanceableHub() } } else { startTransitionTo(KeyguardState.LOCKSCREEN) @@ -218,7 +221,9 @@ constructor( canWakeDirectlyToGone, primaryBouncerShowing) -> if ( - !maybeStartTransitionToOccludedOrInsecureCamera() && + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && // Handled by dismissFromDozing(). !isWakeAndUnlock(biometricUnlockState.mode) ) { @@ -242,7 +247,7 @@ constructor( ownerReason = "waking from dozing" ) } - } else if (isIdleOnCommunal) { + } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) { if (SceneContainerFlag.isEnabled) { // TODO(b/336576536): Check if adaptation for scene framework is // needed @@ -264,10 +269,7 @@ constructor( // TODO(b/336576536): Check if adaptation for scene framework is // needed } else { - startTransitionTo( - KeyguardState.GLANCEABLE_HUB, - ownerReason = "waking from dozing" - ) + transitionToGlanceableHub() } } else { startTransitionTo( @@ -280,6 +282,18 @@ constructor( } } + private suspend fun transitionToGlanceableHub() { + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + CommunalScenes.Communal, + // Immediately show the hub when transitioning from dozing to hub. + CommunalTransitionKeys.Immediately, + ) + } else { + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } + } + /** Dismisses keyguard from the DOZING state. */ fun dismissFromDozing() { scope.launch { startTransitionTo(KeyguardState.GONE) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 453401d2aa7d..4c3a75e765b6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -81,7 +82,9 @@ constructor( listenForDreamingToLockscreenOrGone() listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) - listenForDreamingToGlanceableHub() + if (!communalSceneKtfRefactor()) { + listenForDreamingToGlanceableHub() + } listenForDreamingToPrimaryBouncer() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 1a7012abdbe7..d81195060071 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -19,7 +19,11 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -30,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -50,6 +55,7 @@ constructor( private val glanceableHubTransitions: GlanceableHubTransitions, private val communalSettingsInteractor: CommunalSettingsInteractor, keyguardInteractor: KeyguardInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, override val transitionRepository: KeyguardTransitionRepository, override val internalTransitionInteractor: InternalKeyguardTransitionInteractor, transitionInteractor: KeyguardTransitionInteractor, @@ -72,7 +78,9 @@ constructor( if (!communalSettingsInteractor.isCommunalFlagEnabled()) { return } - listenForHubToLockscreenOrDreaming() + if (!communalSceneKtfRefactor()) { + listenForHubToLockscreenOrDreaming() + } listenForHubToDozing() listenForHubToPrimaryBouncer() listenForHubToAlternateBouncer() @@ -120,7 +128,10 @@ constructor( scope.launch("$TAG#listenForHubToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } - .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) } + .collect { + // Bouncer shows on top of the hub, so do not change scenes here. + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } } } @@ -130,7 +141,10 @@ constructor( .filterRelevantKeyguardStateAnd { alternateBouncerShowing -> alternateBouncerShowing } - .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } + .collect { pair -> + // Bouncer shows on top of the hub, so do not change scenes here. + startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) + } } } @@ -139,10 +153,18 @@ constructor( powerInteractor.isAsleep .filterRelevantKeyguardStateAnd { isAsleep -> isAsleep } .collect { - startTransitionTo( - toState = KeyguardState.DOZING, - modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE, - ) + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.Immediately, + keyguardState = KeyguardState.DOZING, + ) + } else { + startTransitionTo( + toState = KeyguardState.DOZING, + modeOnCanceled = TransitionModeOnCanceled.LAST_VALUE, + ) + } } } } @@ -152,7 +174,44 @@ constructor( scope.launch { keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop .filterRelevantKeyguardStateAnd { onTop -> onTop } - .collect { maybeStartTransitionToOccludedOrInsecureCamera() } + .collect { + maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = state, + ) + null + } else { + startTransitionTo(state, ownerReason = reason) + } + } + } + } + } else if (communalSceneKtfRefactor()) { + scope.launch { + allOf( + keyguardInteractor.isKeyguardOccluded, + noneOf( + // Dream is a special-case of occluded, so filter out the dreaming + // case here. + keyguardInteractor.isDreaming, + // When launching activities from widgets on the hub, we have a + // custom occlusion animation. + communalSceneInteractor.isLaunchingWidget, + ), + ) + .filterRelevantKeyguardStateAnd { isOccludedAndNotDreamingNorLaunchingWidget -> + isOccludedAndNotDreamingNorLaunchingWidget + } + .collect { _ -> + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = KeyguardState.OCCLUDED, + ) + } } } else { scope.launch { @@ -160,9 +219,7 @@ constructor( .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming -> isOccludedAndNotDreaming } - .collect { isOccludedAndNotDreaming -> - startTransitionTo(KeyguardState.OCCLUDED) - } + .collect { _ -> startTransitionTo(KeyguardState.OCCLUDED) } } } } @@ -170,10 +227,33 @@ constructor( private fun listenForHubToGone() { // TODO(b/336576536): Check if adaptation for scene framework is needed if (SceneContainerFlag.isEnabled) return - scope.launch { - keyguardInteractor.isKeyguardGoingAway - .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } - .collect { startTransitionTo(KeyguardState.GONE) } + if (communalSceneKtfRefactor()) { + scope.launch { + allOf( + keyguardInteractor.isKeyguardGoingAway, + // TODO(b/327225415): Handle edit mode opening here to avoid going to GONE + // state until after edit mode is ready to be shown. + noneOf( + // When launching activities from widgets on the hub, we wait to change + // scenes until the activity launch is complete. + communalSceneInteractor.isLaunchingWidget, + ), + ) + .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } + .collect { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Blank, + transitionKey = CommunalTransitionKeys.SimpleFade, + keyguardState = KeyguardState.GONE + ) + } + } + } else { + scope.launch { + keyguardInteractor.isKeyguardGoingAway + .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway } + .collect { startTransitionTo(KeyguardState.GONE) } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 5c7adf0c5a8a..16c014f451f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -90,7 +91,9 @@ constructor( listenForLockscreenToPrimaryBouncerDragging() listenForLockscreenToAlternateBouncer() listenForLockscreenTransitionToCamera() - listenForLockscreenToGlanceableHub() + if (!communalSceneKtfRefactor()) { + listenForLockscreenToGlanceableHub() + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index f3ca9df6491c..2f320409f231 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -18,8 +18,12 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -49,6 +53,7 @@ constructor( keyguardInteractor: KeyguardInteractor, powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( @@ -140,7 +145,14 @@ constructor( } else if (isIdleOnCommunal || showCommunalFromOccluded) { // TODO(b/336576536): Check if adaptation for scene framework is needed if (SceneContainerFlag.isEnabled) return - startTransitionTo(KeyguardState.GLANCEABLE_HUB) + if (communalSceneKtfRefactor()) { + communalSceneInteractor.changeScene( + CommunalScenes.Communal, + CommunalTransitionKeys.SimpleFade + ) + } else { + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } } else { startTransitionTo(KeyguardState.LOCKSCREEN) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 24290881a0fb..6c89ce055470 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -113,10 +113,9 @@ constructor( (isBouncerShowing, isAwake, isActiveDreamLockscreenHosted, isIdleOnCommunal) -> if ( - !maybeStartTransitionToOccludedOrInsecureCamera() && - !isBouncerShowing && - isAwake && - !isActiveDreamLockscreenHosted + !maybeStartTransitionToOccludedOrInsecureCamera { state, reason -> + startTransitionTo(state, ownerReason = reason) + } && !isBouncerShowing && isAwake && !isActiveDreamLockscreenHosted ) { val toState = if (isIdleOnCommunal) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 89c717892d0a..d06ee645652c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -122,9 +122,14 @@ sealed class TransitionInteractor( * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch * gesture cases. If so, start the transition. * + * @param startTransition A callback which is triggered to start the transition to the desired + * KeyguardState. Allows caller to hook into the transition start if needed. + * * Returns true if a transition was started, false otherwise. */ - suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean { + suspend fun maybeStartTransitionToOccludedOrInsecureCamera( + startTransition: suspend (state: KeyguardState, reason: String) -> UUID? + ): Boolean { // The refactor is required for the occlusion interactor to work. KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode() @@ -136,10 +141,7 @@ sealed class TransitionInteractor( if (!maybeHandleInsecurePowerGesture()) { // Otherwise, the double tap gesture occurred while not GONE and not dismissable, // which means we will launch the secure camera, which OCCLUDES the keyguard. - startTransitionTo( - KeyguardState.OCCLUDED, - ownerReason = "Power button gesture on lockscreen" - ) + startTransition(KeyguardState.OCCLUDED, "Power button gesture on lockscreen") } return true @@ -147,10 +149,7 @@ sealed class TransitionInteractor( // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so // it's visible. // TODO(b/307976454) - Centralize transition to DREAMING here. - startTransitionTo( - KeyguardState.OCCLUDED, - ownerReason = "SHOW_WHEN_LOCKED activity on top" - ) + startTransition(KeyguardState.OCCLUDED, "SHOW_WHEN_LOCKED activity on top") return true } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt index 940d1e10651a..0532ee285d1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt @@ -25,6 +25,8 @@ import android.os.Messenger import android.util.ArrayMap import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.runBlocking +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -55,7 +57,14 @@ constructor( var observer: PreviewLifecycleObserver? = null return try { - val renderer = previewRendererFactory.create(request) + val renderer = + if (Flags.lockscreenPreviewRendererCreateOnMainThread()) { + runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) { + previewRendererFactory.create(request) + } + } else { + previewRendererFactory.create(request) + } observer = PreviewLifecycleObserver( diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index d1c9b8ea1803..b2ba0e1cd6a6 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -169,6 +169,14 @@ public class LogModule { return factory.create("NotifRemoteInputLog", 50 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for all logs related to notification visual stability. */ + @Provides + @SysUISingleton + @VisualStabilityLog + public static LogBuffer provideVisualStabilityLogBuffer(LogBufferFactory factory) { + return factory.create("VisualStabilityLog", 50 /* maxSize */, false /* systrace */); + } + /** Provides a logging buffer for all logs related to keyguard media controller. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt new file mode 100644 index 000000000000..b45ffc1128b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VisualStabilityLog.kt @@ -0,0 +1,25 @@ +/* + * 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.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for visual stability-related messages. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class VisualStabilityLog diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index f004c3a8916f..6c5337418d02 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -187,44 +187,17 @@ public class MediaProjectionPermissionActivity extends Activity } } - TextPaint paint = new TextPaint(); - paint.setTextSize(42); - CharSequence dialogText = null; CharSequence dialogTitle = null; - String appName = null; - if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) { + + final String appName = extractAppName(aInfo, packageManager); + final boolean hasCastingCapabilities = + Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName); + + if (hasCastingCapabilities) { dialogText = getString(R.string.media_projection_sys_service_dialog_warning); dialogTitle = getString(R.string.media_projection_sys_service_dialog_title); } else { - String label = aInfo.loadLabel(packageManager).toString(); - - // If the label contains new line characters it may push the security - // message below the fold of the dialog. Labels shouldn't have new line - // characters anyways, so just truncate the message the first time one - // is seen. - final int labelLength = label.length(); - int offset = 0; - while (offset < labelLength) { - final int codePoint = label.codePointAt(offset); - final int type = Character.getType(codePoint); - if (type == Character.LINE_SEPARATOR - || type == Character.CONTROL - || type == Character.PARAGRAPH_SEPARATOR) { - label = label.substring(0, offset) + ELLIPSIS; - break; - } - offset += Character.charCount(codePoint); - } - - if (label.isEmpty()) { - label = mPackageName; - } - - String unsanitizedAppName = TextUtils.ellipsize(label, - paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString(); - appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName); - String actionText = getString(R.string.media_projection_dialog_warning, appName); SpannableString message = new SpannableString(actionText); @@ -255,6 +228,7 @@ public class MediaProjectionPermissionActivity extends Activity grantMediaProjectionPermission(selectedOption.getMode()); }, () -> finish(RECORD_CANCEL, /* projection= */ null), + hasCastingCapabilities, appName, overrideDisableSingleAppOption, mUid, @@ -289,6 +263,47 @@ public class MediaProjectionPermissionActivity extends Activity } } + private String extractAppName(ApplicationInfo applicationInfo, PackageManager packageManager) { + String label = applicationInfo.loadLabel(packageManager).toString(); + + // If the label contains new line characters it may push the security + // message below the fold of the dialog. Labels shouldn't have new line + // characters anyways, so just truncate the message the first time one + // is seen. + final int labelLength = label.length(); + int offset = 0; + while (offset < labelLength) { + final int codePoint = label.codePointAt(offset); + final int type = Character.getType(codePoint); + if (type == Character.LINE_SEPARATOR + || type == Character.CONTROL + || type == Character.PARAGRAPH_SEPARATOR) { + label = label.substring(0, offset) + ELLIPSIS; + break; + } + offset += Character.charCount(codePoint); + } + + if (label.isEmpty()) { + label = mPackageName; + } + + TextPaint paint = new TextPaint(); + paint.setTextSize(42); + + String unsanitizedAppName = TextUtils.ellipsize(label, + paint, MAX_APP_NAME_SIZE_PX, TextUtils.TruncateAt.END).toString(); + String appName = BidiFormatter.getInstance().unicodeWrap(unsanitizedAppName); + + // Have app name be the package name as a default fallback, if specific app name can't be + // extracted + if (appName == null || appName.isEmpty()) { + return mPackageName; + } + + return appName; + } + @Override protected void onDestroy() { super.onDestroy(); diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt index 9ce8070131fa..6d1a4587d089 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt @@ -29,13 +29,20 @@ class MediaProjectionPermissionDialogDelegate( mediaProjectionConfig: MediaProjectionConfig?, private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>, private val onCancelClicked: Runnable, - private val appName: String?, + private val hasCastingCapabilities: Boolean, + appName: String, forceShowPartialScreenshare: Boolean, hostUid: Int, mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, ) : BaseMediaProjectionPermissionDialogDelegate<AlertDialog>( - createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare), + createOptionList( + context, + appName, + hasCastingCapabilities, + mediaProjectionConfig, + forceShowPartialScreenshare + ), appName, hostUid, mediaProjectionMetricsLogger @@ -43,7 +50,7 @@ class MediaProjectionPermissionDialogDelegate( override fun onCreate(dialog: AlertDialog, savedInstanceState: Bundle?) { super.onCreate(dialog, savedInstanceState) // TODO(b/270018943): Handle the case of System sharing (not recording nor casting) - if (appName == null) { + if (hasCastingCapabilities) { setDialogTitle(R.string.media_projection_entry_cast_permission_dialog_title) setStartButtonText(R.string.media_projection_entry_cast_permission_dialog_continue) } else { @@ -65,30 +72,29 @@ class MediaProjectionPermissionDialogDelegate( companion object { private fun createOptionList( context: Context, - appName: String?, + appName: String, + hasCastingCapabilities: Boolean, mediaProjectionConfig: MediaProjectionConfig?, overrideDisableSingleAppOption: Boolean = false, ): List<ScreenShareOption> { val singleAppWarningText = - if (appName == null) { + if (hasCastingCapabilities) { R.string.media_projection_entry_cast_permission_dialog_warning_single_app } else { R.string.media_projection_entry_app_permission_dialog_warning_single_app } val entireScreenWarningText = - if (appName == null) { + if (hasCastingCapabilities) { R.string.media_projection_entry_cast_permission_dialog_warning_entire_screen } else { R.string.media_projection_entry_app_permission_dialog_warning_entire_screen } - // The single app option should only be disabled if there is an app name provided, - // the client has setup a MediaProjection with - // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by - // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override. + // The single app option should only be disabled if the client has setup a + // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND + // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override. val singleAppOptionDisabled = - appName != null && - !overrideDisableSingleAppOption && + !overrideDisableSingleAppOption && mediaProjectionConfig?.regionToCapture == MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 6b3dfe1b90ad..dbfe8188b1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -107,7 +107,9 @@ constructor( set(value) { if (field == value) return field = value - updateHeight() + if (longPressEffect?.state != QSLongPressEffect.State.RUNNING_BACKWARDS_FROM_CANCEL) { + updateHeight() + } } override var squishinessFraction: Float = 1f @@ -381,14 +383,6 @@ constructor( } private fun updateHeight() { - // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the - // launch animation. - if (!haveLongPressPropertiesBeenReset && longPressEffect != null) { - // The launch animation of a long-press effect did not reset the long-press effect so - // we must do it here - resetLongPressEffectProperties() - longPressEffect.resetState() - } val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) { heightOverride @@ -417,17 +411,17 @@ constructor( } override fun init(tile: QSTile) { - val expandable = Expandable.fromView(this) if (longPressEffect != null) { isHapticFeedbackEnabled = false longPressEffect.qsTile = tile - longPressEffect.expandable = expandable + longPressEffect.createExpandableFromView(this) initLongPressEffectCallback() init( { _: View -> longPressEffect.onTileClick() }, null, // Haptics and long-clicks will be handled by the [QSLongPressEffect] ) } else { + val expandable = Expandable.fromView(this) init( { _: View? -> tile.click(expandable) }, { _: View? -> @@ -475,10 +469,10 @@ constructor( } } - override fun onReverseAnimator() { + override fun onReverseAnimator(playHaptics: Boolean) { longPressEffectAnimator?.let { val pausedProgress = it.animatedFraction - longPressEffect?.playReverseHaptics(pausedProgress) + if (playHaptics) longPressEffect?.playReverseHaptics(pausedProgress) it.reverse() } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 49144091cb62..54ae225442c9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -49,6 +49,7 @@ import java.util.List; import java.util.Random; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; /** * An AsyncTask that saves an image to the media store in the background. @@ -59,12 +60,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s"; private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; + /** + * POD used in the AsyncTask which saves an image in the background. + */ + static class SaveImageInBackgroundData { + public Bitmap image; + public Consumer<Uri> finisher; + public ActionsReadyListener mActionsReadyListener; + public QuickShareActionReadyListener mQuickShareActionsReadyListener; + public UserHandle owner; + public int displayId; + + void clearImage() { + image = null; + } + } + + /** + * Structure returned by the SaveImageInBackgroundTask + */ + public static class SavedImageData { + public Uri uri; + public List<Notification.Action> smartActions; + public Notification.Action quickShareAction; + public UserHandle owner; + public String subject; // Title for sharing + public Long imageTime; // Time at which screenshot was saved + + /** + * Used to reset the return data on error + */ + public void reset() { + uri = null; + smartActions = null; + quickShareAction = null; + subject = null; + imageTime = null; + } + } + + /** + * Structure returned by the QueryQuickShareInBackgroundTask + */ + static class QuickShareData { + public Notification.Action quickShareAction; + + /** + * Used to reset the return data on error + */ + public void reset() { + quickShareAction = null; + } + } + + interface ActionsReadyListener { + void onActionsReady(SavedImageData imageData); + } + + interface QuickShareActionReadyListener { + void onActionsReady(QuickShareData quickShareData); + } + private final Context mContext; private FeatureFlags mFlags; private final ScreenshotSmartActions mScreenshotSmartActions; - private final ScreenshotController.SaveImageInBackgroundData mParams; - private final ScreenshotController.SavedImageData mImageData; - private final ScreenshotController.QuickShareData mQuickShareData; + private final SaveImageInBackgroundData mParams; + private final SavedImageData mImageData; + private final QuickShareData mQuickShareData; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private String mScreenshotId; @@ -77,15 +139,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { FeatureFlags flags, ImageExporter exporter, ScreenshotSmartActions screenshotSmartActions, - ScreenshotController.SaveImageInBackgroundData data, + SaveImageInBackgroundData data, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ) { mContext = context; mFlags = flags; mScreenshotSmartActions = screenshotSmartActions; - mImageData = new ScreenshotController.SavedImageData(); - mQuickShareData = new ScreenshotController.QuickShareData(); + mImageData = new SavedImageData(); + mQuickShareData = new QuickShareData(); mImageExporter = exporter; // Prepare all the output metadata @@ -195,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { * Update the listener run when the saving task completes. Used to avoid showing UI for the * first screenshot when a second one is taken. */ - void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) { + void setActionsReadyListener(ActionsReadyListener listener) { mParams.mActionsReadyListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 773900981759..0a4635e0849b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * 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. @@ -35,10 +35,7 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.app.ExitTransitionCoordinator; import android.app.ICompatCameraControlCallback; -import android.app.Notification; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -55,7 +52,6 @@ import android.os.UserManager; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; import android.view.Display; import android.view.ScrollCaptureResponse; import android.view.View; @@ -67,7 +63,6 @@ import android.view.WindowManager; import android.widget.Toast; import android.window.WindowContext; -import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; @@ -89,7 +84,6 @@ import dagger.assisted.AssistedInject; import kotlin.Unit; -import java.util.List; import java.util.UUID; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -104,67 +98,6 @@ import javax.inject.Provider; public class ScreenshotController implements ScreenshotHandler { private static final String TAG = logTag(ScreenshotController.class); - /** - * POD used in the AsyncTask which saves an image in the background. - */ - static class SaveImageInBackgroundData { - public Bitmap image; - public Consumer<Uri> finisher; - public ScreenshotController.ActionsReadyListener mActionsReadyListener; - public ScreenshotController.QuickShareActionReadyListener mQuickShareActionsReadyListener; - public UserHandle owner; - public int displayId; - - void clearImage() { - image = null; - } - } - - /** - * Structure returned by the SaveImageInBackgroundTask - */ - public static class SavedImageData { - public Uri uri; - public List<Notification.Action> smartActions; - public Notification.Action quickShareAction; - public UserHandle owner; - public String subject; // Title for sharing - public Long imageTime; // Time at which screenshot was saved - - /** - * Used to reset the return data on error - */ - public void reset() { - uri = null; - smartActions = null; - quickShareAction = null; - subject = null; - imageTime = null; - } - } - - /** - * Structure returned by the QueryQuickShareInBackgroundTask - */ - static class QuickShareData { - public Notification.Action quickShareAction; - - /** - * Used to reset the return data on error - */ - public void reset() { - quickShareAction = null; - } - } - - interface ActionsReadyListener { - void onActionsReady(ScreenshotController.SavedImageData imageData); - } - - interface QuickShareActionReadyListener { - void onActionsReady(ScreenshotController.QuickShareData quickShareData); - } - public interface TransitionDestination { /** * Allows the long screenshot activity to call back with a destination location (the bounds @@ -213,7 +146,6 @@ public class ScreenshotController implements ScreenshotHandler { private final ScreenshotNotificationSmartActionsProvider mScreenshotNotificationSmartActionsProvider; private final TimeoutHandler mScreenshotHandler; - private final ActionIntentExecutor mActionIntentExecutor; private final UserManager mUserManager; private final AssistContentRequester mAssistContentRequester; private final ActionExecutor mActionExecutor; @@ -259,7 +191,6 @@ public class ScreenshotController implements ScreenshotHandler { BroadcastDispatcher broadcastDispatcher, ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, ScreenshotActionsController.Factory screenshotActionsControllerFactory, - ActionIntentExecutor actionIntentExecutor, ActionExecutor.Factory actionExecutorFactory, UserManager userManager, AssistContentRequester assistContentRequester, @@ -289,7 +220,6 @@ public class ScreenshotController implements ScreenshotHandler { final Context displayContext = context.createDisplayContext(display); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mFlags = flags; - mActionIntentExecutor = actionIntentExecutor; mUserManager = userManager; mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; @@ -765,33 +695,6 @@ public class ScreenshotController implements ScreenshotHandler { mScreenshotAnimation.start(); } - /** - * Supplies the necessary bits for the shared element transition to share sheet. - * Note that once called, the action intent to share must be sent immediately after. - */ - private Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() { - ExitTransitionCoordinator.ExitTransitionCallbacks callbacks = - new ExitTransitionCoordinator.ExitTransitionCallbacks() { - @Override - public boolean isReturnTransitionAllowed() { - return false; - } - - @Override - public void hideSharedElements() { - finishDismiss(); - } - - @Override - public void onFinish() { - } - }; - - return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null, - Pair.create(mViewProxy.getScreenshotPreview(), - ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)); - } - /** Reset screenshot view and then call onCompleteRunnable */ private void finishDismiss() { Log.d(TAG, "finishDismiss"); @@ -838,11 +741,11 @@ public class ScreenshotController implements ScreenshotHandler { private void saveScreenshotInWorkerThread( UserHandle owner, @NonNull Consumer<Uri> finisher, - @Nullable ActionsReadyListener actionsReadyListener, - @Nullable QuickShareActionReadyListener + @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener, + @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener quickShareActionsReadyListener) { - ScreenshotController.SaveImageInBackgroundData - data = new ScreenshotController.SaveImageInBackgroundData(); + SaveImageInBackgroundTask.SaveImageInBackgroundData + data = new SaveImageInBackgroundTask.SaveImageInBackgroundData(); data.image = mScreenBitmap; data.finisher = finisher; data.mActionsReadyListener = actionsReadyListener; @@ -881,7 +784,7 @@ public class ScreenshotController implements ScreenshotHandler { /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ - private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { + private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) { logScreenshotResultStatus(imageData.uri, imageData.owner); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index d090aea4cee5..b468d0e75a7a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -50,6 +50,9 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -76,6 +79,7 @@ constructor( private val communalInteractor: CommunalInteractor, private val communalViewModel: CommunalViewModel, private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, private val communalColors: CommunalColors, @@ -148,6 +152,19 @@ constructor( private var hubShowing = false /** + * True if we're transitioning to or from edit mode + * + * We block all touches and gestures when edit mode is open to prevent funky transition issues + * when entering and exiting edit mode because we delay exiting the hub scene when entering edit + * mode and enter the hub scene early when exiting edit mode to make for a smoother transition. + * Gestures during these transitions can result in broken and unexpected UI states. + * + * Tracks [CommunalInteractor.editActivityShowing] and the [KeyguardState.GONE] to + * [KeyguardState.GLANCEABLE_HUB] transition. + */ + private var inEditModeTransition = false + + /** * True if either the primary or alternate bouncer are open, meaning the hub should not receive * any touch input. */ @@ -323,6 +340,22 @@ constructor( ) collectFlow( containerView, + // When leaving edit mode, editActivityShowing is true until the edit mode activity + // finishes itself and the device locks, after which isInTransition will be true until + // we're fully on the hub. + anyOf( + communalInteractor.editActivityShowing, + keyguardTransitionInteractor.isInTransition( + Edge.create(KeyguardState.GONE, KeyguardState.GLANCEABLE_HUB) + ) + ), + { + inEditModeTransition = it + updateTouchHandlingState() + } + ) + collectFlow( + containerView, combine( shadeInteractor.isAnyFullyExpanded, shadeInteractor.isUserInteracting, @@ -359,8 +392,11 @@ constructor( * Also clears gesture exclusion zones when the hub is occluded or gone. */ private fun updateTouchHandlingState() { + // Only listen to gestures when we're settled in the hub keyguard state and the shade + // bouncer are not showing on top. val shouldInterceptGestures = - hubShowing && !(shadeShowingAndConsumingTouches || anyBouncerShowing) + hubShowing && + !(shadeShowingAndConsumingTouches || anyBouncerShowing || inEditModeTransition) if (shouldInterceptGestures) { lifecycleRegistry.currentState = Lifecycle.State.RESUMED } else { @@ -412,10 +448,10 @@ constructor( return false } - return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false + return communalContainerView?.let { handleTouchEventOnCommunalView(ev) } ?: false } - private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { + private fun handleTouchEventOnCommunalView(ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE @@ -431,7 +467,7 @@ constructor( if (isUp || isCancel) { isTrackingHubTouch = false } - return dispatchTouchEvent(view, ev) + return dispatchTouchEvent(ev) } return false @@ -441,7 +477,14 @@ constructor( * Dispatches the touch event to the communal container and sends a user activity event to reset * the screen timeout. */ - private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean { + private fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (inEditModeTransition) { + // Consume but ignore touches while we're transitioning to or from edit mode so that the + // user can't trigger another transition, such as by swiping the hub away, tapping a + // widget, or opening the shade/bouncer. Doing any of these while transitioning can + // result in broken states. + return true + } try { var handled = false communalContainerWrapper?.dispatchTouchEvent(ev) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c9126161c40f..b9d24abc4037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -51,6 +51,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.util.Pair; import android.util.SparseArray; import android.view.KeyEvent; @@ -200,6 +201,7 @@ public class CommandQueue extends IStatusBar.Stub implements * event. */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; + private final Context mContext; private final DisplayTracker mDisplayTracker; private final @Nullable CommandRegistry mRegistry; private final @Nullable DumpHandler mDumpHandler; @@ -571,6 +573,7 @@ public class CommandQueue extends IStatusBar.Stub implements DumpHandler dumpHandler, Lazy<PowerInteractor> powerInteractor ) { + mContext = context; mDisplayTracker = displayTracker; mRegistry = registry; mDumpHandler = dumpHandler; @@ -1209,7 +1212,12 @@ public class CommandQueue extends IStatusBar.Stub implements boolean showImeSwitcher) { if (displayId == INVALID_DISPLAY) return; - if (mLastUpdatedImeDisplayId != displayId + boolean isConcurrentMultiUserModeEnabled = UserManager.isVisibleBackgroundUsersEnabled() + && mContext.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled) + && android.view.inputmethod.Flags.concurrentInputMethods(); + + if (!isConcurrentMultiUserModeEnabled + && mLastUpdatedImeDisplayId != displayId && mLastUpdatedImeDisplayId != INVALID_DISPLAY) { // Set previous NavBar's IME window status as invisible when IME // window switched to another display for single-session IME case. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 71c98b8a47f0..696298e82db9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -29,7 +27,10 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.GroupEntry; @@ -41,7 +42,6 @@ import com.android.systemui.statusbar.notification.collection.provider.VisualSta import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype; import com.android.systemui.statusbar.policy.HeadsUpManager; -import com.android.systemui.util.Compile; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.kotlin.JavaAdapter; @@ -61,8 +61,6 @@ import javax.inject.Inject; // TODO(b/204468557): Move to @CoordinatorScope @SysUISingleton public class VisualStabilityCoordinator implements Coordinator, Dumpable { - public static final String TAG = "VisualStability"; - public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private final DelayableExecutor mDelayableExecutor; private final HeadsUpManager mHeadsUpManager; private final SeenNotificationsInteractor mSeenNotificationsInteractor; @@ -73,6 +71,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { private final VisualStabilityProvider mVisualStabilityProvider; private final WakefulnessLifecycle mWakefulnessLifecycle; private final CommunalInteractor mCommunalInteractor; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final VisualStabilityCoordinatorLogger mLogger; private boolean mSleepy = true; private boolean mFullyDozed; @@ -81,6 +81,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { private boolean mNotifPanelCollapsing; private boolean mNotifPanelLaunchingActivity; private boolean mCommunalShowing = false; + private boolean mLockscreenShowing = false; private boolean mPipelineRunAllowed; private boolean mReorderingAllowed; @@ -109,7 +110,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { VisibilityLocationProvider visibilityLocationProvider, VisualStabilityProvider visualStabilityProvider, WakefulnessLifecycle wakefulnessLifecycle, - CommunalInteractor communalInteractor) { + CommunalInteractor communalInteractor, + KeyguardTransitionInteractor keyguardTransitionInteractor, + VisualStabilityCoordinatorLogger logger) { mHeadsUpManager = headsUpManager; mShadeAnimationInteractor = shadeAnimationInteractor; mJavaAdapter = javaAdapter; @@ -120,6 +123,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { mStatusBarStateController = statusBarStateController; mDelayableExecutor = delayableExecutor; mCommunalInteractor = communalInteractor; + mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mLogger = logger; dumpManager.registerDumpable(this); } @@ -138,6 +143,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { this::onLaunchingActivityChanged); mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(), this::onCommunalShowingChanged); + mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue( + KeyguardState.LOCKSCREEN), + this::onLockscreenKeyguardStateTransitionValueChanged); pipeline.setVisualStabilityManager(mNotifStabilityManager); } @@ -221,12 +229,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { boolean wasReorderingAllowed = mReorderingAllowed; mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity(); mReorderingAllowed = isReorderingAllowed(); - if (DEBUG && (wasPipelineRunAllowed != mPipelineRunAllowed - || wasReorderingAllowed != mReorderingAllowed)) { - Log.d(TAG, "Stability allowances changed:" - + " pipelineRunAllowed " + wasPipelineRunAllowed + "->" + mPipelineRunAllowed - + " reorderingAllowed " + wasReorderingAllowed + "->" + mReorderingAllowed - + " when setting " + field + "=" + value); + if (wasPipelineRunAllowed != mPipelineRunAllowed + || wasReorderingAllowed != mReorderingAllowed) { + mLogger.logAllowancesChanged( + wasPipelineRunAllowed, mPipelineRunAllowed, + wasReorderingAllowed, mReorderingAllowed, + field, value); } if (mPipelineRunAllowed && mIsSuppressingPipelineRun) { mNotifStabilityManager.invalidateList("pipeline run suppression ended"); @@ -251,7 +259,9 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { } private boolean isReorderingAllowed() { - return ((mFullyDozed && mSleepy) || !mPanelExpanded || mCommunalShowing) && !mPulsing; + final boolean sleepyAndDozed = mFullyDozed && mSleepy; + final boolean stackShowing = mPanelExpanded || mLockscreenShowing; + return (sleepyAndDozed || !stackShowing || mCommunalShowing) && !mPulsing; } /** @@ -364,4 +374,14 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { mCommunalShowing = isShowing; updateAllowedStates("communalShowing", isShowing); } + + private void onLockscreenKeyguardStateTransitionValueChanged(float value) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { + return; + } + + final boolean isShowing = value > 0.0f; + mLockscreenShowing = isShowing; + updateAllowedStates("lockscreenShowing", isShowing); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt new file mode 100644 index 000000000000..fe23e4e12db4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorLogger.kt @@ -0,0 +1,56 @@ +/* + * 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.statusbar.notification.collection.coordinator + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.VisualStabilityLog +import javax.inject.Inject + +private const val TAG = "VisualStability" + +class VisualStabilityCoordinatorLogger +@Inject +constructor(@VisualStabilityLog private val buffer: LogBuffer) { + fun logAllowancesChanged( + wasRunAllowed: Boolean, + isRunAllowed: Boolean, + wasReorderingAllowed: Boolean, + isReorderingAllowed: Boolean, + field: String, + value: Boolean + ) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = wasRunAllowed + bool2 = isRunAllowed + bool3 = wasReorderingAllowed + bool4 = isReorderingAllowed + str1 = field + str2 = value.toString() + }, + { + "stability allowances changed:" + + " pipelineRunAllowed $bool1->$bool2" + + " reorderingAllowed $bool3->$bool4" + + " when setting $str1=$str2" + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt index 0c7ba15baa92..5614f3fc9590 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProvider.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification.collection.provider import android.util.ArraySet import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.util.ListenerSet import javax.inject.Inject @@ -21,7 +22,7 @@ class VisualStabilityProvider @Inject constructor() { field = value if (value) { notifyReorderingAllowed() - } else { + } else if (NotificationThrottleHun.isEnabled){ banListeners.forEach { listener -> listener.onReorderingBanned() } 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 4a447b7439b8..5d2b61b42db9 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 @@ -111,6 +111,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; @@ -4862,10 +4863,13 @@ public class NotificationStackScrollLayout * @param isHeadsUp true for appear, false for disappear animations */ public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { - final boolean closedAndSeenInShade = !mIsExpanded && row.getEntry() != null - && row.getEntry().isSeenInShade(); - final boolean addAnimation = mAnimationsEnabled && !closedAndSeenInShade && - (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); + boolean addAnimation = + mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); + if (NotificationThrottleHun.isEnabled()) { + final boolean closedAndSeenInShade = !mIsExpanded && row.getEntry() != null + && row.getEntry().isSeenInShade(); + addAnimation = addAnimation && !closedAndSeenInShade; + } if (SPEW) { Log.v(TAG, "generateHeadsUpAnimation:" + " addAnimation=" + addAnimation diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 8577d48b6679..e08dbb9df7dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.AnimationStateHandler; @@ -174,9 +175,12 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements }); javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); - mVisualStabilityProvider.addPersistentReorderingBannedListener(mOnReorderingBannedListener); - mVisualStabilityProvider.addPersistentReorderingAllowedListener( - mOnReorderingAllowedListener); + if (NotificationThrottleHun.isEnabled()) { + mVisualStabilityProvider.addPersistentReorderingBannedListener( + mOnReorderingBannedListener); + mVisualStabilityProvider.addPersistentReorderingAllowedListener( + mOnReorderingAllowedListener); + } } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -304,6 +308,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey()); if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) { headsUpEntry.mRemoteInputActive = remoteInputActive; + if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) { + headsUpEntry.mRemoteInputActivatedAtLeastOnce = true; + } if (remoteInputActive) { headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)"); } else { @@ -383,7 +390,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> { mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false); - mAvalancheController.setEnableAtRuntime(true); + if (NotificationThrottleHun.isEnabled()) { + mAvalancheController.setEnableAtRuntime(true); + } for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) { if (isHeadsUpEntry(entry.getKey())) { // Maybe the heads-up was removed already @@ -586,19 +595,29 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements @androidx.annotation.Nullable Runnable removeRunnable) { super.setEntry(entry, removeRunnable); - if (!mVisualStabilityProvider.isReorderingAllowed() - // We don't want to allow reordering while pulsing, but headsup need to - // time out anyway - && !entry.showingPulsing()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - entry.setSeenInShade(true); + if (NotificationThrottleHun.isEnabled()) { + if (!mVisualStabilityProvider.isReorderingAllowed() + // We don't want to allow reordering while pulsing, but headsup need to + // time out anyway + && !entry.showingPulsing()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + entry.setSeenInShade(true); + } } } @Override protected Runnable createRemoveRunnable(NotificationEntry entry) { return () -> { - if (mTrackingHeadsUp) { + if (!NotificationThrottleHun.isEnabled() + && !mVisualStabilityProvider.isReorderingAllowed() + // We don't want to allow reordering while pulsing, but headsup need to + // time out anyway + && !entry.showingPulsing()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityProvider.addTemporaryReorderingAllowedListener( + mOnReorderingAllowedListener); + } else if (mTrackingHeadsUp) { mEntriesToRemoveAfterExpand.add(entry); } else if (mVisualStabilityProvider.isReorderingAllowed() || entry.showingPulsing()) { @@ -614,6 +633,11 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements if (mEntriesToRemoveAfterExpand.contains(mEntry)) { mEntriesToRemoveAfterExpand.remove(mEntry); } + if (!NotificationThrottleHun.isEnabled()) { + if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(mEntry); + } + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index e69a78fb14fd..1a4708170a05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -40,7 +40,6 @@ import com.android.systemui.assist.AssistManager import com.android.systemui.camera.CameraIntents import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor -import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.dagger.qualifiers.Main @@ -208,10 +207,16 @@ constructor( val cancelRunnable = Runnable { callback?.onActivityStarted(ActivityManager.START_CANCELED) } - // Do not deferKeyguard when occluded because, when keyguard is occluded, - // we do not launch the activity until keyguard is done. + // Do not deferKeyguard when occluded because, when keyguard is occluded, we do not launch + // the activity until keyguard is done. The only exception is when we're on the Hub and want + // to dismiss the shade immediately, which means that another animation will take care of + // the transition. val occluded = (keyguardStateController.isShowing && keyguardStateController.isOccluded) - val deferred = !occluded + val dismissOnCommunal = + communalSettingsInteractor.isCommunalFlagEnabled() && + communalSceneInteractor.isCommunalVisible.value && + dismissShadeDirectly + val deferred = !occluded || dismissOnCommunal executeRunnableDismissingKeyguard( runnable, cancelRunnable, @@ -463,10 +468,18 @@ constructor( object : ActivityStarter.OnDismissAction { override fun onDismiss(): Boolean { if (runnable != null) { + // We don't wait for Keyguard to be gone if we're dismissing the shade + // immediately and we're on the Communal Hub. This is to make sure that the + // Hub -> Edit Mode transition is seamless. + val dismissOnCommunal = + communalSettingsInteractor.isCommunalFlagEnabled() && + communalSceneInteractor.isCommunalVisible.value && + dismissShade if ( keyguardStateController.isShowing && keyguardStateController.isOccluded && - !isCommunalWidgetLaunch() + !isCommunalWidgetLaunch() && + !dismissOnCommunal ) { statusBarKeyguardViewManagerLazy .get() @@ -562,12 +575,6 @@ constructor( override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { super.onTransitionAnimationStart(isExpandingFullyAbove) - if (communalSettingsInteractor.isCommunalFlagEnabled()) { - communalSceneInteractor.snapToScene( - CommunalScenes.Blank, - ActivityTransitionAnimator.TIMINGS.totalDuration - ) - } // Double check that the keyguard is still showing and not going // away, but if so set the keyguard occluded. Typically, WM will let // KeyguardViewMediator know directly, but we're overriding that to diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index a0eb989a57bb..65171358b050 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; +import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.GlobalSettings; @@ -726,6 +727,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * of AvalancheController that take it as param. */ public class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public boolean mRemoteInputActivatedAtLeastOnce; public boolean mRemoteInputActive; public boolean mUserActionMayIndirectlyRemove; @@ -835,6 +837,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public boolean isSticky() { if (mEntry == null) return false; + + if (ExpandHeadsUpOnInlineReply.isEnabled()) { + // we don't consider pinned and expanded huns as sticky after the remote input + // has been activated for them + if (!mRemoteInputActive && mRemoteInputActivatedAtLeastOnce) { + return false; + } + } + return (mEntry.isRowPinned() && mExpanded) || mRemoteInputActive || hasFullScreenIntent(mEntry); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt index 29040923a2cc..cf80263b4d14 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingEmptyImplModule.kt @@ -16,20 +16,15 @@ package com.android.systemui.volume.dagger -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.volume.domain.interactor.AudioSharingInteractor import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl +import dagger.Binds import dagger.Module -import dagger.Provides /** Dagger module for empty audio sharing impl for unnecessary volume overlay */ @Module interface AudioSharingEmptyImplModule { - companion object { - @Provides - @SysUISingleton - fun provideAudioSharingInteractor(): AudioSharingInteractor = - AudioSharingInteractorEmptyImpl() - } + @Binds + fun bindsAudioSharingInteractor(impl: AudioSharingInteractorEmptyImpl): AudioSharingInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt index 4d29788edf68..aba3015a6b7d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt @@ -77,7 +77,7 @@ constructor( } @SysUISingleton -class AudioSharingInteractorEmptyImpl : AudioSharingInteractor { +class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor { override val volume: Flow<Int?> = emptyFlow() override val volumeMin: Int = EMPTY_VOLUME override val volumeMax: Int = EMPTY_VOLUME diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index a71435168aca..14cd202b7ce6 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -20,8 +20,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; @@ -66,6 +67,7 @@ import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.sysui.ShellInterface; @@ -249,7 +251,25 @@ public final class WMShell implements pip.showPictureInPictureMenu(); } }); + pip.registerPipTransitionCallback( + new PipTransitionController.PipTransitionCallback() { + @Override + public void onPipTransitionStarted(int direction, Rect pipBounds) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + + @Override + public void onPipTransitionFinished(int direction) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + @Override + public void onPipTransitionCanceled(int direction) { + // No op. + } + }, mSysUiMainExecutor); mSysUiState.addCallback(sysUiStateFlag -> { mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index 41a41164b2a5..038b81b34d77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -216,6 +216,16 @@ public class MagnificationTest extends SysuiTestCase { } @Test + public void onRestoreWindowSize_updateSettingsButtonStatusOnRestore() { + mMagnification.mWindowMagnifierCallback + .onWindowMagnifierBoundsRestored(TEST_DISPLAY, MagnificationSize.SMALL); + waitForIdleSync(); + + verify(mMagnificationSettingsController) + .updateSettingsButtonStatusOnRestore(MagnificationSize.SMALL); + } + + @Test public void onSetMagnifierSize_delegateToMagnifier() { final @MagnificationSize int index = MagnificationSize.SMALL; mMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize( diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java index e27268292763..e01366a9e594 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java @@ -67,9 +67,8 @@ import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.testing.TestableResources; @@ -130,14 +129,12 @@ import java.util.function.Supplier; @LargeTest @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4.class) -@RequiresFlagsEnabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) +@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER) public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase { @Rule // NOTE: pass 'null' to allow this test advances time on the main thread. public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; @Mock @@ -625,6 +622,7 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT 0); } + @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) @Test public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() { int newSmallestScreenWidthDp = @@ -663,6 +661,50 @@ public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiT assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin)); } + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() { + int newSmallestScreenWidthDp = + mContext.getResources().getConfiguration().smallestScreenWidthDp * 2; + int windowFrameSize = mResources.getDimensionPixelSize( + com.android.internal.R.dimen.accessibility_window_magnifier_min_size); + Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize); + mSharedPreferences + .edit() + .putString(String.valueOf(newSmallestScreenWidthDp), + WindowMagnificationFrameSpec.serialize( + WindowMagnificationSettings.MagnificationSize.CUSTOM, + preferredWindowSize)) + .commit(); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN, + Float.NaN); + }); + + // Screen density and size change + mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp; + final Rect testWindowBounds = new Rect( + mWindowManager.getCurrentWindowMetrics().getBounds()); + testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, + testWindowBounds.right + 100, testWindowBounds.bottom + 100); + mWindowManager.setWindowBounds(testWindowBounds); + mInstrumentation.runOnMainSync(() -> { + mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); + }); + + // wait for rect update + waitForIdleSync(); + verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored( + eq(mContext.getDisplayId()), + eq(WindowMagnificationSettings.MagnificationSize.CUSTOM)); + ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams(); + final int mirrorSurfaceMargin = mResources.getDimensionPixelSize( + R.dimen.magnification_mirror_surface_margin); + // The width and height of the view include the magnification frame and the margins. + assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin)); + assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin)); + } + @Test public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() { mInstrumentation.runOnMainSync(() -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java index ad9053a7edda..944066fa4954 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -23,12 +25,15 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.util.Size; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.FakeSharedPreferences; @@ -54,19 +59,59 @@ public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase { mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext); } + @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) @Test public void saveSizeForCurrentDensity_getExpectedSize() { Size testSize = new Size(500, 500); - mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize); + mWindowMagnificationFrameSizePrefs + .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize); assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity()) .isEqualTo(testSize); } + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void saveSizeForCurrentDensity_validPreference_getExpectedSize() { + int testIndex = MagnificationSize.MEDIUM; + Size testSize = new Size(500, 500); + mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize); + + assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity()) + .isEqualTo(testSize); + } + + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() { + int testIndex = MagnificationSize.MEDIUM; + Size testSize = new Size(500, 500); + mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize); + + assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity()) + .isEqualTo(testIndex); + } + + @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS) + @Test + public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() { + mSharedPreferences + .edit() + .putString( + String.valueOf( + mContext.getResources().getConfiguration().smallestScreenWidthDp), + "100x200") + .commit(); + + assertThat(mWindowMagnificationFrameSizePrefs.getIndexForCurrentDensity()) + .isEqualTo(MagnificationSize.DEFAULT); + } + @Test public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() { + int testIndex = MagnificationSize.MEDIUM; Size testSize = new Size(500, 500); - mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize); + mWindowMagnificationFrameSizePrefs.saveIndexAndSizeForCurrentDensity(testIndex, testSize); assertThat(mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) .isTrue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt new file mode 100644 index 000000000000..791a26ed761d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSpecTest.kt @@ -0,0 +1,51 @@ +/* + * 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.accessibility + +import android.testing.AndroidTestingRunner +import android.util.Size +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class WindowMagnificationFrameSpecTest : SysuiTestCase() { + + @Test + fun deserializeSpec_validSpec_expectedIndex() { + val targetIndex = MagnificationSize.LARGE + val targetSize = Size(100, 200) + val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize) + + assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).index) + .isEqualTo(targetIndex) + } + + @Test + fun deserializeSpec_validSpec_expectedSize() { + val targetIndex = MagnificationSize.LARGE + val targetSize = Size(100, 200) + val targetPreference = WindowMagnificationFrameSpec.serialize(targetIndex, targetSize) + + assertThat(WindowMagnificationFrameSpec.deserialize(targetPreference).size) + .isEqualTo(targetSize) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 677d1fdaa01d..6dcea144f2a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -449,150 +449,214 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun shows_authenticated_no_errors_no_confirmation_required() = runGenericTest { - if (!testCase.confirmationRequested) { - val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - val iconOverlayAsset by - collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) - val iconContentDescriptionId by - collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) - val shouldAnimateIconView by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) - val shouldAnimateIconOverlay by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) - verifyIconSize() + fun shows_error_to_unlock_or_success() { + // Face-only auth does not use error -> unlock or error -> success assets + if (testCase.isFingerprintOnly || testCase.isCoex) { + runGenericTest { + // Distinct asset for error -> success only applicable for fingerprint-only / + // explicit co-ex auth + val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + val iconContentDescriptionId by + collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) + val shouldAnimateIconView by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) + + var forceExplicitFlow = + testCase.isCoex && testCase.confirmationRequested || + testCase.authenticatedByFingerprint + if (forceExplicitFlow) { + kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true) + } + verifyIconSize(forceExplicitFlow) - kosmos.promptViewModel.showAuthenticated( - modality = testCase.authenticatedModality, - dismissAfterDelay = DELAY - ) + kosmos.promptViewModel.ensureFingerprintHasStarted(isDelayed = true) + kosmos.promptViewModel.iconViewModel.setPreviousIconWasError(true) - if (testCase.isFingerprintOnly) { - // Fingerprint icon asset assertions - if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { - assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset()) - assertThat(iconOverlayAsset) - .isEqualTo(R.raw.biometricprompt_symbol_fingerprint_to_success_landscape) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) - assertThat(shouldAnimateIconView).isEqualTo(true) - assertThat(shouldAnimateIconOverlay).isEqualTo(true) - } else { - assertThat(iconAsset) - .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie) + kosmos.promptViewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) + + // TODO(b/350121748): SFPS test cases to be added after SFPS assets update + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + // Non-SFPS (UDFPS / rear-FPS) test cases + // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint + if (testCase.authenticatedByFingerprint) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_error_to_success_lottie) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + } else { // co-ex, authenticated by face + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_error_to_unlock_lottie) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + assertThat(shouldAnimateIconView).isEqualTo(true) + + // Confirm authentication + kosmos.promptViewModel.confirmAuthenticated() + + assertThat(iconAsset) + .isEqualTo( + R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie + ) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + } + } + } + } + } + + @Test + fun shows_authenticated_no_errors_no_confirmation_required() { + if (!testCase.confirmationRequested) { + runGenericTest { + val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + val iconOverlayAsset by + collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) + val iconContentDescriptionId by + collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) + val shouldAnimateIconView by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) + val shouldAnimateIconOverlay by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) + verifyIconSize() + + kosmos.promptViewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) + + if (testCase.isFingerprintOnly) { + // Fingerprint icon asset assertions + if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset).isEqualTo(getSfpsBaseIconAsset()) + assertThat(iconOverlayAsset) + .isEqualTo( + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape + ) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(true) + } else { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } else if (testCase.isFaceOnly || testCase.isCoex) { + // Face icon asset assertions + // If co-ex, use implicit flow (explicit flow always requires confirmation) + assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) assertThat(iconOverlayAsset).isEqualTo(-1) assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) assertThat(shouldAnimateIconView).isEqualTo(true) assertThat(shouldAnimateIconOverlay).isEqualTo(false) } - } else if (testCase.isFaceOnly || testCase.isCoex) { - // Face icon asset assertions - // If co-ex, use implicit flow (explicit flow always requires confirmation) - assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) - assertThat(iconOverlayAsset).isEqualTo(-1) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) - assertThat(shouldAnimateIconView).isEqualTo(true) - assertThat(shouldAnimateIconOverlay).isEqualTo(false) } } } @Test - fun shows_pending_confirmation() = runGenericTest { - if ( - (testCase.isFaceOnly || testCase.isCoex) && - testCase.authenticatedByFace && - testCase.confirmationRequested - ) { - val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - val iconOverlayAsset by - collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) - val iconContentDescriptionId by - collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) - val shouldAnimateIconView by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) - val shouldAnimateIconOverlay by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) - - val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested - verifyIconSize(forceExplicitFlow = forceExplicitFlow) - - kosmos.promptViewModel.showAuthenticated( - modality = testCase.authenticatedModality, - dismissAfterDelay = DELAY - ) + fun shows_pending_confirmation() { + if (testCase.authenticatedByFace && testCase.confirmationRequested) { + runGenericTest { + val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + val iconOverlayAsset by + collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) + val iconContentDescriptionId by + collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) + val shouldAnimateIconView by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) + val shouldAnimateIconOverlay by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) + + val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested + verifyIconSize(forceExplicitFlow = forceExplicitFlow) + + kosmos.promptViewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) - if (testCase.isFaceOnly) { - assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark) - assertThat(iconOverlayAsset).isEqualTo(-1) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) - assertThat(shouldAnimateIconView).isEqualTo(true) - assertThat(shouldAnimateIconOverlay).isEqualTo(false) - } else if (testCase.isCoex) { // explicit flow, confirmation requested - // TODO: Update when SFPS co-ex is implemented - if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { - assertThat(iconAsset) - .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie) + if (testCase.isFaceOnly) { + assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark) assertThat(iconOverlayAsset).isEqualTo(-1) assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated) assertThat(shouldAnimateIconView).isEqualTo(true) assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } else if (testCase.isCoex) { // explicit flow, confirmation requested + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } } } } } @Test - fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest { - if ( - (testCase.isFaceOnly || testCase.isCoex) && - testCase.authenticatedByFace && - testCase.confirmationRequested - ) { - val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - val iconOverlayAsset by - collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) - val iconContentDescriptionId by - collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) - val shouldAnimateIconView by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) - val shouldAnimateIconOverlay by - collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) - val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested - verifyIconSize(forceExplicitFlow = forceExplicitFlow) - - kosmos.promptViewModel.showAuthenticated( - modality = testCase.authenticatedModality, - dismissAfterDelay = DELAY - ) - - kosmos.promptViewModel.confirmAuthenticated() + fun shows_authenticated_explicitly_confirmed() { + if (testCase.authenticatedByFace && testCase.confirmationRequested) { + runGenericTest { + val iconAsset by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + val iconOverlayAsset by + collectLastValue(kosmos.promptViewModel.iconViewModel.iconOverlayAsset) + val iconContentDescriptionId by + collectLastValue(kosmos.promptViewModel.iconViewModel.contentDescriptionId) + val shouldAnimateIconView by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconView) + val shouldAnimateIconOverlay by + collectLastValue(kosmos.promptViewModel.iconViewModel.shouldAnimateIconOverlay) + val forceExplicitFlow = testCase.isCoex && testCase.confirmationRequested + verifyIconSize(forceExplicitFlow = forceExplicitFlow) + + kosmos.promptViewModel.showAuthenticated( + modality = testCase.authenticatedModality, + dismissAfterDelay = DELAY + ) - if (testCase.isFaceOnly) { - assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) - assertThat(iconOverlayAsset).isEqualTo(-1) - assertThat(iconContentDescriptionId) - .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed) - assertThat(shouldAnimateIconView).isEqualTo(true) - assertThat(shouldAnimateIconOverlay).isEqualTo(false) - } + kosmos.promptViewModel.confirmAuthenticated() - // explicit flow because confirmation requested - if (testCase.isCoex) { - // TODO: Update when SFPS co-ex is implemented - if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { - assertThat(iconAsset) - .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie) + if (testCase.isFaceOnly) { + assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark) assertThat(iconOverlayAsset).isEqualTo(-1) assertThat(iconContentDescriptionId) - .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed) assertThat(shouldAnimateIconView).isEqualTo(true) assertThat(shouldAnimateIconOverlay).isEqualTo(false) } + + // explicit flow because confirmation requested + if (testCase.isCoex) { + // TODO: Update when SFPS co-ex is implemented + if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) { + assertThat(iconAsset) + .isEqualTo( + R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie + ) + assertThat(iconOverlayAsset).isEqualTo(-1) + assertThat(iconContentDescriptionId) + .isEqualTo(R.string.fingerprint_dialog_touch_sensor) + assertThat(shouldAnimateIconView).isEqualTo(true) + assertThat(shouldAnimateIconOverlay).isEqualTo(false) + } + } } } } @@ -700,58 +764,68 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun sfpsIconUpdates_onFoldConfigurationChanged() = runGenericTest { + fun sfpsIconUpdates_onFoldConfigurationChanged() { if ( testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON && !testCase.isInRearDisplayMode ) { - val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + runGenericTest { + val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getFoldedConfiguration()) - val foldedIcon = currentIcon + kosmos.promptViewModel.iconViewModel.onConfigurationChanged( + getFoldedConfiguration() + ) + val foldedIcon = currentIcon - kosmos.promptViewModel.iconViewModel.onConfigurationChanged(getUnfoldedConfiguration()) - val unfoldedIcon = currentIcon + kosmos.promptViewModel.iconViewModel.onConfigurationChanged( + getUnfoldedConfiguration() + ) + val unfoldedIcon = currentIcon - assertThat(foldedIcon).isNotEqualTo(unfoldedIcon) + assertThat(foldedIcon).isNotEqualTo(unfoldedIcon) + } } } @Test - fun sfpsIconUpdates_onRotation() = runGenericTest { + fun sfpsIconUpdates_onRotation() { if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { - val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + runGenericTest { + val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) - val iconRotation0 = currentIcon + kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0) + val iconRotation0 = currentIcon - kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) - val iconRotation90 = currentIcon + kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + val iconRotation90 = currentIcon - kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) - val iconRotation180 = currentIcon + kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + val iconRotation180 = currentIcon - kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) - val iconRotation270 = currentIcon + kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270) + val iconRotation270 = currentIcon - assertThat(iconRotation0).isEqualTo(iconRotation180) - assertThat(iconRotation0).isNotEqualTo(iconRotation90) - assertThat(iconRotation0).isNotEqualTo(iconRotation270) + assertThat(iconRotation0).isEqualTo(iconRotation180) + assertThat(iconRotation0).isNotEqualTo(iconRotation90) + assertThat(iconRotation0).isNotEqualTo(iconRotation270) + } } } @Test - fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest { + fun sfpsIconUpdates_onRearDisplayMode() { if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) { - val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) + runGenericTest { + val currentIcon by collectLastValue(kosmos.promptViewModel.iconViewModel.iconAsset) - kosmos.displayStateRepository.setIsInRearDisplayMode(false) - val iconNotRearDisplayMode = currentIcon + kosmos.displayStateRepository.setIsInRearDisplayMode(false) + val iconNotRearDisplayMode = currentIcon - kosmos.displayStateRepository.setIsInRearDisplayMode(true) - val iconRearDisplayMode = currentIcon + kosmos.displayStateRepository.setIsInRearDisplayMode(true) + val iconRearDisplayMode = currentIcon - assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode) + assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt new file mode 100644 index 000000000000..4d112e93013a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/CurrentAppShortcutsSourceTest.kt @@ -0,0 +1,93 @@ +/* + * 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.keyboard.shortcut.data.source + +import android.view.KeyboardShortcutGroup +import android.view.WindowManager +import android.view.mockWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CurrentAppShortcutsSourceTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val mockWindowManager = kosmos.mockWindowManager + private val source = CurrentAppShortcutsSource(mockWindowManager) + + private var shortcutGroups: List<KeyboardShortcutGroup>? = null + + @Before + fun setUp() { + whenever(mockWindowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer { + val receiver = it.arguments[0] as WindowManager.KeyboardShortcutsReceiver + receiver.onKeyboardShortcutsReceived(shortcutGroups) + } + } + + @Test + fun shortcutGroups_wmReturnsNullList_returnsEmptyList() = + testScope.runTest { + shortcutGroups = null + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).isEmpty() + } + + @Test + fun shortcutGroups_wmReturnsEmptyList_returnsEmptyList() = + testScope.runTest { + shortcutGroups = emptyList() + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).isEmpty() + } + + @Test + fun shortcutGroups_wmReturnsGroups_returnsWmGroups() = + testScope.runTest { + shortcutGroups = + listOf( + KeyboardShortcutGroup("wm ime group 1"), + KeyboardShortcutGroup("wm ime group 2"), + KeyboardShortcutGroup("wm ime group 3"), + ) + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).hasSize(3) + } + + companion object { + private const val TEST_DEVICE_ID = 9876 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt new file mode 100644 index 000000000000..715d907b7372 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/InputShortcutsSourceTest.kt @@ -0,0 +1,94 @@ +/* + * 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.keyboard.shortcut.data.source + +import android.content.res.mainResources +import android.view.KeyboardShortcutGroup +import android.view.WindowManager.KeyboardShortcutsReceiver +import android.view.mockWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class InputShortcutsSourceTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val mockWindowManager = kosmos.mockWindowManager + private val source = InputShortcutsSource(kosmos.mainResources, mockWindowManager) + + private var wmImeShortcutGroups: List<KeyboardShortcutGroup>? = null + + @Before + fun setUp() { + whenever(mockWindowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer { + val receiver = it.arguments[0] as KeyboardShortcutsReceiver + receiver.onKeyboardShortcutsReceived(wmImeShortcutGroups) + } + } + + @Test + fun shortcutGroups_wmReturnsNullList_returnsSingleGroup() = + testScope.runTest { + wmImeShortcutGroups = null + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).hasSize(1) + } + + @Test + fun shortcutGroups_wmReturnsEmptyList_returnsSingleGroup() = + testScope.runTest { + wmImeShortcutGroups = emptyList() + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).hasSize(1) + } + + @Test + fun shortcutGroups_wmReturnsGroups_returnsWmGroupsPlusOne() = + testScope.runTest { + wmImeShortcutGroups = + listOf( + KeyboardShortcutGroup("wm ime group 1"), + KeyboardShortcutGroup("wm ime group 2"), + KeyboardShortcutGroup("wm ime group 3"), + ) + + val groups = source.shortcutGroups(TEST_DEVICE_ID) + + assertThat(groups).hasSize(4) + } + + companion object { + private const val TEST_DEVICE_ID = 1234 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt index d183c7370713..7dd802878674 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt @@ -47,13 +47,7 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { private lateinit var dialog: AlertDialog private val flags = mock<FeatureFlagsClassic>() - private val onStartRecordingClicked = mock<Runnable>() - private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>() - - private val mediaProjectionConfig: MediaProjectionConfig = - MediaProjectionConfig.createConfigForDefaultDisplay() - private val appName: String = "testApp" - private val hostUid: Int = 12345 + private val appName = "Test App" private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen @@ -73,16 +67,33 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { } @Test - fun showDialog_forceShowPartialScreenShareFalse() { - // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and - // overrideDisableSingleAppOption = false - val overrideDisableSingleAppOption = false - setUpAndShowDialog(overrideDisableSingleAppOption) + fun showDefaultDialog() { + setUpAndShowDialog() val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) val secondOptionText = spinner.adapter .getDropDownView(1, null, spinner) + .findViewById<TextView>(android.R.id.text1) + ?.text + + // check that the first option is single app and enabled + assertEquals(context.getString(resIdSingleApp), spinner.selectedItem) + + // check that the second option is full screen and enabled + assertEquals(context.getString(resIdFullScreen), secondOptionText) + } + + @Test + fun showDialog_disableSingleApp() { + setUpAndShowDialog( + mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay() + ) + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) + val secondOptionWarningText = + spinner.adapter + .getDropDownView(1, null, spinner) .findViewById<TextView>(android.R.id.text2) ?.text @@ -90,15 +101,15 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { assertEquals(context.getString(resIdFullScreen), spinner.selectedItem) // check that the second option is single app and disabled - assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText) + assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText) } @Test - fun showDialog_forceShowPartialScreenShareTrue() { - // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and - // overrideDisableSingleAppOption = true - val overrideDisableSingleAppOption = true - setUpAndShowDialog(overrideDisableSingleAppOption) + fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() { + setUpAndShowDialog( + mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(), + overrideDisableSingleAppOption = true + ) val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) val secondOptionText = @@ -114,17 +125,43 @@ class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { assertEquals(context.getString(resIdFullScreen), secondOptionText) } - private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) { + @Test + fun showDialog_disableSingleApp_hasCastingCapabilities() { + setUpAndShowDialog( + mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(), + hasCastingCapabilities = true + ) + + val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options) + val secondOptionWarningText = + spinner.adapter + .getDropDownView(1, null, spinner) + .findViewById<TextView>(android.R.id.text2) + ?.text + + // check that the first option is full screen and enabled + assertEquals(context.getString(resIdFullScreen), spinner.selectedItem) + + // check that the second option is single app and disabled + assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionWarningText) + } + + private fun setUpAndShowDialog( + mediaProjectionConfig: MediaProjectionConfig? = null, + overrideDisableSingleAppOption: Boolean = false, + hasCastingCapabilities: Boolean = false, + ) { val delegate = MediaProjectionPermissionDialogDelegate( context, mediaProjectionConfig, - {}, - onStartRecordingClicked, + onStartRecordingClicked = {}, + onCancelClicked = {}, + hasCastingCapabilities, appName, overrideDisableSingleAppOption, - hostUid, - mediaProjectionMetricsLogger + hostUid = 12345, + mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>() ) dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index e46416c1b321..ebab04989590 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -75,6 +75,7 @@ import com.android.systemui.qs.QsEventLoggerFake; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.StatusBarState; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -146,6 +147,12 @@ public class QSTileImplTest extends SysuiTestCase { mTile.setTileSpec(SPEC); } + @After + public void destroyTile() { + mTile.destroy(); + mTestableLooper.processAllMessages(); + } + @Test public void testClick_Metrics() { mTile.click(null /* expandable */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt index 25dd9fedba7c..24e8b1886438 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt @@ -27,7 +27,6 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -44,7 +43,7 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() { private val imageExporter = mock<ImageExporter>() private val smartActions = mock<ScreenshotSmartActions>() private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>() - private val saveImageData = SaveImageInBackgroundData() + private val saveImageData = SaveImageInBackgroundTask.SaveImageInBackgroundData() private val testScreenshotId: String = "testScreenshotId" private val testBitmap = mock<Bitmap>() private val testUser = UserHandle.getUserHandleForUid(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 86c9ab789429..967df39c9269 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -48,7 +48,12 @@ import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -67,13 +72,13 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyFloat -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi @RunWith(AndroidTestingRunner::class) @@ -87,11 +92,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() } - @Mock private lateinit var communalViewModel: CommunalViewModel - @Mock private lateinit var powerManager: PowerManager - @Mock private lateinit var touchMonitor: TouchMonitor - @Mock private lateinit var communalColors: CommunalColors - @Mock private lateinit var communalContent: CommunalContent + private var communalViewModel = mock<CommunalViewModel>() + private var powerManager = mock<PowerManager>() + private var touchMonitor = mock<TouchMonitor>() + private var communalColors = mock<CommunalColors>() + private var communalContent = mock<CommunalContent>() private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory private lateinit var parentView: FrameLayout @@ -103,8 +108,6 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - communalRepository = kosmos.fakeCommunalSceneRepository ambientTouchComponentFactory = @@ -124,6 +127,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalInteractor, communalViewModel, keyguardInteractor, + kosmos.keyguardTransitionInteractor, shadeInteractor, powerManager, communalColors, @@ -167,6 +171,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalInteractor, communalViewModel, keyguardInteractor, + kosmos.keyguardTransitionInteractor, shadeInteractor, powerManager, communalColors, @@ -192,6 +197,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalInteractor, communalViewModel, keyguardInteractor, + kosmos.keyguardTransitionInteractor, shadeInteractor, powerManager, communalColors, @@ -212,6 +218,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalInteractor, communalViewModel, keyguardInteractor, + kosmos.keyguardTransitionInteractor, shadeInteractor, powerManager, communalColors, @@ -235,12 +242,15 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test - fun lifecycle_resumedAfterCommunalShows() { - // Communal is open. - goToScene(CommunalScenes.Communal) + fun lifecycle_resumedAfterCommunalShows() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) - assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED) - } + assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED) + } + } @Test fun lifecycle_startedAfterCommunalCloses() = @@ -289,6 +299,43 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test + fun lifecycle_startedWhenEditActivityShowing() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) + + // Edit activity is showing. + communalInteractor.setEditActivityShowing(true) + testableLooper.processAllMessages() + + assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) + } + } + + @Test + fun lifecycle_startedWhenEditModeTransitionStarted() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) + + // Leaving edit mode to return to the hub. + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.GLANCEABLE_HUB, + value = 1.0f, + transitionState = TransitionState.RUNNING + ) + ) + testableLooper.processAllMessages() + + assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) + } + } + + @Test fun lifecycle_createdAfterDisposeView() { // Container view disposed. underTest.disposeView() @@ -486,10 +533,10 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { testScope.runTest { // Communal is closed. goToScene(CommunalScenes.Blank) - `when`( + whenever( notificationStackScrollLayoutController.isBelowLastNotification( - anyFloat(), - anyFloat() + any(), + any() ) ) .thenReturn(false) @@ -497,6 +544,62 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @Test + fun onTouchEvent_hubOpen_touchesDispatched() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) + + // Touch event is sent to the container view. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() + verify(containerView).onTouchEvent(any()) + } + } + + @Test + fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) + + // Transitioning to or from edit mode. + communalInteractor.setEditActivityShowing(true) + testableLooper.processAllMessages() + + // onTouchEvent returns true to consume the touch, but it is not sent to the + // container view. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() + verify(containerView, never()).onTouchEvent(any()) + } + } + + @Test + fun onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatched() = + with(kosmos) { + testScope.runTest { + // Communal is open. + goToScene(CommunalScenes.Communal) + + // Leaving edit mode to return to the hub. + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.GLANCEABLE_HUB, + value = 1.0f, + transitionState = TransitionState.RUNNING + ) + ) + testableLooper.processAllMessages() + + // onTouchEvent returns true to consume the touch, but it is not sent to the + // container view. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() + verify(containerView, never()).onTouchEvent(any()) + } + } + private fun initAndAttachContainerView() { val mockInsets = mock<WindowInsets> { @@ -515,8 +618,21 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { testableLooper.processAllMessages() } - private fun goToScene(scene: SceneKey) { + private suspend fun goToScene(scene: SceneKey) { communalRepository.changeScene(scene) + if (scene == CommunalScenes.Communal) { + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GLANCEABLE_HUB, + kosmos.testScope + ) + } else { + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.LOCKSCREEN, + kosmos.testScope + ) + } testableLooper.processAllMessages() } 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 c9710370c2c2..b7995953b327 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 @@ -91,6 +91,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; @@ -1196,7 +1197,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) + @EnableFlags(NotificationThrottleHun.FLAG_NAME) public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() { // GIVEN NSSL is ready for HUN animations Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt new file mode 100644 index 000000000000..2050437da38a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSceneTransitionRepositoryKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.communal.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.communalSceneTransitionRepository: CommunalSceneTransitionRepository by + Kosmos.Fixture { CommunalSceneTransitionRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt index d280be216f5e..82454817ecbb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt @@ -6,7 +6,6 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.shared.model.CommunalScenes import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -25,11 +24,10 @@ class FakeCommunalSceneRepository( ) : CommunalSceneRepository { override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = - snapToScene(toScene, 0) + snapToScene(toScene) - override fun snapToScene(toScene: SceneKey, delayMillis: Long) { + override fun snapToScene(toScene: SceneKey) { applicationScope.launch { - delay(delayMillis) currentScene.value = toScene _transitionState.value = flowOf(ObservableTransitionState.Idle(toScene)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..e6e59e1a523e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * 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.communal.domain.interactor + +import com.android.systemui.communal.data.repository.communalSceneTransitionRepository +import com.android.systemui.keyguard.domain.interactor.internalKeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope + +val Kosmos.communalSceneTransitionInteractor: CommunalSceneTransitionInteractor by + Kosmos.Fixture { + CommunalSceneTransitionInteractor( + applicationScope = applicationCoroutineScope, + transitionInteractor = keyguardTransitionInteractor, + internalTransitionInteractor = internalKeyguardTransitionInteractor, + settingsInteractor = communalSettingsInteractor, + sceneInteractor = communalSceneInteractor, + repository = communalSceneTransitionRepository, + keyguardInteractor = keyguardInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt index 079852a641c2..494f08b88345 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -38,5 +39,6 @@ var Kosmos.fromGlanceableHubTransitionInteractor by transitionInteractor = keyguardTransitionInteractor, powerInteractor = powerInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt index c2169456eac5..7827655806d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope @@ -37,5 +38,6 @@ val Kosmos.fromOccludedTransitionInteractor by powerInteractor = powerInteractor, communalInteractor = communalInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index b918d80fc63d..30c743e508b6 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -161,6 +161,7 @@ import android.view.inputmethod.EditorInfo; import com.android.internal.R; import com.android.internal.accessibility.AccessibilityShortcutController; +import com.android.internal.accessibility.AccessibilityShortcutController.ExtraDimFrameworkFeatureInfo; import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo; import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo; import com.android.internal.accessibility.common.ShortcutConstants; @@ -3910,6 +3911,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); return; } + // In case user assigned an accessibility framework feature to the given shortcut. if (performAccessibilityFrameworkFeature(displayId, targetComponentName, shortcutType)) { return; @@ -3933,6 +3935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!frameworkFeatureMap.containsKey(assignedTarget)) { return false; } + final int userId; + synchronized (mLock) { + userId = mCurrentUserId; + } final FrameworkFeatureInfo featureInfo = frameworkFeatureMap.get(assignedTarget); final SettingStringHelper setting = new SettingStringHelper(mContext.getContentResolver(), featureInfo.getSettingKey(), mCurrentUserId); @@ -3944,6 +3950,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } + if (featureInfo instanceof ExtraDimFrameworkFeatureInfo) { + boolean serviceEnabled = + ((ExtraDimFrameworkFeatureInfo) featureInfo) + .activateShortcut(mContext, userId); + logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType, + serviceEnabled); + return true; + } + // Assuming that the default state will be to have the feature off if (!TextUtils.equals(featureInfo.getSettingOnValue(), setting.read())) { logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType, diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index ad8f5e1481e1..668852b46ea2 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -31,12 +31,14 @@ import android.hardware.display.DisplayManager; import android.metrics.LogMaker; import android.os.UserManager; import android.service.autofill.Dataset; +import android.service.autofill.FillResponse; import android.service.autofill.InternalSanitizer; import android.service.autofill.SaveInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseArray; import android.view.Display; import android.view.View; import android.view.WindowManager; @@ -374,4 +376,50 @@ public final class Helper { private interface ViewNodeFilter { boolean matches(ViewNode node); } + + public static class SaveInfoStats { + public int saveInfoCount; + public int saveDataTypeCount; + + public SaveInfoStats(int saveInfoCount, int saveDataTypeCount) { + this.saveInfoCount = saveInfoCount; + this.saveDataTypeCount = saveDataTypeCount; + } + } + + /** + * Get statistic information of save info given a sparse array of fill responses. + * + * Specifically the statistic includes + * 1. how many save info the current session has. + * 2. How many distinct save data types current session has. + * + * @return SaveInfoStats returns the above two number in a SaveInfoStats object + */ + public static SaveInfoStats getSaveInfoStatsFromFillResponses( + SparseArray<FillResponse> fillResponses) { + if (fillResponses == null) { + if (sVerbose) { + Slog.v(TAG, "getSaveInfoStatsFromFillResponses(): fillResponse sparse array is " + + "null"); + } + return new SaveInfoStats(-1, -1); + } + int numSaveInfos = 0; + int numSaveDataTypes = 0; + ArraySet<Integer> saveDataTypeSeen = new ArraySet<>(); + final int numResponses = fillResponses.size(); + for (int responseNum = 0; responseNum < numResponses; responseNum++) { + final FillResponse response = fillResponses.valueAt(responseNum); + if (response != null && response.getSaveInfo() != null) { + numSaveInfos += 1; + int saveDataType = response.getSaveInfo().getType(); + if (!saveDataTypeSeen.contains(saveDataType)) { + saveDataTypeSeen.add(saveDataType); + numSaveDataTypes += 1; + } + } + } + return new SaveInfoStats(numSaveInfos, numSaveDataTypes); + } } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index a5ec2ba2f267..49ca29745b11 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -854,7 +854,6 @@ public final class PresentationStatsEventLogger { mCallingAppUid, event.mIsCredentialRequest, event.mWebviewRequestedCredential, - event.mFilteredFillabaleViewCount, event.mViewFillableTotalCount, event.mViewFillFailureCount, event.mFocusedId, @@ -868,6 +867,7 @@ public final class PresentationStatsEventLogger { event.mFocusedVirtualAutofillId, event.mFieldFirstLength, event.mFieldLastLength, + event.mFilteredFillabaleViewCount, event.mViewFailedPriorToRefillCount, event.mViewFilledSuccessfullyOnRefillCount, event.mViewFailedOnRefillCount, diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c6ddc16211bc..21df7a5487ef 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -70,6 +70,7 @@ import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATU import static com.android.server.autofill.Helper.containsCharsInOrder; import static com.android.server.autofill.Helper.createSanitizers; import static com.android.server.autofill.Helper.getNumericValue; +import static com.android.server.autofill.Helper.SaveInfoStats; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sVerbose; import static com.android.server.autofill.Helper.toArray; @@ -3203,11 +3204,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return saveInfo == null ? 0 : saveInfo.getFlags(); } - static class SaveInfoStats { - public int saveInfoCount; - public int saveDataTypeCount; - } - /** * Get statistic information of save info in current session. Specifically * 1. how many save info the current session has. @@ -3217,42 +3213,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ @GuardedBy("mLock") private SaveInfoStats getSaveInfoStatsLocked() { - SaveInfoStats retSaveInfoStats = new SaveInfoStats(); - retSaveInfoStats.saveInfoCount = -1; - retSaveInfoStats.saveDataTypeCount = -1; - if (mContexts == null) { if (sVerbose) { Slog.v(TAG, "getSaveInfoStatsLocked(): mContexts is null"); } - } else if (mResponses == null) { - // Happens when the activity / session was finished before the service replied, or - // when the service cannot autofill it (and returned a null response). - if (sVerbose) { - Slog.v(TAG, "getSaveInfoStatsLocked(): mResponses is null"); - } - return retSaveInfoStats; - } else { - int numSaveInfos = 0; - int numSaveDataTypes = 0; - ArraySet<Integer> saveDataTypeSeen = new ArraySet<>(); - final int numResponses = mResponses.size(); - for (int responseNum = 0; responseNum < numResponses; responseNum++) { - final FillResponse response = mResponses.valueAt(responseNum); - if (response != null && response.getSaveInfo() != null) { - numSaveInfos += 1; - int saveDataType = response.getSaveInfo().getType(); - if (!saveDataTypeSeen.contains(saveDataType)) { - saveDataTypeSeen.add(saveDataType); - numSaveDataTypes += 1; - } - } - } - retSaveInfoStats.saveInfoCount = numSaveInfos; - retSaveInfoStats.saveDataTypeCount = numSaveDataTypes; + return new SaveInfoStats(-1, -1); } - - return retSaveInfoStats; + return Helper.getSaveInfoStatsFromFillResponses(mResponses); } /** diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 3d53deb8d2bb..4fc9d55d3e17 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -103,9 +103,10 @@ class CompanionDeviceShellCommand extends ShellCommand { String packageName = getNextArgRequired(); String address = getNextArgRequired(); String deviceProfile = getNextArg(); + boolean selfManaged = getNextBooleanArg(); final MacAddress macAddress = MacAddress.fromString(address); mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress, - deviceProfile, deviceProfile, /* associatedDevice */ null, false, + deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged, /* callback */ null, /* resultReceiver */ null); } break; @@ -462,6 +463,17 @@ class CompanionDeviceShellCommand extends ShellCommand { } } + private boolean getNextBooleanArg() { + String arg = getNextArg(); + if (arg == null || "false".equalsIgnoreCase(arg)) { + return false; + } else if ("true".equalsIgnoreCase(arg)) { + return Boolean.parseBoolean(arg); + } else { + throw new IllegalArgumentException("Expected a boolean argument but was: " + arg); + } + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -470,7 +482,7 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" Print this help text."); pw.println(" list USER_ID"); pw.println(" List all Associations for a user."); - pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]"); + pw.println(" associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE] [SELF_MANAGED]"); pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 69ee8fc831f4..cf0befaab98d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16961,16 +16961,24 @@ public class ActivityManagerService extends IActivityManager.Stub int userId = UserHandle.getCallingUserId(); - if (UserManager.isVisibleBackgroundUsersEnabled() && userId != getCurrentUserId()) { - // The check is added mainly for auto devices. On auto devices, it is possible that - // multiple users are visible simultaneously using visible background users. - // In such cases, it is desired that only the current user (not the visible background - // user) can change the locale and other persistent settings of the device. - Slog.w(TAG, "Only current user is allowed to update persistent configuration if " - + "visible background users are enabled. Current User" + getCurrentUserId() - + ". Calling User: " + userId); - throw new SecurityException("Only current user is allowed to update persistent " - + "configuration."); + if (UserManager.isVisibleBackgroundUsersEnabled()) { + final long origId = Binder.clearCallingIdentity(); + try { + if (userId != getCurrentUserId()) { + // The check is added mainly for auto devices. On auto devices, it is + // possible that multiple users are visible simultaneously using visible + // background users. In such cases, it is desired that only the current user + // (not the visible background user) can change the locale and other persistent + // settings of the device. + Slog.w(TAG, "Only current user is allowed to update persistent configuration " + + "if visible background users are enabled. Current User" + + getCurrentUserId() + ". Calling User: " + userId); + throw new SecurityException("Only current user is allowed to update persistent " + + "configuration."); + } + } finally { + Binder.restoreCallingIdentity(origId); + } } mActivityTaskManager.updatePersistentConfiguration(values, userId); diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 061bcd740f6b..ee7033e4a437 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -47,6 +47,7 @@ import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; @@ -63,6 +64,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.server.LocalServices; import com.android.server.PackageWatchdog; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.UserManagerService; import com.android.server.usage.AppStandbyInternal; import com.android.server.wm.WindowProcessController; @@ -868,9 +871,6 @@ class AppErrors { private boolean handleAppCrashLSPB(ProcessRecord app, String reason, String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) { final long now = SystemClock.uptimeMillis(); - final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.ANR_SHOW_BACKGROUND, 0, - mService.mUserController.getCurrentUserId()) != 0; Long crashTime; Long crashTimePersistent; @@ -881,6 +881,8 @@ class AppErrors { final boolean persistent = app.isPersistent(); final WindowProcessController proc = app.getWindowProcessController(); final ProcessErrorStateRecord errState = app.mErrorState; + final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ANR_SHOW_BACKGROUND, 0, getVisibleUserId(userId)) != 0; if (!app.isolated) { crashTime = mProcessCrashTimes.get(processName, uid); @@ -1000,9 +1002,6 @@ class AppErrors { void handleShowAppErrorUi(Message msg) { AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj; - boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.ANR_SHOW_BACKGROUND, 0, - mService.mUserController.getCurrentUserId()) != 0; final int userId; synchronized (mProcLock) { @@ -1027,7 +1026,11 @@ class AppErrors { for (int profileId : mService.mUserController.getCurrentProfileIds()) { isBackground &= (userId != profileId); } - if (isBackground && !showBackground) { + int visibleUserId = getVisibleUserId(userId); + boolean isVisibleUser = isVisibleBackgroundUser(visibleUserId); + boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0; + if (isBackground && !showBackground && !isVisibleUser) { Slog.w(TAG, "Skipping crash dialog of " + proc + ": background"); if (res != null) { res.set(AppErrorDialog.BACKGROUND_USER); @@ -1054,7 +1057,7 @@ class AppErrors { final long now = SystemClock.uptimeMillis(); final boolean shouldThottle = crashShowErrorTime != null && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL; - if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground) + if ((mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground) && !crashSilenced && !shouldThottle && (showFirstCrash || showFirstCrashDevOption || data.repeating)) { Slog.i(TAG, "Showing crash dialog for package " + packageName + " u" + userId); @@ -1103,10 +1106,10 @@ class AppErrors { return; } + int visibleUserId = getVisibleUserId(proc.userId); boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.ANR_SHOW_BACKGROUND, 0, - mService.mUserController.getCurrentUserId()) != 0; - if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) { + Settings.Secure.ANR_SHOW_BACKGROUND, 0, visibleUserId) != 0; + if (mService.mAtmInternal.canShowErrorDialogs(visibleUserId) || showBackground) { AnrController anrController = errState.getDialogController().getAnrController(); if (anrController == null) { errState.getDialogController().showAnrDialogs(data); @@ -1163,6 +1166,43 @@ class AppErrors { } /** + * Returns the user ID of the visible user associated with the error occurrence. + * + * <p>For most cases it will return the current foreground user ID, but on devices that + * {@link UserManager#isVisibleBackgroundUsersEnabled() support visible background users}, + * it will return the given app user ID passed as parameter. + * + * @param appUserId The user ID of the app where the error occurred. + * @return The ID of the visible user associated with the error. + */ + private int getVisibleUserId(int appUserId) { + if (!UserManager.isVisibleBackgroundUsersEnabled()) { + return mService.mUserController.getCurrentUserId(); + } + return appUserId; + } + + /** + * Checks if the given user is a visible background user, which is a full, background user + * assigned to secondary displays on the devices that have + * {@link UserManager#isVisibleBackgroundUsersEnabled() + * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on + * automotive builds, using the display associated with their seats). + * + * @see UserManager#isUserVisible() + */ + private boolean isVisibleBackgroundUser(int userId) { + if (!UserManager.isVisibleBackgroundUsersEnabled()) { + return false; + } + boolean isForeground = mService.mUserController.getCurrentUserId() == userId; + boolean isProfile = UserManagerService.getInstance().isProfile(userId); + boolean isVisible = LocalServices.getService(UserManagerInternal.class) + .isUserVisible(userId); + return isVisible && !isForeground && !isProfile; + } + + /** * Information about a process that is currently marked as bad. */ static final class BadProcessInfo { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 67985efcd7bc..8df4e7702be8 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -102,6 +102,7 @@ import android.util.StatsEvent; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.Clock; @@ -671,6 +672,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, Flags.streamlinedMiscBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_GNSS, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_GNSS, + Flags.streamlinedMiscBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA, Flags.streamlinedMiscBatteryStats()); mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( @@ -1097,18 +1104,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub final StatsManager statsManager = mContext.getSystemService(StatsManager.class); final StatsPullAtomCallbackImpl pullAtomCallback = new StatsPullAtomCallbackImpl(); - statsManager.setPullAtomCallback( - FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET, - null, // use default PullAtomMetadata values - DIRECT_EXECUTOR, pullAtomCallback); - statsManager.setPullAtomCallback( - FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL, - null, // use default PullAtomMetadata values - DIRECT_EXECUTOR, pullAtomCallback); - statsManager.setPullAtomCallback( - FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET, - null, // use default PullAtomMetadata values - DIRECT_EXECUTOR, pullAtomCallback); + if (!Flags.disableCompositeBatteryUsageStatsAtoms()) { + statsManager.setPullAtomCallback( + FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, pullAtomCallback); + statsManager.setPullAtomCallback( + FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, pullAtomCallback); + statsManager.setPullAtomCallback( + FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, pullAtomCallback); + } if (Flags.addBatteryUsageStatsSliceAtom()) { statsManager.setPullAtomCallback( FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, @@ -1125,6 +1134,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub final BatteryUsageStats bus; switch (atomTag) { case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: { + if (Flags.disableCompositeBatteryUsageStatsAtoms()) { + return StatsManager.PULL_SKIP; + } + @SuppressLint("MissingPermission") final double minConsumedPowerThreshold = DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE, @@ -1141,6 +1154,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub break; } case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL: + if (Flags.disableCompositeBatteryUsageStatsAtoms()) { + return StatsManager.PULL_SKIP; + } + final BatteryUsageStatsQuery queryPowerProfile = new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) @@ -1152,6 +1169,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0); break; case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: { + if (Flags.disableCompositeBatteryUsageStatsAtoms()) { + return StatsManager.PULL_SKIP; + } + final long sessionStart = getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); final long sessionEnd; @@ -1191,7 +1212,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMinConsumedPowerThreshold(minConsumedPowerThreshold) .build(); bus = getBatteryUsageStats(List.of(query)).get(0); - return StatsPerUidLogger.logStats(bus, data); + return new StatsPerUidLogger(new FrameworkStatsLogger()).logStats(bus, data); } default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -1204,7 +1225,35 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } - private static class StatsPerUidLogger { + public static class FrameworkStatsLogger { + /** + * Wrapper for the FrameworkStatsLog.buildStatsEvent method that makes it easier + * for mocking. + */ + @VisibleForTesting + public StatsEvent buildStatsEvent(long sessionStartTs, long sessionEndTs, + long sessionDuration, int sessionDischargePercentage, long sessionDischargeDuration, + int uid, @BatteryConsumer.ProcessState int processState, long timeInStateMillis, + String powerComponentName, float totalConsumedPowerMah, float powerComponentMah, + long powerComponentDurationMillis) { + return FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, + sessionStartTs, + sessionEndTs, + sessionDuration, + sessionDischargePercentage, + sessionDischargeDuration, + uid, + processState, + timeInStateMillis, + powerComponentName, + totalConsumedPowerMah, + powerComponentMah, + powerComponentDurationMillis); + } + } + + public static class StatsPerUidLogger { private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000; @@ -1224,7 +1273,18 @@ public final class BatteryStatsService extends IBatteryStats.Stub long dischargeDuration) {} ; - static int logStats(BatteryUsageStats bus, List<StatsEvent> data) { + private final FrameworkStatsLogger mFrameworkStatsLogger; + + public StatsPerUidLogger(FrameworkStatsLogger frameworkStatsLogger) { + mFrameworkStatsLogger = frameworkStatsLogger; + } + + /** + * Generates StatsEvents for the supplied battery usage stats and adds them to + * the supplied list. + */ + @VisibleForTesting + public int logStats(BatteryUsageStats bus, List<StatsEvent> data) { final SessionInfo sessionInfo = new SessionInfo( bus.getStatsStartTimestamp(), @@ -1340,7 +1400,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub return StatsManager.PULL_SUCCESS; } - private static boolean addStatsForPredefinedComponent( + private boolean addStatsForPredefinedComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1380,7 +1440,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub powerComponentDurationMillis); } - private static boolean addStatsForCustomComponent( + private boolean addStatsForCustomComponent( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1422,7 +1482,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub * Returns true on success and false if reached max atoms capacity and no more atoms should * be added */ - private static boolean addStatsAtom( + private boolean addStatsAtom( List<StatsEvent> data, SessionInfo sessionInfo, int uid, @@ -1432,9 +1492,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub float totalConsumedPowerMah, float powerComponentMah, long powerComponentDurationMillis) { - data.add( - FrameworkStatsLog.buildStatsEvent( - FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID, + data.add(mFrameworkStatsLogger.buildStatsEvent( sessionInfo.startTs(), sessionInfo.endTs(), sessionInfo.duration(), @@ -3214,6 +3272,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMaxStatsAgeMs(0) .includeProcessStateData() .includePowerModels(); + if (Flags.batteryUsageStatsByPowerAndScreenState()) { + builder.includeScreenStateData().includePowerStateData(); + } if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { builder.powerProfileModeledOnly(); } @@ -3232,7 +3293,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub if (proto) { batteryUsageStats.dumpToProto(fd); } else { - batteryUsageStats.dump(pw, ""); + batteryUsageStats.dump(pw, " "); } } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index f6df60f736e8..e4c65bd2147d 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -269,7 +269,8 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { - new GameManagerShellCommand().exec(this, in, out, err, args, callback, result); + new GameManagerShellCommand(mPackageManager).exec(this, in, out, err, args, callback, + result); } @Override diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java index ab57c4fe837e..d3b4312bf4a3 100644 --- a/services/core/java/com/android/server/app/GameManagerShellCommand.java +++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java @@ -16,10 +16,13 @@ package com.android.server.app; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.GameManager; import android.app.IGameManagerService; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; @@ -47,7 +50,10 @@ public class GameManagerShellCommand extends ShellCommand { private static final String UNSUPPORTED_MODE_NUM = String.valueOf( GameManager.GAME_MODE_UNSUPPORTED); - public GameManagerShellCommand() { + private PackageManager mPackageManager; + + public GameManagerShellCommand(@NonNull PackageManager packageManager) { + mPackageManager = packageManager; } @Override @@ -91,9 +97,29 @@ public class GameManagerShellCommand extends ShellCommand { return -1; } + private boolean isPackageGame(String packageName, int userId, PrintWriter pw) { + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + boolean isGame = applicationInfo.category == ApplicationInfo.CATEGORY_GAME; + if (!isGame) { + pw.println("Package " + packageName + " is not of game type, to use the game " + + "mode commands, it must specify game category in the manifest as " + + "android:appCategory=\"game\""); + } + return isGame; + } catch (PackageManager.NameNotFoundException e) { + pw.println("Package " + packageName + " is not found for user " + userId); + return false; + } + } + private int runListGameModes(PrintWriter pw) throws ServiceNotFoundException, RemoteException { final String packageName = getNextArgRequired(); final int userId = ActivityManager.getCurrentUser(); + if (!isPackageGame(packageName, userId, pw)) { + return -1; + } final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); final String currentMode = gameModeIntToString( @@ -110,12 +136,15 @@ public class GameManagerShellCommand extends ShellCommand { private int runListGameModeConfigs(PrintWriter pw) throws ServiceNotFoundException, RemoteException { final String packageName = getNextArgRequired(); - + final int userId = ActivityManager.getCurrentUser(); + if (!isPackageGame(packageName, userId, pw)) { + return -1; + } final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); final String listStr = gameManagerService.getInterventionList(packageName, - ActivityManager.getCurrentUser()); + userId); if (listStr == null) { pw.println("No interventions found for " + packageName); @@ -131,15 +160,17 @@ public class GameManagerShellCommand extends ShellCommand { if (option != null && option.equals("--user")) { userIdStr = getNextArgRequired(); } - final String gameMode = getNextArgRequired(); final String packageName = getNextArgRequired(); + int userId = userIdStr != null ? Integer.parseInt(userIdStr) + : ActivityManager.getCurrentUser(); + if (!isPackageGame(packageName, userId, pw)) { + return -1; + } final IGameManagerService service = IGameManagerService.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.GAME_SERVICE)); boolean batteryModeSupported = false; boolean perfModeSupported = false; - int userId = userIdStr != null ? Integer.parseInt(userIdStr) - : ActivityManager.getCurrentUser(); int[] modes = service.getAvailableGameModes(packageName, userId); for (int mode : modes) { if (mode == GameManager.GAME_MODE_PERFORMANCE) { @@ -262,6 +293,9 @@ public class GameManagerShellCommand extends ShellCommand { int userId = userIdStr != null ? Integer.parseInt(userIdStr) : ActivityManager.getCurrentUser(); + if (!isPackageGame(packageName, userId, pw)) { + return -1; + } final GameManagerService gameManagerService = (GameManagerService) ServiceManager.getService(Context.GAME_SERVICE); @@ -308,13 +342,14 @@ public class GameManagerShellCommand extends ShellCommand { } final String packageName = getNextArgRequired(); - - final GameManagerService gameManagerService = (GameManagerService) - ServiceManager.getService(Context.GAME_SERVICE); - int userId = userIdStr != null ? Integer.parseInt(userIdStr) : ActivityManager.getCurrentUser(); + if (!isPackageGame(packageName, userId, pw)) { + return -1; + } + final GameManagerService gameManagerService = (GameManagerService) + ServiceManager.getService(Context.GAME_SERVICE); if (gameMode == null) { gameManagerService.resetGameModeConfigOverride(packageName, userId, -1); return 0; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ed22b4ce7827..fe3bbb094284 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -9953,9 +9953,9 @@ public class AudioService extends IAudioService.Stub int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; synchronized (mCachedAbsVolDrivingStreamsLock) { mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> { - if (stream != null && !mAvrcpAbsVolSupported) { + if (!mAvrcpAbsVolSupported) { mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/ - "", /*enabled*/false, AudioSystem.DEVICE_NONE); + "", /*enabled*/false, AudioSystem.STREAM_DEFAULT); return null; } // For A2DP and AVRCP we need to set the driving stream based on the diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 8e8a0378c111..8ec835bae0bf 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE; @@ -41,6 +42,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.BiometricStateListener; import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -54,8 +56,12 @@ import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.camera2.CameraManager; +import android.hardware.face.FaceManager; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; import android.hardware.security.keymint.HardwareAuthenticatorType; import android.net.Uri; import android.os.Binder; @@ -234,6 +240,8 @@ public class BiometricService extends SystemService { private static final boolean DEFAULT_APP_ENABLED = true; private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false; private static final boolean DEFAULT_MANDATORY_BIOMETRICS_STATUS = false; + private static final boolean DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS = + true; // Some devices that shipped before S already have face-specific settings. Instead of // migrating, which is complicated, let's just keep using the existing settings. @@ -256,6 +264,8 @@ public class BiometricService extends SystemService { Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED); private final Uri MANDATORY_BIOMETRICS_ENABLED = Settings.Secure.getUriFor(Settings.Secure.MANDATORY_BIOMETRICS); + private final Uri MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED = Settings.Secure.getUriFor( + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED); private final ContentResolver mContentResolver; private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks; @@ -264,6 +274,12 @@ public class BiometricService extends SystemService { private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>(); private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>(); private final Map<Integer, Boolean> mMandatoryBiometricsEnabled = new HashMap<>(); + private final Map<Integer, Boolean> mMandatoryBiometricsRequirementsSatisfied = + new HashMap<>(); + private final Map<Integer, Boolean> mFingerprintEnrolledForUser = + new HashMap<>(); + private final Map<Integer, Boolean> mFaceEnrolledForUser = + new HashMap<>(); /** * Creates a content observer. @@ -288,7 +304,13 @@ public class BiometricService extends SystemService { mMandatoryBiometricsEnabled.put(context.getUserId(), Settings.Secure.getIntForUser( mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS, DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0, context.getUserId()) != 0); + mMandatoryBiometricsRequirementsSatisfied.put(context.getUserId(), + Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0, + context.getUserId()) != 0); + addBiometricListenersForMandatoryBiometrics(context); updateContentObserver(); } @@ -322,6 +344,10 @@ public class BiometricService extends SystemService { false /* notifyForDescendants */, this /* observer */, UserHandle.USER_ALL); + mContentResolver.registerContentObserver(MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + false /* notifyForDescendants */, + this /* observer */, + UserHandle.USER_ALL); } @Override @@ -370,6 +396,13 @@ public class BiometricService extends SystemService { Settings.Secure.MANDATORY_BIOMETRICS, DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0 /* default */, userId) != 0); + } else if (MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED.equals(uri)) { + mMandatoryBiometricsRequirementsSatisfied.put(userId, Settings.Secure.getIntForUser( + mContentResolver, + Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS + ? 1 : 0 /* default */, + userId) != 0); } } @@ -411,9 +444,15 @@ public class BiometricService extends SystemService { } } - public boolean getMandatoryBiometricsEnabledForUser(int userId) { + public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) { return mMandatoryBiometricsEnabled.getOrDefault(userId, - DEFAULT_MANDATORY_BIOMETRICS_STATUS); + DEFAULT_MANDATORY_BIOMETRICS_STATUS) + && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId, + DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS) + && mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED) + && getEnabledForApps(userId) + && (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */) + || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)); } void notifyEnabledOnKeyguardCallbacks(int userId) { @@ -424,6 +463,79 @@ public class BiometricService extends SystemService { userId); } } + + private void addBiometricListenersForMandatoryBiometrics(Context context) { + final FingerprintManager fingerprintManager = context.getSystemService( + FingerprintManager.class); + final FaceManager faceManager = context.getSystemService(FaceManager.class); + if (fingerprintManager != null) { + fingerprintManager.addAuthenticatorsRegisteredCallback( + new IFingerprintAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FingerprintSensorPropertiesInternal> list) { + if (list == null || list.isEmpty()) { + Slog.d(TAG, "No fingerprint authenticators registered."); + return; + } + final FingerprintSensorPropertiesInternal + fingerprintSensorProperties = list.get(0); + if (fingerprintSensorProperties.sensorStrength + == STRENGTH_STRONG) { + fingerprintManager.registerBiometricStateListener( + new BiometricStateListener() { + @Override + public void onEnrollmentsChanged( + int userId, + int sensorId, + boolean hasEnrollments + ) { + if (sensorId == fingerprintSensorProperties + .sensorId) { + mFingerprintEnrolledForUser.put(userId, + hasEnrollments); + } + } + }); + } + } + }); + } + if (faceManager != null) { + faceManager.addAuthenticatorsRegisteredCallback( + new IFaceAuthenticatorsRegisteredCallback.Stub() { + @Override + public void onAllAuthenticatorsRegistered( + List<FaceSensorPropertiesInternal> list) { + if (list == null || list.isEmpty()) { + Slog.d(TAG, "No face authenticators registered."); + return; + } + final FaceSensorPropertiesInternal + faceSensorPropertiesInternal = list.get(0); + if (faceSensorPropertiesInternal.sensorStrength + == STRENGTH_STRONG) { + faceManager.registerBiometricStateListener( + new BiometricStateListener() { + @Override + public void onEnrollmentsChanged( + int userId, + int sensorId, + boolean hasEnrollments + ) { + if (sensorId + == faceSensorPropertiesInternal + .sensorId) { + mFaceEnrolledForUser.put(userId, + hasEnrollments); + } + } + }); + } + } + }); + } + } } final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient { diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index b9e6563a7d6a..0bd22f3da67f 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -112,8 +112,8 @@ class PreAuthInfo { == BiometricManager.Authenticators.MANDATORY_BIOMETRICS; if (dropCredentialFallback(promptInfo.getAuthenticators(), - settingObserver.getMandatoryBiometricsEnabledForUser(userId), - trustManager)) { + settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( + userId), trustManager)) { promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG); promptInfo.setNegativeButtonText(context.getString(R.string.cancel)); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 73aa14ba016f..78f71877afed 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -684,7 +684,8 @@ public class ClipboardService extends SystemService { if (clipboard == null) { return null; } - showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard); + showAccessNotificationLocked( + pkg, intendingUid, intendingUserId, clipboard, deviceId); notifyTextClassifierLocked(clipboard, pkg, intendingUid); if (clipboard.primaryClip != null) { scheduleAutoClear(userId, intendingUid, intendingDeviceId); @@ -1438,7 +1439,7 @@ public class ClipboardService extends SystemService { */ @GuardedBy("mLock") private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId, - Clipboard clipboard) { + Clipboard clipboard, int accessDeviceId) { if (clipboard.primaryClip == null) { return; } @@ -1477,7 +1478,7 @@ public class ClipboardService extends SystemService { return; } - final ArraySet<Context> toastContexts = getToastContexts(clipboard); + final ArraySet<Context> toastContexts = getToastContexts(clipboard, accessDeviceId); Binder.withCleanCallingIdentity(() -> { try { CharSequence callingAppLabel = mPm.getApplicationLabel( @@ -1516,40 +1517,55 @@ public class ClipboardService extends SystemService { * If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for * the focused VirtualDisplay for that device, but might need to return the contexts for * multiple displays if the VirtualDevice has several but none of them were focused. + * + * If the clipboard is NOT for a VirtualDevice, but it's being accessed from a VirtualDevice, + * this means that the clipboard is shared between the default and that device. In this case we + * need to show a toast in both places. */ - private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException { + private ArraySet<Context> getToastContexts(Clipboard clipboard, int accessDeviceId) + throws IllegalStateException { ArraySet<Context> contexts = new ArraySet<>(); + if (clipboard.deviceId == DEVICE_ID_DEFAULT || accessDeviceId == DEVICE_ID_DEFAULT) { + // Always show the toast on the default display when the default clipboard is accessed - + // also when the clipboard is shared with a virtual device and accessed from there. + // Same when any clipboard is accessed from the default device. + contexts.add(getContext()); + } - if (mVdmInternal != null && clipboard.deviceId != DEVICE_ID_DEFAULT) { - DisplayManager displayManager = getContext().getSystemService(DisplayManager.class); + if ((accessDeviceId == DEVICE_ID_DEFAULT && clipboard.deviceId == DEVICE_ID_DEFAULT) + || mVdmInternal == null) { + // No virtual devices involved. + return contexts; + } - int topFocusedDisplayId = mWm.getTopFocusedDisplayId(); - ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(clipboard.deviceId); + // At this point the clipboard is either accessed from a virtual device, or it is a virtual + // device clipboard, so show a toast on the relevant virtual display(s). + DisplayManager displayManager = getContext().getSystemService(DisplayManager.class); + ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(accessDeviceId); + int topFocusedDisplayId = mWm.getTopFocusedDisplayId(); - if (displayIds.contains(topFocusedDisplayId)) { - Display display = displayManager.getDisplay(topFocusedDisplayId); - if (display != null) { - contexts.add(getContext().createDisplayContext(display)); - return contexts; - } + if (displayIds.contains(topFocusedDisplayId)) { + Display display = displayManager.getDisplay(topFocusedDisplayId); + if (display != null) { + contexts.add(getContext().createDisplayContext(display)); + return contexts; } + } - for (int i = 0; i < displayIds.size(); i++) { - Display display = displayManager.getDisplay(displayIds.valueAt(i)); - if (display != null) { - contexts.add(getContext().createDisplayContext(display)); - } - } - if (!contexts.isEmpty()) { - return contexts; + for (int i = 0; i < displayIds.size(); i++) { + Display display = displayManager.getDisplay(displayIds.valueAt(i)); + if (display != null) { + contexts.add(getContext().createDisplayContext(display)); } + } + if (contexts.isEmpty()) { Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice " - + clipboard.deviceId); + + accessDeviceId); // Since we couldn't find any VirtualDisplays to use at all, just fall through to using // the default display below. + contexts.add(getContext()); } - contexts.add(getContext()); return contexts; } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 22b85d45d15e..a9fe8cb01b3a 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -248,6 +248,25 @@ public class PlatformCompat extends IPlatformCompat.Stub { return enabled; } + /** + * Internal version of {@link #isChangeEnabledByUid(long, int)}. + * + * <p>Does not perform costly permission check and logging. + */ + public boolean isChangeEnabledByUidInternalNoLogging(long changeId, int uid) { + String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null || packages.length == 0) { + return mCompatConfig.defaultChangeIdValue(changeId); + } + boolean enabled = true; + final int userId = UserHandle.getUserId(uid); + for (String packageName : packages) { + final var appInfo = getApplicationInfo(packageName, userId); + enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo); + } + return enabled; + } + @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java index 133c79f81bb5..8e725465ddd6 100644 --- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java +++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java @@ -24,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.net.ConnectivityModuleConnector; +import android.sysprop.CrashRecoveryProperties; import android.text.TextUtils; import android.util.Slog; @@ -35,7 +36,7 @@ import java.util.List; /** * Provides helper methods for the CrashRecovery APEX - * + * TODO: b/354112511 Add tests for this class when it is finalized. * @hide */ public final class CrashRecoveryHelper { @@ -76,11 +77,13 @@ public final class CrashRecoveryHelper { } /** - * Register health listeners for explicit package failures. - * Currently only registering for Connectivity Module health. - * @hide + * Register health listeners for Connectivity packages health. + * + * TODO: b/354112511 Have an internal method to trigger a rollback by reporting high severity errors, + * and rely on ActivityManager to inform the watchdog of severe network stack crashes + * instead of having this listener in parallel. */ - public void registerConnectivityModuleHealthListener(@NonNull int failureReason) { + public void registerConnectivityModuleHealthListener() { // register listener for ConnectivityModule mConnectivityModuleConnector.registerHealthListener( packageName -> { @@ -90,7 +93,7 @@ public final class CrashRecoveryHelper { return; } final List<VersionedPackage> pkgList = Collections.singletonList(pkg); - PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason); + PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); }); } @@ -126,4 +129,21 @@ public final class CrashRecoveryHelper { return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); } } + + /** + * Check if we're currently attempting to reboot for a factory reset. This method must + * return true if RescueParty tries to reboot early during a boot loop, since the device + * will not be fully booted at this time. + */ + public static boolean isRecoveryTriggeredReboot() { + return isFactoryResetPropertySet() || isRebootPropertySet(); + } + + static boolean isFactoryResetPropertySet() { + return CrashRecoveryProperties.attemptingFactoryReset().orElse(false); + } + + static boolean isRebootPropertySet() { + return CrashRecoveryProperties.attemptingReboot().orElse(false); + } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 76b4263c4b89..ed6ed60a6806 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -99,6 +99,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import javax.xml.datatype.DatatypeConfigurationException; @@ -1798,15 +1799,17 @@ public class DisplayDeviceConfig { loadThermalThrottlingConfig(config); loadPowerThrottlingConfigData(config); // Backlight and evenDimmer data should be loaded for HbmData - mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, (hbm) -> { + Function<HighBrightnessMode, Float> transitionPointProvider = (hbm) -> { float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue(); if (transitionPointBacklightScale >= mBacklightMaximum) { throw new IllegalArgumentException("HBM transition point invalid. " - + mHbmData.transitionPoint + " is not less than " + + transitionPointBacklightScale + " is not less than " + mBacklightMaximum); } return getBrightnessFromBacklight(transitionPointBacklightScale); - }); + }; + mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, + transitionPointProvider); if (mHbmData.isHighBrightnessModeEnabled && mHbmData.refreshRateLimit != null) { // TODO(b/331650248): cleanup, DMD can use mHbmData.refreshRateLimit mRefreshRateLimitations.add(new RefreshRateLimitation( @@ -1830,7 +1833,7 @@ public class DisplayDeviceConfig { loadRefreshRateSetting(config); loadScreenOffBrightnessSensorValueToLuxFromDdc(config); loadUsiVersion(config); - mHdrBrightnessData = HdrBrightnessData.loadConfig(config); + mHdrBrightnessData = HdrBrightnessData.loadConfig(config, transitionPointProvider); loadBrightnessCapForWearBedtimeMode(config); loadIdleScreenRefreshRateTimeoutConfigs(config); mVrrSupportEnabled = config.getSupportsVrr(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b3a6c1c1e20a..2cec869c290e 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -3849,9 +3849,10 @@ public final class DisplayManagerService extends SystemService { // Ignore redundant events. Further optimization is possible by merging adjacent events. Pair<Integer, Integer> last = mDisplayEvents.get(mDisplayEvents.size() - 1); if (last.first == displayId && last.second == event) { - Slog.d(TAG, - "Ignore redundant display event " + displayId + "/" + event + " to " - + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid); + if (DEBUG) { + Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event + " to " + + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid); + } return; } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index afab7438bf16..9324fc1c4e06 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -271,8 +271,9 @@ public class BrightnessClamperController { ModifiersAggregatedState state2) { return !BrightnessSynchronizer.floatEquals(state1.mMaxDesiredHdrRatio, state2.mMaxDesiredHdrRatio) - || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline - || state1.mHdrHbmEnabled != state2.mHdrHbmEnabled; + || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness, + state2.mMaxHdrBrightness) + || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline; } private void start() { @@ -470,8 +471,8 @@ public class BrightnessClamperController { */ public static class ModifiersAggregatedState { float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO; + float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX; @Nullable Spline mSdrHdrRatioSpline = null; - boolean mHdrHbmEnabled = false; } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java index 2ee70fd88919..5e44cc357b28 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java @@ -16,11 +16,15 @@ package com.android.server.display.brightness.clamper; +import static com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET; +import static com.android.server.display.brightness.clamper.LightSensorController.INVALID_LUX; + import android.annotation.Nullable; import android.annotation.SuppressLint; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.view.SurfaceControlHdrLayerInfoListener; import com.android.internal.annotations.VisibleForTesting; @@ -30,6 +34,7 @@ import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.config.HdrBrightnessData; import java.io.PrintWriter; +import java.util.Map; public class HdrBrightnessModifier implements BrightnessStateModifier, BrightnessClamperController.DisplayDeviceDataListener, @@ -53,20 +58,32 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, private final Handler mHandler; private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener; private final Injector mInjector; + private final Runnable mDebouncer; private IBinder mRegisteredDisplayToken; - private float mScreenSize; - private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE; - private HdrBrightnessData mHdrBrightnessData; private DisplayDeviceConfig mDisplayDeviceConfig; + @Nullable + private HdrBrightnessData mHdrBrightnessData; + private float mScreenSize; + private float mMaxDesiredHdrRatio = DEFAULT_MAX_HDR_SDR_RATIO; + private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE; + + private float mAmbientLux = INVALID_LUX; + private Mode mMode = Mode.NO_HDR; + // The maximum brightness allowed for current lux + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + private float mPendingMaxBrightness = PowerManager.BRIGHTNESS_MAX; + // brightness change speed, in units per seconds. Applied only on ambient lux changes + private float mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET; + private float mPendingTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET; HdrBrightnessModifier(Handler handler, BrightnessClamperController.ClamperChangeListener clamperChangeListener, BrightnessClamperController.DisplayDeviceData displayData) { - this(handler, clamperChangeListener, new Injector(), displayData); + this(new Handler(handler.getLooper()), clamperChangeListener, new Injector(), displayData); } @VisibleForTesting @@ -77,6 +94,11 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, mHandler = handler; mClamperChangeListener = clamperChangeListener; mInjector = injector; + mDebouncer = () -> { + mTransitionRate = mPendingTransitionRate; + mMaxBrightness = mPendingMaxBrightness; + mClamperChangeListener.onChanged(); + }; onDisplayChanged(displayData); } @@ -90,33 +112,60 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, if (mMode == Mode.NO_HDR) { return; } - float hdrBrightness = mDisplayDeviceConfig.getHdrBrightnessFromSdr( stateBuilder.getBrightness(), mMaxDesiredHdrRatio, mHdrBrightnessData.sdrToHdrRatioSpline); + float maxBrightness = getMaxBrightness(mMode, mMaxBrightness, mHdrBrightnessData); + hdrBrightness = Math.min(hdrBrightness, maxBrightness); + stateBuilder.setHdrBrightness(hdrBrightness); + stateBuilder.setCustomAnimationRate(mTransitionRate); + // transition rate applied, reset + mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET; } @Override - public void dump(PrintWriter printWriter) { - // noop + public void dump(PrintWriter pw) { + pw.println("HdrBrightnessModifier:"); + pw.println(" mHdrBrightnessData=" + mHdrBrightnessData); + pw.println(" mScreenSize=" + mScreenSize); + pw.println(" mMaxDesiredHdrRatio=" + mMaxDesiredHdrRatio); + pw.println(" mHdrLayerSize=" + mHdrLayerSize); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mMode=" + mMode); + pw.println(" mMaxBrightness=" + mMaxBrightness); + pw.println(" mPendingMaxBrightness=" + mPendingMaxBrightness); + pw.println(" mTransitionRate=" + mTransitionRate); + pw.println(" mPendingTransitionRate=" + mPendingTransitionRate); + pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null)); } // Called in DisplayControllerHandler @Override public void stop() { unregisterHdrListener(); + mHandler.removeCallbacksAndMessages(null); } - + // Called in DisplayControllerHandler @Override public boolean shouldListenToLightSensor() { - return false; + return hasBrightnessLimits(); } + // Called in DisplayControllerHandler @Override public void setAmbientLux(float lux) { - // noop + mAmbientLux = lux; + if (!hasBrightnessLimits()) { + return; + } + float desiredMaxBrightness = findBrightnessLimit(mHdrBrightnessData, lux); + if (mMode == Mode.NO_HDR) { + mMaxBrightness = desiredMaxBrightness; + } else { + scheduleMaxBrightnessUpdate(desiredMaxBrightness, mHdrBrightnessData); + } } @Override @@ -125,15 +174,44 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, displayData.mHeight, displayData.mDisplayDeviceConfig)); } - // Called in DisplayControllerHandler + // Called in DisplayControllerHandler, when any modifier state changes @Override public void applyStateChange( BrightnessClamperController.ModifiersAggregatedState aggregatedState) { - if (mMode != Mode.NO_HDR) { + if (mMode != Mode.NO_HDR && mHdrBrightnessData != null) { aggregatedState.mMaxDesiredHdrRatio = mMaxDesiredHdrRatio; aggregatedState.mSdrHdrRatioSpline = mHdrBrightnessData.sdrToHdrRatioSpline; - aggregatedState.mHdrHbmEnabled = (mMode == Mode.HBM_HDR); + aggregatedState.mMaxHdrBrightness = getMaxBrightness( + mMode, mMaxBrightness, mHdrBrightnessData); + } + } + + private boolean hasBrightnessLimits() { + return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty(); + } + + private void scheduleMaxBrightnessUpdate(float desiredMaxBrightness, HdrBrightnessData data) { + if (mMaxBrightness == desiredMaxBrightness) { + mPendingMaxBrightness = mMaxBrightness; + mPendingTransitionRate = -1f; + mTransitionRate = -1f; + mHandler.removeCallbacks(mDebouncer); + } else if (mPendingMaxBrightness != desiredMaxBrightness) { + mPendingMaxBrightness = desiredMaxBrightness; + long debounceTime; + if (mPendingMaxBrightness > mMaxBrightness) { + debounceTime = data.brightnessIncreaseDebounceMillis; + mPendingTransitionRate = data.screenBrightnessRampIncrease; + } else { + debounceTime = data.brightnessDecreaseDebounceMillis; + mPendingTransitionRate = data.screenBrightnessRampDecrease; + } + + mHandler.removeCallbacks(mDebouncer); + mHandler.postDelayed(mDebouncer, debounceTime); } + // do nothing if expectedMaxBrightness == mDesiredMaxBrightness + // && expectedMaxBrightness != mMaxBrightness } // Called in DisplayControllerHandler @@ -168,6 +246,8 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, mMaxDesiredHdrRatio = maxDesiredHdrRatio; if (needToNotifyChange) { + // data or hdr layer changed, reset custom transition rate + mTransitionRate = CUSTOM_ANIMATION_RATE_NOT_SET; mClamperChangeListener.onChanged(); } } @@ -190,6 +270,32 @@ public class HdrBrightnessModifier implements BrightnessStateModifier, return Mode.HBM_HDR; } + private float getMaxBrightness(Mode mode, float maxBrightness, HdrBrightnessData data) { + if (mode == Mode.NBM_HDR) { + return Math.min(data.hbmTransitionPoint, maxBrightness); + } else if (mode == Mode.HBM_HDR) { + return maxBrightness; + } else { + return PowerManager.BRIGHTNESS_MAX; + } + } + + // Called in DisplayControllerHandler + private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) { + float foundAmbientBoundary = Float.MAX_VALUE; + float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; + for (Map.Entry<Float, Float> brightnessPoint : + data.maxBrightnessLimits.entrySet()) { + float ambientBoundary = brightnessPoint.getKey(); + // find ambient lux upper boundary closest to current ambient lux + if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) { + foundMaxBrightness = brightnessPoint.getValue(); + foundAmbientBoundary = ambientBoundary; + } + } + return foundMaxBrightness; + } + // Called in DisplayControllerHandler private void onHdrInfoChanged(float hdrLayerSize, float maxDesiredHdrSdrRatio) { mHdrLayerSize = hdrLayerSize; diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java index c9408077e5af..ef4a7984755d 100644 --- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java +++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java @@ -19,6 +19,7 @@ package com.android.server.display.config; import static com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; import android.annotation.Nullable; +import android.os.PowerManager; import android.util.Spline; import com.android.internal.annotations.VisibleForTesting; @@ -29,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; /** * Brightness config for HDR content @@ -48,9 +50,9 @@ import java.util.Map; * </point> * </brightnessMap> * <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis> - * <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis> + * <screenBrightnessRampIncrease>0.04</brightnessIncreaseDurationMillis> * <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis> - * <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis> + * <screenBrightnessRampDecrease>0.03</brightnessDecreaseDurationMillis> * <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm> * <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm> * <allowInLowPowerMode>true</allowInLowPowerMode> @@ -99,6 +101,11 @@ public class HdrBrightnessData { public final float screenBrightnessRampDecrease; /** + * Brightness level at which we transition from normal to high-brightness + */ + public final float hbmTransitionPoint; + + /** * Min Hdr layer size to start hdr brightness boost up to high brightness mode transition point */ public final float minimumHdrPercentOfScreenForNbm; @@ -123,6 +130,7 @@ public class HdrBrightnessData { public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits, long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease, long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease, + float hbmTransitionPoint, float minimumHdrPercentOfScreenForNbm, float minimumHdrPercentOfScreenForHbm, boolean allowInLowPowerMode, @Nullable Spline sdrToHdrRatioSpline) { this.maxBrightnessLimits = maxBrightnessLimits; @@ -130,6 +138,7 @@ public class HdrBrightnessData { this.screenBrightnessRampIncrease = screenBrightnessRampIncrease; this.brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis; this.screenBrightnessRampDecrease = screenBrightnessRampDecrease; + this.hbmTransitionPoint = hbmTransitionPoint; this.minimumHdrPercentOfScreenForNbm = minimumHdrPercentOfScreenForNbm; this.minimumHdrPercentOfScreenForHbm = minimumHdrPercentOfScreenForHbm; this.allowInLowPowerMode = allowInLowPowerMode; @@ -144,6 +153,7 @@ public class HdrBrightnessData { + ", mScreenBrightnessRampIncrease: " + screenBrightnessRampIncrease + ", mBrightnessDecreaseDebounceMillis: " + brightnessDecreaseDebounceMillis + ", mScreenBrightnessRampDecrease: " + screenBrightnessRampDecrease + + ", transitionPoint: " + hbmTransitionPoint + ", minimumHdrPercentOfScreenForNbm: " + minimumHdrPercentOfScreenForNbm + ", minimumHdrPercentOfScreenForHbm: " + minimumHdrPercentOfScreenForHbm + ", allowInLowPowerMode: " + allowInLowPowerMode @@ -155,10 +165,12 @@ public class HdrBrightnessData { * Loads HdrBrightnessData from DisplayConfiguration */ @Nullable - public static HdrBrightnessData loadConfig(DisplayConfiguration config) { + public static HdrBrightnessData loadConfig(DisplayConfiguration config, + Function<HighBrightnessMode, Float> transitionPointProvider) { + HighBrightnessMode hbmConfig = config.getHighBrightnessMode(); HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig(); if (hdrConfig == null) { - return getFallbackData(config.getHighBrightnessMode()); + return getFallbackData(hbmConfig, transitionPointProvider); } List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint(); @@ -169,22 +181,38 @@ public class HdrBrightnessData { float minHdrPercentForHbm = hdrConfig.getMinimumHdrPercentOfScreenForHbm() != null ? hdrConfig.getMinimumHdrPercentOfScreenForHbm().floatValue() - : getFallbackHdrPercent(config.getHighBrightnessMode()); + : getFallbackHdrPercent(hbmConfig); float minHdrPercentForNbm = hdrConfig.getMinimumHdrPercentOfScreenForNbm() != null ? hdrConfig.getMinimumHdrPercentOfScreenForNbm().floatValue() : minHdrPercentForHbm; + if (minHdrPercentForNbm > minHdrPercentForHbm) { + throw new IllegalArgumentException( + "minHdrPercentForHbm should be >= minHdrPercentForNbm"); + } + return new HdrBrightnessData(brightnessLimits, hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(), hdrConfig.getScreenBrightnessRampIncrease().floatValue(), hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(), hdrConfig.getScreenBrightnessRampDecrease().floatValue(), + getTransitionPoint(hbmConfig, transitionPointProvider), minHdrPercentForNbm, minHdrPercentForHbm, hdrConfig.getAllowInLowPowerMode(), getSdrHdrRatioSpline(hdrConfig, config.getHighBrightnessMode())); } + private static float getTransitionPoint(@Nullable HighBrightnessMode hbm, + Function<HighBrightnessMode, Float> transitionPointProvider) { + if (hbm == null) { + return PowerManager.BRIGHTNESS_MAX; + } else { + return transitionPointProvider.apply(hbm); + } + } + @Nullable - private static HdrBrightnessData getFallbackData(HighBrightnessMode hbm) { + private static HdrBrightnessData getFallbackData(@Nullable HighBrightnessMode hbm, + Function<HighBrightnessMode, Float> transitionPointProvider) { if (hbm == null) { return null; } @@ -193,6 +221,7 @@ public class HdrBrightnessData { return new HdrBrightnessData(Collections.emptyMap(), 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET, 0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET, + getTransitionPoint(hbm, transitionPointProvider), fallbackPercent, fallbackPercent, false, fallbackSpline); } diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java index 8bfc4a3ad77a..1437c8dba0cd 100644 --- a/services/core/java/com/android/server/display/config/SensorData.java +++ b/services/core/java/com/android/server/display/config/SensorData.java @@ -34,6 +34,8 @@ public class SensorData { public static final String TEMPERATURE_TYPE_DISPLAY = "DISPLAY"; public static final String TEMPERATURE_TYPE_SKIN = "SKIN"; + private static final SensorData UNSPECIFIED_SENSOR_DATA = new SensorData( + /* type= */null, /* name= */ null); @Nullable public final String type; @@ -43,24 +45,14 @@ public class SensorData { public final float maxRefreshRate; public final List<SupportedModeData> supportedModes; - @VisibleForTesting - public SensorData() { - this(/* type= */ null, /* name= */ null); - } - - @VisibleForTesting - public SensorData(String type, String name) { - this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY); - } - - @VisibleForTesting - public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) { - this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of()); + private SensorData(@Nullable String type, @Nullable String name) { + this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY, + /* supportedModes= */ List.of()); } @VisibleForTesting - public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate, - List<SupportedModeData> supportedModes) { + SensorData(@Nullable String type, @Nullable String name, + float minRefreshRate, float maxRefreshRate, List<SupportedModeData> supportedModes) { this.type = type; this.name = name; this.minRefreshRate = minRefreshRate; @@ -72,7 +64,7 @@ public class SensorData { * @return True if the sensor matches both the specified name and type, or one if only one * is specified (not-empty). Always returns false if both parameters are null or empty. */ - public boolean matches(String sensorName, String sensorType) { + public boolean matches(@Nullable String sensorName, @Nullable String sensorType) { final boolean isNameSpecified = !TextUtils.isEmpty(sensorName); final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType); return (isNameSpecified || isTypeSpecified) @@ -120,7 +112,7 @@ public class SensorData { if (sensorDetails != null) { return loadSensorData(sensorDetails); } else { - return new SensorData(); + return UNSPECIFIED_SENSOR_DATA; } } @@ -130,13 +122,12 @@ public class SensorData { @Nullable public static SensorData loadProxSensorConfig( DisplayManagerFlags flags, DisplayConfiguration config) { - SensorData DEFAULT_SENSOR = new SensorData(); List<SensorDetails> sensorDetailsList = config.getProxSensor(); if (sensorDetailsList.isEmpty()) { - return DEFAULT_SENSOR; + return UNSPECIFIED_SENSOR_DATA; } - SensorData selectedSensor = DEFAULT_SENSOR; + SensorData selectedSensor = UNSPECIFIED_SENSOR_DATA; // Prioritize flagged sensors. for (SensorDetails sensorDetails : sensorDetailsList) { String flagStr = sensorDetails.getFeatureFlag(); @@ -148,7 +139,7 @@ public class SensorData { } // Check for normal un-flagged sensor if a flagged one wasn't found. - if (DEFAULT_SENSOR == selectedSensor) { + if (UNSPECIFIED_SENSOR_DATA == selectedSensor) { for (SensorDetails sensorDetails : sensorDetailsList) { if (sensorDetails.getFeatureFlag() != null) { continue; @@ -159,7 +150,7 @@ public class SensorData { } // Check if we shouldn't use a sensor at all. - if (DEFAULT_SENSOR != selectedSensor) { + if (UNSPECIFIED_SENSOR_DATA != selectedSensor) { if ("".equals(selectedSensor.name) && "".equals(selectedSensor.type)) { // <proxSensor> with empty values to the config means no sensor should be used. // See also {@link com.android.server.display.utils.SensorUtils} @@ -174,7 +165,7 @@ public class SensorData { * Loads temperature sensor data for no config case. (Type: SKIN, Name: null) */ public static SensorData loadTempSensorUnspecifiedConfig() { - return new SensorData(TEMPERATURE_TYPE_SKIN, null); + return new SensorData(TEMPERATURE_TYPE_SKIN, /* name= */ null); } /** @@ -185,7 +176,7 @@ public class SensorData { DisplayConfiguration config) { SensorDetails sensorDetails = config.getTempSensor(); if (!flags.isSensorBasedBrightnessThrottlingEnabled() || sensorDetails == null) { - return new SensorData(TEMPERATURE_TYPE_SKIN, null); + return loadTempSensorUnspecifiedConfig(); } String name = sensorDetails.getName(); String type = sensorDetails.getType(); @@ -202,7 +193,7 @@ public class SensorData { */ @NonNull public static SensorData loadSensorUnspecifiedConfig() { - return new SensorData(); + return UNSPECIFIED_SENSOR_DATA; } private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index c82e5be7c643..cc9f63048843 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -27,6 +27,7 @@ import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; @@ -100,6 +101,18 @@ public abstract class InputMethodManagerInternal { public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId); /** + * Returns the list of installed input methods that are enabled for the specified user. + * + * @param imiId IME ID to be queried about + * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled subtypes + * @param userId the user ID to be queried about + * @return a list of {@link InputMethodSubtype} that are enabled for {@code userId} + */ + @NonNull + public abstract List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( + String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId); + + /** * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from * the input method. * @@ -312,6 +325,13 @@ public abstract class InputMethodManagerInternal { return Collections.emptyList(); } + @NonNull + @Override + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(String imiId, + boolean allowsImplicitlyEnabledSubtypes, int userId) { + return Collections.emptyList(); + } + @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 992192702828..e8c15987751a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1406,16 +1406,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final String defaultImiId = SecureSettingsWrapper.getString( Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - currentUserId, AdditionalSubtypeMapRepository.get(currentUserId), - DirectBootAwareness.AUTO); - InputMethodSettingsRepository.put(currentUserId, newSettings); + final var settings = InputMethodSettingsRepository.get(currentUserId); postInputMethodSettingUpdatedLocked( !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId); updateFromSettingsLocked(true, currentUserId); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), - newSettings.getEnabledInputMethodList()); + settings.getEnabledInputMethodList()); AdditionalSubtypeMapRepository.startWriterThread(); @@ -1486,17 +1483,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return InputMethodInfoSafeList.empty(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return InputMethodInfoSafeList.empty(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { return InputMethodInfoSafeList.create(getInputMethodListLocked( - resolvedUserIds[0], directBootAwareness, callingUid)); + userId, directBootAwareness, callingUid)); } finally { Binder.restoreCallingIdentity(ident); } @@ -1511,17 +1506,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return InputMethodInfoSafeList.empty(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return InputMethodInfoSafeList.empty(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { return InputMethodInfoSafeList.create( - getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid)); + getEnabledInputMethodListLocked(userId, callingUid)); } finally { Binder.restoreCallingIdentity(ident); } @@ -1537,17 +1530,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return Collections.emptyList(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getInputMethodListLocked( - resolvedUserIds[0], directBootAwareness, callingUid); + return getInputMethodListLocked(userId, directBootAwareness, callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -1562,16 +1552,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } + if (!mUserManagerInternal.exists(userId)) { + return Collections.emptyList(); + } synchronized (ImfLock.class) { - final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mCurrentUserId, null); - if (resolvedUserIds.length != 1) { - return Collections.emptyList(); - } final int callingUid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - return getEnabledInputMethodListLocked(resolvedUserIds[0], callingUid); + return getEnabledInputMethodListLocked(userId, callingUid); } finally { Binder.restoreCallingIdentity(ident); } @@ -5877,6 +5865,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @NonNull + @Override + public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( + String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { + synchronized (ImfLock.class) { + return getEnabledInputMethodSubtypeListLocked(imiId, + allowsImplicitlyEnabledSubtypes, + userId, Process.SYSTEM_UID); + } + } + @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) { diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 3d0b079c69c8..741513cf3c0b 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -616,9 +616,10 @@ public class LocaleManagerService extends SystemService { LocaleConfig resLocaleConfig = null; try { resLocaleConfig = LocaleConfig.fromContextIgnoringOverride( - mContext.createPackageContext(appPackageName, 0)); + mContext.createPackageContextAsUser(appPackageName, /* flags= */ 0, + UserHandle.of(userId))); } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Unknown package name " + appPackageName); + Slog.e(TAG, "Unknown package name " + appPackageName + " for user " + userId); return; } final File file = getXmlFileNameForUser(appPackageName, userId); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 6e991b4db2b1..2e167efc4d81 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -376,6 +376,11 @@ public class LocationManagerService extends ILocationManager.Stub implements mContext.getContentResolver(), Settings.Global.LOCATION_ENABLE_STATIONARY_THROTTLE, defaultStationaryThrottlingSetting) != 0; + if (Flags.disableStationaryThrottling() && !( + Flags.keepGnssStationaryThrottling() && enableStationaryThrottling + && GPS_PROVIDER.equals(manager.getName()))) { + enableStationaryThrottling = false; + } if (enableStationaryThrottling) { realProvider = new StationaryThrottlingLocationProvider(manager.getName(), mInjector, realProvider); 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 ed451ff0c194..3f4a9bb4d864 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -297,7 +297,10 @@ public class ContextHubService extends IContextHubService.Stub { } public boolean isExpired() { - return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos() + return mTimestamp + + ContextHubTransactionManager + .RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT + .toNanos() < SystemClock.elapsedRealtimeNanos(); } } @@ -333,8 +336,14 @@ public class ContextHubService extends IContextHubService.Stub { return new IContextHubClientCallback.Stub() { private void finishCallback() { try { - IContextHubClient client = mDefaultClientMap.get(contextHubId); - client.callbackFinished(); + if (mDefaultClientMap != null && mDefaultClientMap.containsKey(contextHubId)) { + IContextHubClient client = mDefaultClientMap.get(contextHubId); + client.callbackFinished(); + } else { + Log.e(TAG, "Default client not found for hub (ID = " + contextHubId + "): " + + mDefaultClientMap == null ? "map was null" + : "map did not contain the hub"); + } } catch (RemoteException e) { Log.e( TAG, diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java index f2714dbd7e5f..2bb3be6a3332 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java @@ -17,10 +17,12 @@ package com.android.server.location.contexthub; import android.chre.flags.Flags; +import android.hardware.location.ContextHubTransaction; import android.hardware.location.NanoAppMessage; import android.util.Log; -import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.Callable; /** * A class to manage behaviors during test mode. This is used for testing. @@ -29,32 +31,31 @@ import java.util.Random; public class ContextHubTestModeManager { private static final String TAG = "ContextHubTestModeManager"; - /** Probability of duplicating a message. */ - private static final double MESSAGE_DROP_PROBABILITY = 0.05; - - /** Probability of duplicating a message. */ - private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05; + private static final int DROP_MESSAGE_TO_HOST_EVENT = 0; + private static final int DROP_MESSAGE_TO_CONTEXT_HUB_EVENT = 1; + private static final int DUPLICATE_MESSAGE_TO_HOST_EVENT = 2; + private static final int DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT = 3; + private static final int NUMBER_OF_EVENTS = 4; /** The number of total messages to send when the duplication event happens. */ private static final int NUM_MESSAGES_TO_DUPLICATE = 3; - /** - * The seed for the random number generator. This is used to make the - * test more deterministic. - */ - private static final long SEED = 0xDEADBEEF; - - private final Random mRandom = new Random(SEED); + /** The counter to track the number of interactions with the test mode manager. */ + private final AtomicLong mCounter = new AtomicLong(0); /** * @return whether the message was handled * @see ContextHubServiceCallback#handleNanoappMessage */ public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) { + if (!message.isReliable()) { + return false; + } + + long counterValue = mCounter.getAndIncrement(); if (Flags.reliableMessageDuplicateDetectionService() - && message.isReliable() - && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) { - Log.i(TAG, "[TEST MODE] Duplicating message (" + && counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_HOST_EVENT) { + Log.i(TAG, "[TEST MODE] Duplicating message to host (" + NUM_MESSAGES_TO_DUPLICATE + " sends) with message sequence number: " + message.getMessageSequenceNumber()); @@ -63,6 +64,14 @@ public class ContextHubTestModeManager { } return true; } + + if (counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_HOST_EVENT) { + Log.i(TAG, "[TEST MODE] Dropping message to host with " + + "message sequence number: " + + message.getMessageSequenceNumber()); + return true; + } + return false; } @@ -70,14 +79,39 @@ public class ContextHubTestModeManager { * @return whether the message was handled * @see IContextHubWrapper#sendMessageToContextHub */ - public boolean sendMessageToContextHub(NanoAppMessage message) { + public boolean sendMessageToContextHub(Callable<Integer> sendMessage, NanoAppMessage message) { + if (!message.isReliable()) { + return false; + } + + long counterValue = mCounter.getAndIncrement(); + if (counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT) { + Log.i(TAG, "[TEST MODE] Duplicating message to the Context Hub (" + + NUM_MESSAGES_TO_DUPLICATE + + " sends) with message sequence number: " + + message.getMessageSequenceNumber()); + for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) { + try { + int result = sendMessage.call(); + if (result != ContextHubTransaction.RESULT_SUCCESS) { + Log.e(TAG, "sendMessage returned an error: " + result); + } + } catch (Exception e) { + Log.e(TAG, "Exception in sendMessageToContextHub: " + + e.getMessage()); + } + } + return true; + } + if (Flags.reliableMessageRetrySupportService() - && message.isReliable() - && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) { - Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: " + && counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_CONTEXT_HUB_EVENT) { + Log.i(TAG, "[TEST MODE] Dropping message to the Context Hub with " + + "message sequence number: " + message.getMessageSequenceNumber()); return true; } + return false; } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index e6d330f85dfc..cd69ebaba766 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -56,6 +56,9 @@ import java.util.concurrent.atomic.AtomicInteger; public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1); + public static final Duration RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT = + RELIABLE_MESSAGE_TIMEOUT.multipliedBy(3); + private static final int MAX_PENDING_REQUESTS = 10000; private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3; diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 4fc3d8715a88..a8ad41853d34 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -53,6 +53,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.Callable; /** * @hide @@ -659,32 +660,40 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int sendMessageToContextHub(short hostEndpointId, int contextHubId, - NanoAppMessage message) throws RemoteException { + NanoAppMessage message) { android.hardware.contexthub.IContextHub hub = getHub(); if (hub == null) { return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } - try { - var msg = ContextHubServiceUtil.createAidlContextHubMessage( - hostEndpointId, message); - - // Only process the message normally if not using test mode manager or if - // the test mode manager call returned false as this indicates it did not - // process the message. - boolean useTestModeManager = Flags.reliableMessageImplementation() - && Flags.reliableMessageTestModeBehavior() - && mIsTestModeEnabled.get(); - if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) { + Callable<Integer> sendMessage = () -> { + try { + var msg = ContextHubServiceUtil.createAidlContextHubMessage( + hostEndpointId, message); hub.sendMessageToHub(contextHubId, msg); + return ContextHubTransaction.RESULT_SUCCESS; + } catch (RemoteException | ServiceSpecificException e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + }; + + // Only process the message normally if not using test mode manager or if + // the test mode manager call returned false as this indicates it did not + // process the message. + boolean useTestModeManager = Flags.reliableMessageImplementation() + && Flags.reliableMessageTestModeBehavior() + && mIsTestModeEnabled.get(); + if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub( + sendMessage, message)) { + try { + return sendMessage.call(); + } catch (Exception e) { + return ContextHubTransaction.RESULT_FAILED_UNKNOWN; } - - return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { - return ContextHubTransaction.RESULT_FAILED_UNKNOWN; - } catch (IllegalArgumentException e) { - return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } + return ContextHubTransaction.RESULT_SUCCESS; } @ContextHubTransaction.Result diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 803b125cbabc..621c090d37b8 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -702,7 +702,7 @@ public final class MediaProjectionManagerService extends SystemService } } - private final class BinderService extends IMediaProjectionManager.Stub { + final class BinderService extends IMediaProjectionManager.Stub { BinderService(Context context) { super(PermissionEnforcer.fromContext(context)); @@ -891,6 +891,13 @@ public final class MediaProjectionManagerService extends SystemService @Override public void requestConsentForInvalidProjection(@NonNull IMediaProjection projection) { requestConsentForInvalidProjection_enforcePermission(); + + if (android.companion.virtualdevice.flags.Flags.mediaProjectionKeyguardRestrictions() + && mKeyguardManager.isKeyguardLocked()) { + Slog.v(TAG, "Reusing token: Won't request consent while the keyguard is locked"); + return; + } + synchronized (mLock) { if (!isCurrentProjection(projection)) { Slog.v(TAG, "Reusing token: Won't request consent again for a token that " diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index 5ea3e70f7957..74f0d9cf3e39 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -81,8 +81,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.HexDump; -import com.android.modules.utils.build.SdkLevel; -import com.android.net.module.util.NetdUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -833,144 +831,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } @Override - public boolean getIpForwardingEnabled() throws IllegalStateException{ - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException( - "NMS#getIpForwardingEnabled not supported in V+"); - } - try { - return mNetdService.ipfwdEnabled(); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void setIpForwardingEnabled(boolean enable) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException( - "NMS#setIpForwardingEnabled not supported in V+"); - } try { - if (enable) { - mNetdService.ipfwdEnableForwarding("tethering"); - } else { - mNetdService.ipfwdDisableForwarding("tethering"); - } - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void startTethering(String[] dhcpRange) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#startTethering not supported in V+"); - } - try { - NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void stopTethering() { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#stopTethering not supported in V+"); - } - try { - mNetdService.tetherStop(); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public boolean isTetheringStarted() { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+"); - } - try { - return mNetdService.tetherIsEnabled(); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void tetherInterface(String iface) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+"); - } - try { - final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress(); - final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength()); - NetdUtils.tetherInterface(mNetdService, iface, dest); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void untetherInterface(String iface) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+"); - } - try { - NetdUtils.untetherInterface(mNetdService, iface); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public String[] listTetheredInterfaces() { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException( - "NMS#listTetheredInterfaces not supported in V+"); - } - try { - return mNetdService.tetherInterfaceList(); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void enableNat(String internalInterface, String externalInterface) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#enableNat not supported in V+"); - } - try { - mNetdService.tetherAddForward(internalInterface, externalInterface); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override - public void disableNat(String internalInterface, String externalInterface) { - PermissionUtils.enforceNetworkStackPermission(mContext); - if (SdkLevel.isAtLeastV()) { - throw new UnsupportedOperationException("NMS#disableNat not supported in V+"); - } - try { - mNetdService.tetherRemoveForward(internalInterface, externalInterface); - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } - } - - @Override public void setInterfaceQuota(String iface, long quotaBytes) { PermissionUtils.enforceNetworkStackPermission(mContext); @@ -1126,30 +986,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled"); try { - if (SdkLevel.isAtLeastV()) { - // setDataSaverEnabled throws if it fails to set data saver. - mContext.getSystemService(ConnectivityManager.class) - .setDataSaverEnabled(enable); - mDataSaverMode = enable; - if (mUseMeteredFirewallChains) { - // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW - // until ConnectivityService allows manipulation of the data saver mode via - // FIREWALL_CHAIN_METERED_ALLOW. - synchronized (mRulesLock) { - mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable); - } - } - return true; - } else { - final boolean changed = mNetdService.bandwidthEnableDataSaver(enable); - if (changed) { - mDataSaverMode = enable; - } else { - Log.e(TAG, "setDataSaverMode(" + enable + "): failed to set iptables"); + // setDataSaverEnabled throws if it fails to set data saver. + mContext.getSystemService(ConnectivityManager.class).setDataSaverEnabled(enable); + mDataSaverMode = enable; + if (mUseMeteredFirewallChains) { + // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW + // until ConnectivityService allows manipulation of the data saver mode via + // FIREWALL_CHAIN_METERED_ALLOW. + synchronized (mRulesLock) { + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable); } - return changed; } - } catch (RemoteException | IllegalStateException e) { + return true; + } catch (IllegalStateException e) { Log.e(TAG, "setDataSaverMode(" + enable + "): failed with exception", e); return false; } finally { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d9e22c5a270f..53b67969e91a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4118,7 +4118,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.increaseIndent(); for (int i = 0; i < mSubscriptionPlans.size(); i++) { final int subId = mSubscriptionPlans.keyAt(i); - fout.println("Subscriber ID " + subId + ":"); + fout.println("Subscription ID " + subId + ":"); fout.increaseIndent(); final SubscriptionPlan[] plans = mSubscriptionPlans.valueAt(i); if (!ArrayUtils.isEmpty(plans)) { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index c078409f468c..b12a917eede9 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1159,11 +1159,18 @@ public class ZenModeHelper { rule.conditionId = azr.getConditionId(); modified = true; } - boolean shouldPreserveCondition = Flags.modesApi() && Flags.modesUi() - && !isNew && origin == UPDATE_ORIGIN_USER - && rule.enabled == azr.isEnabled() - && rule.conditionId != null && rule.condition != null - && rule.conditionId.equals(rule.condition.id); + // This can be removed when {@link Flags#modesUi} is fully ramped up + final boolean isWatch = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); + boolean shouldPreserveCondition = + Flags.modesApi() + && (Flags.modesUi() || isWatch) + && !isNew + && origin == UPDATE_ORIGIN_USER + && rule.enabled == azr.isEnabled() + && rule.conditionId != null + && rule.condition != null + && rule.conditionId.equals(rule.condition.id); if (!shouldPreserveCondition) { // Do not update 'modified'. If only this changes we treat it as a no-op updateAZR. rule.condition = null; diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 58b14b14fdef..15e758cf6ffd 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -37,7 +37,6 @@ import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.os.Vibrator; import android.util.Log; import com.android.internal.R; @@ -47,9 +46,9 @@ public class BackgroundUserSoundNotifier { private static final boolean DEBUG = false; private static final String LOG_TAG = BackgroundUserSoundNotifier.class.getSimpleName(); - public static final String BUSN_CHANNEL_ID = "bg_user_sound_channel"; - public static final String BUSN_CHANNEL_NAME = "BackgroundUserSound"; - private static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER"; + private static final String BUSN_CHANNEL_ID = "bg_user_sound_channel"; + private static final String BUSN_CHANNEL_NAME = "BackgroundUserSound"; + public static final String ACTION_MUTE_SOUND = "com.android.server.ACTION_MUTE_BG_USER"; private static final String EXTRA_NOTIFICATION_ID = "com.android.server.EXTRA_CLIENT_UID"; private static final String EXTRA_CURRENT_USER_ID = "com.android.server.EXTRA_CURRENT_USER_ID"; private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER"; @@ -144,6 +143,7 @@ public class BackgroundUserSoundNotifier { -1) + " current user id " + intent.getIntExtra( EXTRA_CURRENT_USER_ID, -1)); } + mUserWithNotification = -1; mNotificationManager.cancelAsUser(LOG_TAG, notificationId, UserHandle.of(intent.getIntExtra(EXTRA_CURRENT_USER_ID, -1))); if (ACTION_MUTE_SOUND.equals(intent.getAction())) { @@ -159,10 +159,6 @@ public class BackgroundUserSoundNotifier { } } } - Vibrator vibrator = mSystemUserContext.getSystemService(Vibrator.class); - if (vibrator != null && vibrator.isVibrating()) { - vibrator.cancel(); - } } else if (ACTION_SWITCH_USER.equals(intent.getAction())) { service.switchUser(intent.getIntExtra(Intent.EXTRA_USER_ID, -1)); } diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java index 8175321ea293..9a7ba0f082ea 100644 --- a/services/core/java/com/android/server/pm/SaferIntentUtils.java +++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java @@ -104,6 +104,7 @@ public class SaferIntentUtils { @Disabled private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188; + @Nullable private static ParsedMainComponent infoToComponent( ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) { if (info instanceof ActivityInfo) { @@ -186,7 +187,7 @@ public class SaferIntentUtils { } boolean isChangeEnabled(long changeId) { - return platformCompat == null || platformCompat.isChangeEnabledByUidInternal( + return platformCompat == null || platformCompat.isChangeEnabledByUidInternalNoLogging( changeId, callingUid); } @@ -233,7 +234,8 @@ public class SaferIntentUtils { } final ParsedMainComponent comp = infoToComponent( resolveInfo.getComponentInfo(), resolver, args.isReceiver); - if (!comp.getIntents().isEmpty() && args.intent.getAction() == null) { + if (comp != null && !comp.getIntents().isEmpty() + && args.intent.getAction() == null) { match = false; } } else if (c instanceof IntentFilter) { diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java index fde23b726572..9b644880d737 100644 --- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java +++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java @@ -16,6 +16,7 @@ package com.android.server.policy; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.role.RoleManager; import android.content.ActivityNotFoundException; @@ -248,31 +249,7 @@ public class ModifierShortcutManager { + " className=" + className + " shortcutChar=" + shortcutChar); continue; } - ComponentName componentName = new ComponentName(packageName, className); - try { - mPackageManager.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_UNINSTALLED_PACKAGES); - } catch (PackageManager.NameNotFoundException e) { - String[] packages = mPackageManager.canonicalToCurrentPackageNames( - new String[] { packageName }); - componentName = new ComponentName(packages[0], className); - try { - mPackageManager.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_UNINSTALLED_PACKAGES); - } catch (PackageManager.NameNotFoundException e1) { - Log.w(TAG, "Unable to add bookmark: " + packageName - + "/" + className + " not found."); - continue; - } - } - - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setComponent(componentName); + intent = resolveComponentNameIntent(packageName, className); } else if (categoryName != null) { if (roleName != null) { Log.w(TAG, "Cannot specify role bookmark when category is present for" @@ -310,6 +287,32 @@ public class ModifierShortcutManager { } } + @Nullable + private Intent resolveComponentNameIntent(String packageName, String className) { + int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES; + ComponentName componentName = new ComponentName(packageName, className); + try { + mPackageManager.getActivityInfo(componentName, flags); + } catch (PackageManager.NameNotFoundException e) { + String[] packages = mPackageManager.canonicalToCurrentPackageNames( + new String[] { packageName }); + componentName = new ComponentName(packages[0], className); + try { + mPackageManager.getActivityInfo(componentName, flags); + } catch (PackageManager.NameNotFoundException e1) { + Log.w(TAG, "Unable to add bookmark: " + packageName + + "/" + className + " not found."); + return null; + } + } + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(componentName); + return intent; + } + void registerShortcutKey(long shortcutCode, IShortcutService shortcutService) throws RemoteException { IShortcutService service = mShortcutKeyServices.get(shortcutCode); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8419a608dc41..df973135f5f0 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2402,7 +2402,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); } - mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { + final var transitionListener = new AppTransitionListener(DEFAULT_DISPLAY) { @Override public int onAppTransitionStartingLocked(long statusBarAnimationStartTime, long statusBarAnimationDuration) { @@ -2436,7 +2436,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mLockAfterDreamingTransitionFinished = false; } } - }); + }; + mWindowManagerInternal.registerAppTransitionListener(transitionListener); mKeyguardDrawnTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_keyguardDrawnTimeout); diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java index ad146afe16e7..fb54c5de260a 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsDumpHelperImpl.java @@ -20,6 +20,8 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import com.android.server.power.optimization.Flags; + public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDumpHelper { private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; @@ -33,6 +35,9 @@ public class BatteryStatsDumpHelperImpl implements BatteryStats.BatteryStatsDump .setMaxStatsAgeMs(0); if (detailed) { builder.includePowerModels().includeProcessStateData().includeVirtualUids(); + if (Flags.batteryUsageStatsByPowerAndScreenState()) { + builder.includePowerStateData().includeScreenStateData(); + } } return mBatteryUsageStatsProvider.getBatteryUsageStats((BatteryStatsImpl) batteryStats, builder.build()); 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 1b7bf89d7b44..4052a64aabba 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -16356,6 +16356,7 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothPowerStatsCollector.collectAndDump(pw); mCameraPowerStatsCollector.collectAndDump(pw); mGnssPowerStatsCollector.collectAndDump(pw); + mCustomEnergyConsumerPowerStatsCollector.collectAndDump(pw); } private final Runnable mWriteAsyncRunnable = () -> { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 8127b8217bb0..ac6896696de6 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -201,7 +201,8 @@ public class BatteryUsageStatsProvider { batteryUsageStatsBuilder = new BatteryUsageStats.Builder( stats.getCustomEnergyConsumerNames(), includePowerModels, - includeProcessStateData, minConsumedPowerThreshold); + includeProcessStateData, query.isScreenStateDataNeeded(), + query.isPowerStateDataNeeded(), minConsumedPowerThreshold); // TODO(b/188068523): use a monotonic clock to ensure resilience of order and duration // of batteryUsageStats sessions to wall-clock adjustments @@ -348,6 +349,7 @@ public class BatteryUsageStatsProvider { final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames(); final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( customEnergyConsumerNames, includePowerModels, includeProcessStateData, + query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(), minConsumedPowerThreshold); if (mPowerStatsStore == null) { Log.e(TAG, "PowerStatsStore is unavailable"); @@ -408,7 +410,6 @@ public class BatteryUsageStatsProvider { + " does not include process state data"); continue; } - builder.add(snapshot); } } diff --git a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java index 11919019be4a..0273ba6a6d18 100644 --- a/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerStatsCollector.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import android.hardware.power.stats.EnergyConsumerType; import android.os.BatteryConsumer; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -65,4 +66,12 @@ public class CustomEnergyConsumerPowerStatsCollector extends PowerStatsCollector } return success; } + + @Override + public void collectAndDump(PrintWriter pw) { + ensureInitialized(); + for (int i = 0; i < mCollectors.size(); i++) { + mCollectors.get(i).collectAndDump(pw); + } + } } diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java index cace94114aa0..ce11fa0edaf7 100644 --- a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java @@ -176,9 +176,12 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { for (EnergyConsumerAttribution attribution : perUid) { int uid = mUidResolver.mapUid(attribution.uid); - long lastEnergy = mLastConsumerEnergyPerUid.get(uid); - long deltaEnergy = attribution.energyUWs - lastEnergy; + long lastEnergy = mLastConsumerEnergyPerUid.get(uid, ENERGY_UNSPECIFIED); mLastConsumerEnergyPerUid.put(uid, attribution.energyUWs); + if (lastEnergy == ENERGY_UNSPECIFIED) { + continue; + } + long deltaEnergy = attribution.energyUWs - lastEnergy; if (deltaEnergy <= 0) { continue; } @@ -189,7 +192,8 @@ public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector { } mLayout.setUidConsumedEnergy(uidStats, 0, - mLayout.getUidConsumedEnergy(uidStats, 0) + deltaEnergy); + mLayout.getUidConsumedEnergy(uidStats, 0) + + uJtoUc(deltaEnergy, averageVoltage)); } } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java index d9f6c1ff1444..f5b00054bea4 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java @@ -196,12 +196,11 @@ public abstract class PowerStatsCollector { } IndentingPrintWriter out = new IndentingPrintWriter(pw); - out.print(getClass().getSimpleName()); if (!isEnabled()) { + out.print(getClass().getSimpleName()); out.println(": disabled"); return; } - out.println(); ArrayList<PowerStats> collected = new ArrayList<>(); Consumer<PowerStats> consumer = collected::add; @@ -215,11 +214,9 @@ public abstract class PowerStatsCollector { removeConsumer(consumer); } - out.increaseIndent(); for (PowerStats stats : collected) { stats.dump(out); } - out.decreaseIndent(); } private void awaitCompletion() { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index 549a97ea49cd..0f1349287a0f 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -129,17 +129,55 @@ public class PowerStatsExporter { if (descriptor == null) { return; } + boolean isCustomComponent = + descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; PowerStatsLayout layout = new PowerStatsLayout(); layout.fromExtras(descriptor.extras); long[] deviceStats = new long[descriptor.statsArrayLength]; + for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) { + if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) { + if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { + continue; + } + } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { + continue; + } + + for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) { + if (batteryUsageStatsBuilder.isPowerStateDataNeeded() && !isCustomComponent) { + if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) { + continue; + } + } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) { + continue; + } + + populateAggregatedBatteryConsumer(batteryUsageStatsBuilder, powerComponentStats, + layout, deviceStats, screenState, powerState); + } + } + if (layout.isUidPowerAttributionSupported()) { + populateBatteryConsumers(batteryUsageStatsBuilder, + powerComponentStats, layout); + } + } + + private static void populateAggregatedBatteryConsumer( + BatteryUsageStats.Builder batteryUsageStatsBuilder, + PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout, + long[] deviceStats, @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState) { + int powerComponentId = powerComponentStats.powerComponentId; + boolean isCustomComponent = + powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + double[] totalPower = new double[1]; MultiStateStats.States.forEachTrackedStateCombination( powerComponentStats.getConfig().getDeviceStateConfig(), states -> { - if (states[AggregatedPowerStatsConfig.STATE_POWER] - != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + if (!areMatchingStates(states, screenState, powerState)) { return; } @@ -153,24 +191,23 @@ public class PowerStatsExporter { AggregateBatteryConsumer.Builder deviceScope = batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { - if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent( - descriptor.powerComponentId)) { - deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId, - totalPower[0]); + if (isCustomComponent) { + if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(powerComponentId)) { + deviceScope.addConsumedPowerForCustomComponent(powerComponentId, totalPower[0]); } } else { - deviceScope.addConsumedPower(descriptor.powerComponentId, - totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); - } - - if (layout.isUidPowerAttributionSupported()) { - populateUidBatteryConsumers(batteryUsageStatsBuilder, - powerComponentStats, layout); + BatteryConsumer.Key key = deviceScope.getKey(powerComponentId, + BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState); + if (key != null) { + deviceScope.addConsumedPower(key, totalPower[0], + BatteryConsumer.POWER_MODEL_UNDEFINED); + } + deviceScope.addConsumedPower(powerComponentId, totalPower[0], + BatteryConsumer.POWER_MODEL_UNDEFINED); } } - private static void populateUidBatteryConsumers( + private static void populateBatteryConsumers( BatteryUsageStats.Builder batteryUsageStatsBuilder, PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout) { @@ -185,11 +222,44 @@ public class PowerStatsExporter { .getUidStateConfig()[AggregatedPowerStatsConfig.STATE_PROCESS_STATE].isTracked() && powerComponentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID; + ArrayList<Integer> uids = new ArrayList<>(); + powerComponentStats.collectUids(uids); + for (int screenState = 0; screenState < BatteryConsumer.SCREEN_STATE_COUNT; screenState++) { + if (batteryUsageStatsBuilder.isScreenStateDataNeeded()) { + if (screenState == BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { + continue; + } + } else if (screenState != BatteryConsumer.SCREEN_STATE_UNSPECIFIED) { + continue; + } + + for (int powerState = 0; powerState < BatteryConsumer.POWER_STATE_COUNT; powerState++) { + if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) { + if (powerState == BatteryConsumer.POWER_STATE_UNSPECIFIED) { + continue; + } + } else if (powerState != BatteryConsumer.POWER_STATE_BATTERY) { + continue; + } + + populateUidBatteryConsumers(batteryUsageStatsBuilder, powerComponentStats, layout, + uids, powerComponent, uidStats, breakDownByProcState, screenState, + powerState); + } + } + } + + private static void populateUidBatteryConsumers( + BatteryUsageStats.Builder batteryUsageStatsBuilder, + PowerComponentAggregatedPowerStats powerComponentStats, PowerStatsLayout layout, + List<Integer> uids, AggregatedPowerStatsConfig.PowerComponent powerComponent, + long[] uidStats, boolean breakDownByProcState, + @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState) { + int powerComponentId = powerComponentStats.powerComponentId; double[] powerByProcState = new double[breakDownByProcState ? BatteryConsumer.PROCESS_STATE_COUNT : 1]; double powerAllApps = 0; - ArrayList<Integer> uids = new ArrayList<>(); - powerComponentStats.collectUids(uids); for (int uid : uids) { UidBatteryConsumer.Builder builder = batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid); @@ -199,8 +269,7 @@ public class PowerStatsExporter { MultiStateStats.States.forEachTrackedStateCombination( powerComponent.getUidStateConfig(), states -> { - if (states[AggregatedPowerStatsConfig.STATE_POWER] - != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + if (!areMatchingStates(states, screenState, powerState)) { return; } @@ -224,8 +293,17 @@ public class PowerStatsExporter { powerAllProcStates += power; if (breakDownByProcState && procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { - builder.addConsumedPower(builder.getKey(powerComponentId, procState), power, - BatteryConsumer.POWER_MODEL_UNDEFINED); + if (batteryUsageStatsBuilder.isPowerStateDataNeeded()) { + builder.addConsumedPower( + builder.getKey(powerComponentId, procState, screenState, + powerState), + power, BatteryConsumer.POWER_MODEL_UNDEFINED); + } else { + builder.addConsumedPower( + builder.getKey(powerComponentId, procState, screenState, + BatteryConsumer.POWER_STATE_UNSPECIFIED), + power, BatteryConsumer.POWER_MODEL_UNDEFINED); + } } } if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { @@ -243,8 +321,49 @@ public class PowerStatsExporter { if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { allAppsScope.addConsumedPowerForCustomComponent(powerComponentId, powerAllApps); } else { + BatteryConsumer.Key key = allAppsScope.getKey(powerComponentId, + BatteryConsumer.PROCESS_STATE_ANY, screenState, powerState); + if (key != null) { + allAppsScope.addConsumedPower(key, powerAllApps, + BatteryConsumer.POWER_MODEL_UNDEFINED); + } allAppsScope.addConsumedPower(powerComponentId, powerAllApps, BatteryConsumer.POWER_MODEL_UNDEFINED); } } + + private static boolean areMatchingStates(int[] states, + @BatteryConsumer.ScreenState int screenState, + @BatteryConsumer.PowerState int powerState) { + switch (screenState) { + case BatteryConsumer.SCREEN_STATE_ON: + if (states[AggregatedPowerStatsConfig.STATE_SCREEN] + != AggregatedPowerStatsConfig.SCREEN_STATE_ON) { + return false; + } + break; + case BatteryConsumer.SCREEN_STATE_OTHER: + if (states[AggregatedPowerStatsConfig.STATE_SCREEN] + != AggregatedPowerStatsConfig.SCREEN_STATE_OTHER) { + return false; + } + break; + } + + switch (powerState) { + case BatteryConsumer.POWER_STATE_BATTERY: + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_BATTERY) { + return false; + } + break; + case BatteryConsumer.POWER_STATE_OTHER: + if (states[AggregatedPowerStatsConfig.STATE_POWER] + != AggregatedPowerStatsConfig.POWER_STATE_OTHER) { + return false; + } + break; + } + return true; + } } diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index d34498a3764b..cc0a283db6a0 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -54,3 +54,17 @@ flag { description: "Adds battery_usage_stats_slice atom" bug: "324602949" } + +flag { + name: "battery_usage_stats_by_power_and_screen_state" + namespace: "backstage_power" + description: "Batterystats dumpsys is enhanced by including power break-down by power s" + bug: "352835319" +} + +flag { + name: "disable_composite_battery_usage_stats_atoms" + namespace: "backstage_power" + description: "Disable deprecated BatteryUsageStatsAtom pulled atom" + bug: "324602949" +} diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 6537228583ec..5fab13bdc402 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -67,6 +67,7 @@ abstract class Vibration { CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF), CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE), CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER), + CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER), CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON), CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED), CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS), diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 4437a2ddf3a7..bff175fec1dd 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -16,6 +16,7 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.USAGE_CLASS_ALARM; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; @@ -73,6 +74,7 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.SystemService; +import com.android.server.pm.BackgroundUserSoundNotifier; import libcore.util.NativeAllocationRegistry; @@ -173,7 +175,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider; - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @VisibleForTesting + BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { @@ -190,6 +193,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /* immediate= */ false); } } + } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers() + && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) { + synchronized (mLock) { + if (shouldCancelOnFgUserRequest(mNextVibration)) { + clearNextVibrationLocked(new Vibration.EndInfo( + Vibration.Status.CANCELLED_BY_FOREGROUND_USER)); + } + if (shouldCancelOnFgUserRequest(mCurrentVibration)) { + mCurrentVibration.notifyCancelled(new Vibration.EndInfo( + Vibration.Status.CANCELLED_BY_FOREGROUND_USER), + /* immediate= */ false); + } + } } } }; @@ -299,6 +315,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); + if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) { + filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND); + } context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); @@ -1423,6 +1442,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") + private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) { + if (conductor == null) { + return false; + } + return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM; + } + + @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { consumer.accept(mVibrators.valueAt(i)); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 400919a88b1f..516fc656ccb4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -53,7 +53,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isFloating; import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; @@ -336,7 +335,6 @@ import android.service.contentcapture.ActivityEvent; import android.service.dreams.DreamActivity; import android.service.voice.IVoiceInteractionSession; import android.util.ArraySet; -import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.MergedConfiguration; @@ -8648,7 +8646,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } - applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig); + applySizeOverrideIfNeeded( + mDisplayContent, + info.applicationInfo, + newParentConfiguration, + resolvedConfig, + mOptOutEdgeToEdge, + hasFixedRotationTransform(), + getCompatDisplayInsets() != null); mResolveConfigHint.resetTmpOverrides(); logAppCompatState(); @@ -8658,100 +8663,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } - /** - * If necessary, override configuration fields related to app bounds. - * This will happen when the app is targeting SDK earlier than 35. - * The insets and configuration has decoupled since SDK level 35, to make the system - * compatible to existing apps, override the configuration with legacy metrics. In legacy - * metrics, fields such as appBounds will exclude some of the system bar areas. - * The override contains all potentially affected fields in Configuration, including - * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. - * All overrides to those fields should be in this method. - * - * TODO: Consider integrate this with computeConfigByResolveHint() - */ - private void applySizeOverrideIfNeeded(Configuration newParentConfiguration, - int parentWindowingMode, Configuration inOutConfig) { - if (mDisplayContent == null) { - return; - } - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - int rotation = newParentConfiguration.windowConfiguration.getRotation(); - if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { - rotation = mDisplayContent.getRotation(); - } - if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig - || getCompatDisplayInsets() != null - || (isFloating(parentWindowingMode) - // Check the requested windowing mode of activity as well in case it is - // switching between PiP and fullscreen. - && (inOutConfig.windowConfiguration.getWindowingMode() - == WINDOWING_MODE_UNDEFINED - || isFloating(inOutConfig.windowConfiguration.getWindowingMode()))) - || rotation == ROTATION_UNDEFINED)) { - // If the insets configuration decoupled logic is not enabled for the app, or the app - // already has a compat override, or the context doesn't contain enough info to - // calculate the override, skip the override. - return; - } - // Make sure the orientation related fields will be updated by the override insets, because - // fixed rotation has assigned the fields from display's configuration. - if (hasFixedRotationTransform()) { - inOutConfig.windowConfiguration.setAppBounds(null); - inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; - inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - inOutConfig.orientation = ORIENTATION_UNDEFINED; - } - - // Override starts here. - final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final int dw = rotated ? mDisplayContent.mBaseDisplayHeight - : mDisplayContent.mBaseDisplayWidth; - final int dh = rotated ? mDisplayContent.mBaseDisplayWidth - : mDisplayContent.mBaseDisplayHeight; - final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy() - .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets; - // This should be the only place override the configuration for ActivityRecord. Override - // the value if not calculated yet. - Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - if (outAppBounds == null || outAppBounds.isEmpty()) { - inOutConfig.windowConfiguration.setAppBounds(parentBounds); - outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); - outAppBounds.inset(nonDecorInsets); - } - float density = inOutConfig.densityDpi; - if (density == Configuration.DENSITY_DPI_UNDEFINED) { - density = newParentConfiguration.densityDpi; - } - density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; - if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { - inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); - } - if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { - inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); - } - if (inOutConfig.smallestScreenWidthDp - == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED - && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { - // For the case of PIP transition and multi-window environment, the - // smallestScreenWidthDp is handled already. Override only if the app is in - // fullscreen. - final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo()); - mDisplayContent.computeSizeRanges(info, rotated, dw, dh, - mDisplayContent.getDisplayMetrics().density, - inOutConfig, true /* overrideConfig */); - } - - // It's possible that screen size will be considered in different orientation with or - // without considering the system bar insets. Override orientation as well. - if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { - inOutConfig.orientation = - (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) - ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; - } - } - private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, @NonNull Configuration parentConfig) { task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 3b0b727597a5..26a6b00254d3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -584,7 +584,7 @@ public abstract class ActivityTaskManagerInternal { public abstract void clearLockedTasks(String reason); public abstract void updateUserConfiguration(); - public abstract boolean canShowErrorDialogs(); + public abstract boolean canShowErrorDialogs(int userId); public abstract void setProfileApp(String profileApp); public abstract void setProfileProc(WindowProcessController wpc); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ff46b33571f3..a84598dd73dc 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -190,6 +190,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -4899,14 +4900,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * dialog / global actions also might want different behaviors. */ private void updateShouldShowDialogsLocked(Configuration config) { + mShowDialogs = shouldShowDialogs(config, /* checkUiMode= */ true); + } + + private boolean shouldShowDialogs(Configuration config, boolean checkUiMode) { final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH && config.navigation == Configuration.NAVIGATION_NONAV); final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(), HIDE_ERROR_DIALOGS, 0) != 0; - mShowDialogs = inputMethodExists - && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config) - && !hideDialogsSet; + boolean showDialogs = inputMethodExists && !hideDialogsSet; + if (checkUiMode) { + showDialogs = showDialogs + && ActivityTaskManager.currentUiModeSupportsErrorDialogs(config); + } + return showDialogs; } private void updateFontScaleIfNeeded(@UserIdInt int userId) { @@ -7148,15 +7156,67 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public boolean canShowErrorDialogs() { + public boolean canShowErrorDialogs(int userId) { synchronized (mGlobalLock) { - return mShowDialogs && !mSleeping && !mShuttingDown + final boolean showDialogs = mShowDialogs + || shouldShowDialogsForVisibleBackgroundUserLocked(userId); + final UserInfo userInfo = getUserManager().getUserInfo(userId); + if (userInfo == null) { + // Unable to retrieve user information. Returning false, assuming there is + // no valid user with the given id. + return false; + } + return showDialogs && !mSleeping && !mShuttingDown && !mKeyguardController.isKeyguardOrAodShowing(DEFAULT_DISPLAY) - && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, - mAmInternal.getCurrentUserId()) + && !hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, userId) && !(UserManager.isDeviceInDemoMode(mContext) - && mAmInternal.getCurrentUser().isDemo()); + && userInfo.isDemo()); + } + } + + /** + * Checks if the given user is a visible background user, which is a full, background user + * assigned to secondary displays on the devices that have + * {@link UserManager#isVisibleBackgroundUsersEnabled() + * config_multiuserVisibleBackgroundUsers enabled} (for example, passenger users on + * automotive builds, using the display associated with their seats). + * + * @see UserManager#isUserVisible() + */ + private boolean isVisibleBackgroundUser(int userId) { + if (!UserManager.isVisibleBackgroundUsersEnabled()) { + return false; + } + boolean isForeground = getCurrentUserId() == userId; + boolean isProfile = getUserManager().isProfile(userId); + boolean isVisible = mWindowManager.mUmInternal.isUserVisible(userId); + return isVisible && !isForeground && !isProfile; + } + + /** + * In a car environment, {@link ActivityTaskManagerService#mShowDialogs} is always set to + * {@code false} from {@link ActivityTaskManagerService#updateShouldShowDialogsLocked} + * because its UI mode is {@link Configuration#UI_MODE_TYPE_CAR}. Thus, error dialogs are + * not displayed when an ANR or a crash occurs. However, in the automotive multi-user + * multi-display environment, this can confuse the passenger users and leave them + * uninformed when an app is terminated by the ANR or crash without any notification. + * To address this, error dialogs are allowed for the passenger users who have UI access + * on assigned displays (a.k.a. visible background users) on devices that have + * config_multiuserVisibleBackgroundUsers enabled even though the UI mode is + * {@link Configuration#UI_MODE_TYPE_CAR}. + * + * @see ActivityTaskManagerService#updateShouldShowDialogsLocked + */ + private boolean shouldShowDialogsForVisibleBackgroundUserLocked(int userId) { + if (!isVisibleBackgroundUser(userId)) { + return false; + } + final int displayId = mWindowManager.mUmInternal.getMainDisplayAssignedToUser(userId); + final DisplayContent dc = mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + return false; } + return shouldShowDialogs(dc.getConfiguration(), /* checkUiMode= */ false); } @Override diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 8421765060ce..0f8d68b713a7 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -166,15 +166,14 @@ class BackNavigationController { return null; } - // Move focus to the top embedded window if possible - if (mWindowManagerService.moveFocusToAdjacentEmbeddedWindow(window)) { - window = wmService.getFocusedWindowLocked(); - if (window == null) { - Slog.e(TAG, "New focused window is null, returning null."); - return null; - } + // Updating the window to the most recently used one among the embedded windows + // that are displayed adjacently, unless the IME is visible. + // When the IME is visible, the IME is displayed on top of embedded activities. + // In that case, the back event should still be delivered to focused activity in + // order to dismiss the IME. + if (!window.getDisplayContent().getImeContainer().isVisible()) { + window = mWindowManagerService.getMostRecentUsedEmbeddedWindowForBack(window); } - if (!window.isDrawn()) { ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focused window didn't have a valid surface drawn."); diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index efd52026cfec..3ebaf03c4a31 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -22,14 +22,23 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isFloating; import static android.app.WindowConfiguration.windowingModeToString; import static android.app.WindowConfigurationProto.WINDOWING_MODE; import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; +import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; @@ -38,11 +47,14 @@ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGU import android.annotation.CallSuper; import android.annotation.NonNull; import android.app.WindowConfiguration; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.LocaleList; +import android.util.DisplayMetrics; import android.util.proto.ProtoOutputStream; +import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; @@ -173,6 +185,110 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration); } + /** + * If necessary, override configuration fields related to app bounds. + * This will happen when the app is targeting SDK earlier than 35. + * The insets and configuration has decoupled since SDK level 35, to make the system + * compatible to existing apps, override the configuration with legacy metrics. In legacy + * metrics, fields such as appBounds will exclude some of the system bar areas. + * The override contains all potentially affected fields in Configuration, including + * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. + * All overrides to those fields should be in this method. + * + * TODO: Consider integrate this with computeConfigByResolveHint() + */ + static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo, + Configuration newParentConfiguration, Configuration inOutConfig, + boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform, + boolean hasCompatDisplayInsets) { + if (displayContent == null) { + return; + } + final boolean useOverrideInsetsForConfig = + displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration + ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) + && !appInfo.isChangeEnabled( + OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION) + : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); + final int parentWindowingMode = + newParentConfiguration.windowConfiguration.getWindowingMode(); + final boolean isFloating = isFloating(parentWindowingMode) + // Check the requested windowing mode of activity as well in case it is + // switching between PiP and fullscreen. + && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED + || isFloating(inOutConfig.windowConfiguration.getWindowingMode())); + int rotation = newParentConfiguration.windowConfiguration.getRotation(); + if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) { + rotation = displayContent.getRotation(); + } + if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig + || hasCompatDisplayInsets + || isFloating + || rotation == ROTATION_UNDEFINED)) { + // If the insets configuration decoupled logic is not enabled for the app, or the app + // already has a compat override, or the context doesn't contain enough info to + // calculate the override, skip the override. + return; + } + // Make sure the orientation related fields will be updated by the override insets, because + // fixed rotation has assigned the fields from display's configuration. + if (hasFixedRotationTransform) { + inOutConfig.windowConfiguration.setAppBounds(null); + inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; + inOutConfig.orientation = ORIENTATION_UNDEFINED; + } + + // Override starts here. + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated + ? displayContent.mBaseDisplayHeight + : displayContent.mBaseDisplayWidth; + final int dh = rotated + ? displayContent.mBaseDisplayWidth + : displayContent.mBaseDisplayHeight; + // This should be the only place override the configuration for ActivityRecord. Override + // the value if not calculated yet. + Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + if (outAppBounds == null || outAppBounds.isEmpty()) { + inOutConfig.windowConfiguration.setAppBounds( + newParentConfiguration.windowConfiguration.getBounds()); + outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); + outAppBounds.inset(displayContent.getDisplayPolicy() + .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets); + } + float density = inOutConfig.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = newParentConfiguration.densityDpi; + } + density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; + if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { + inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f); + } + if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { + inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f); + } + if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED + && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { + // For the case of PIP transition and multi-window environment, the + // smallestScreenWidthDp is handled already. Override only if the app is in + // fullscreen. + final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo()); + displayContent.computeSizeRanges(info, rotated, dw, dh, + displayContent.getDisplayMetrics().density, + inOutConfig, true /* overrideConfig */); + } + + // It's possible that screen size will be considered in different orientation with or + // without considering the system bar insets. Override orientation as well. + if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { + inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) + ? ORIENTATION_PORTRAIT + : ORIENTATION_LANDSCAPE; + } + } + /** Returns {@code true} if requested override override configuration is not empty. */ boolean hasRequestedOverrideConfiguration() { return mHasOverrideConfiguration; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 93711497f590..fa603682bc40 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3895,6 +3895,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * Returns the focused window of the given Activity if the Activity is focused. + */ + WindowState findFocusedWindow(ActivityRecord activityRecord) { + final ActivityRecord tmpApp = mFocusedApp; + mTmpWindow = null; + try { + mFocusedApp = activityRecord; + // mFindFocusedWindow will populate mTmpWindow with the new focused window when found. + activityRecord.forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */); + } finally { + mFocusedApp = tmpApp; + } + return mTmpWindow; + } + + /** * Update the focused window and make some adjustments if the focus has changed. * * @param mode Indicates the situation we are in. Possible modes are: @@ -6911,6 +6927,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Whether {@link #mAnimatingRecents} is going to be the top activity. */ private boolean mRecentsWillBeTop; + FixedRotationTransitionListener() { + super(DisplayContent.this.mDisplayId); + } + /** * If the recents activity has a fixed orientation which is different from the current top * activity, it will be rotated before being shown so we avoid a screen rotation animation diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 80362a44a33f..c3339cde6828 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -588,7 +588,7 @@ public class DisplayPolicy { gesturesPointerEventCallbacks); displayContent.registerPointerEventListener(mSystemGestures); } - mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() { + mAppTransitionListener = new WindowManagerInternal.AppTransitionListener(displayId) { private Runnable mAppTransitionPending = () -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 63fe94c33061..e50a089a4d5e 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -44,6 +44,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.server.UiThread; +import com.android.window.flags.Flags; /** * Controls camera compatibility treatment that handles orientation mismatch between camera @@ -69,6 +70,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @NonNull private final ActivityRefresher mActivityRefresher; + @Nullable + private Task mCameraTask; + @ScreenOrientation private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET; @@ -104,7 +108,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp * guaranteed to match, the rotation can cause letterboxing. * * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link - * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment. + * #isTreatmentEnabledForDisplay} for conditions enabling the treatment. */ @ScreenOrientation int getOrientation() { @@ -136,9 +140,9 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp // are aligned when they compute orientation of the preview. // This means that even for a landscape-only activity and a device with landscape natural // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that - // natural orientation = portrait window = portait camera is the main wrong assumption + // natural orientation = portrait window = portrait camera is the main wrong assumption // that apps make when they implement camera previews so landscape windows need be - // rotated in the orientation oposite to the natural one even if it's portrait. + // rotated in the orientation opposite to the natural one even if it's portrait. // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions // of the portrait and landscape orientation requests. final int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait) @@ -296,6 +300,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @Override public boolean onCameraOpened(@NonNull ActivityRecord cameraActivity, @NonNull String cameraId) { + mCameraTask = cameraActivity.getTask(); // Checking whether an activity in fullscreen rather than the task as this camera // compat treatment doesn't cover activity embedding. if (cameraActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { @@ -305,7 +310,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp } // Checking that the whole app is in multi-window mode as we shouldn't show toast // for the activity embedding case. - if (cameraActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW + if (mCameraTask != null && mCameraTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW && isTreatmentEnabledForActivity( cameraActivity, /* mustBeFullscreen */ false)) { final PackageManager packageManager = mWmService.mContext.getPackageManager(); @@ -343,10 +348,15 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp @Override public boolean onCameraClosed(@NonNull String cameraId) { - // Top activity in the same task as the camera activity, or `null` if the task is - // closed. - final ActivityRecord topActivity = mDisplayContent.topRunningActivity( - /* considerKeyguardState= */ true); + final ActivityRecord topActivity; + if (Flags.cameraCompatFullscreenPickSameTaskActivity()) { + topActivity = mCameraTask != null ? mCameraTask.getTopActivity( + /* includeFinishing= */ true, /* includeOverlays= */ false) : null; + } else { + topActivity = mDisplayContent.topRunningActivity(/* considerKeyguardState= */ true); + } + + mCameraTask = null; if (topActivity == null) { return true; } @@ -368,8 +378,6 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp mDisplayContent.mDisplayId); // Checking whether an activity in fullscreen rather than the task as this camera compat // treatment doesn't cover activity embedding. - // TODO(b/350495350): Consider checking whether this activity is the camera activity, or - // whether the top activity has the same task as the one which opened camera. if (topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { return true; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index c26684f60731..cc95518cce39 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -39,7 +39,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; @@ -537,27 +536,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public boolean startMovingTask(IWindow window, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) Slog.d( - TAG_WM, "startMovingTask: {" + startX + "," + startY + "}"); - - final long ident = Binder.clearCallingIdentity(); - try { - return mService.mTaskPositioningController.startMovingTask(window, startX, startY); - } finally { - Binder.restoreCallingIdentity(ident); - } + return false; } @Override public void finishMovingTask(IWindow window) { - if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishMovingTask"); - - final long ident = Binder.clearCallingIdentity(); - try { - mService.mTaskPositioningController.finishTaskPositioning(window); - } finally { - Binder.restoreCallingIdentity(ident); - } } @Override diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java deleted file mode 100644 index 972dd2e382cc..000000000000 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.ActivityTaskManager.RESIZE_MODE_USER; -import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED; -import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; - -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; -import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import android.annotation.NonNull; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Binder; -import android.os.IBinder; -import android.os.InputConfig; -import android.os.RemoteException; -import android.os.Trace; -import android.util.DisplayMetrics; -import android.util.Slog; -import android.view.BatchedInputEventReceiver; -import android.view.InputApplicationHandle; -import android.view.InputChannel; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.InputEventReceiver; -import android.view.InputWindowHandle; -import android.view.MotionEvent; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.policy.TaskResizingAlgorithm; -import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; -import com.android.internal.protolog.ProtoLog; - -import java.util.concurrent.CompletableFuture; - -class TaskPositioner implements IBinder.DeathRecipient { - private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; - private static final String TAG_LOCAL = "TaskPositioner"; - private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM; - - private static Factory sFactory; - - public static final float RESIZING_HINT_ALPHA = 0.5f; - - public static final int RESIZING_HINT_DURATION_MS = 0; - - private final WindowManagerService mService; - private InputEventReceiver mInputEventReceiver; - private DisplayContent mDisplayContent; - private Rect mTmpRect = new Rect(); - private int mMinVisibleWidth; - private int mMinVisibleHeight; - - @VisibleForTesting - Task mTask; - WindowState mWindow; - private boolean mResizing; - private boolean mPreserveOrientation; - private boolean mStartOrientationWasLandscape; - private final Rect mWindowOriginalBounds = new Rect(); - private final Rect mWindowDragBounds = new Rect(); - private final Point mMaxVisibleSize = new Point(); - private float mStartDragX; - private float mStartDragY; - @CtrlType - private int mCtrlType = CTRL_NONE; - @VisibleForTesting - boolean mDragEnded; - IBinder mClientCallback; - - InputChannel mClientChannel; - InputApplicationHandle mDragApplicationHandle; - InputWindowHandle mDragWindowHandle; - - /** Use {@link #create(WindowManagerService)} instead. */ - @VisibleForTesting - TaskPositioner(WindowManagerService service) { - mService = service; - } - - private boolean onInputEvent(InputEvent event) { - // All returns need to be in the try block to make sure the finishInputEvent is - // called correctly. - if (!(event instanceof MotionEvent) - || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { - return false; - } - final MotionEvent motionEvent = (MotionEvent) event; - if (mDragEnded) { - // The drag has ended but the clean-up message has not been processed by - // window manager. Drop events that occur after this until window manager - // has a chance to clean-up the input handle. - return true; - } - - final float newX = motionEvent.getRawX(); - final float newY = motionEvent.getRawY(); - - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); - } - } - break; - - case MotionEvent.ACTION_MOVE: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); - } - synchronized (mService.mGlobalLock) { - mDragEnded = notifyMoveLocked(newX, newY); - mTask.getDimBounds(mTmpRect); - } - if (!mTmpRect.equals(mWindowDragBounds)) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, - "wm.TaskPositioner.resizeTask"); - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - break; - - case MotionEvent.ACTION_UP: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } - break; - - case MotionEvent.ACTION_CANCEL: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } - break; - } - - if (mDragEnded) { - final boolean wasResizing = mResizing; - synchronized (mService.mGlobalLock) { - endDragLocked(); - mTask.getDimBounds(mTmpRect); - } - if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { - // We were using fullscreen surface during resizing. Request - // resizeTask() one last time to restore surface to window size. - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); - } - - // Post back to WM to handle clean-ups. We still need the input - // event handler for the last finishInputEvent()! - mService.mTaskPositioningController.finishTaskPositioning(); - } - return true; - } - - @VisibleForTesting - Rect getWindowDragBounds() { - return mWindowDragBounds; - } - - /** - * @param displayContent The Display that the window being dragged is on. - * @param win The window which will be dragged. - */ - CompletableFuture<Void> register(DisplayContent displayContent, @NonNull WindowState win) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "Registering task positioner"); - } - - if (mClientChannel != null) { - Slog.e(TAG, "Task positioner already registered"); - return completedFuture(null); - } - - mDisplayContent = displayContent; - mClientChannel = mService.mInputManager.createInputChannel(TAG); - - mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( - mClientChannel, mService.mAnimationHandler.getLooper(), - mService.mAnimator.getChoreographer(), this::onInputEvent); - - mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG, - DEFAULT_DISPATCHING_TIMEOUT_MILLIS); - - mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, - displayContent.getDisplayId()); - mDragWindowHandle.name = TAG; - mDragWindowHandle.token = mClientChannel.getToken(); - mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; - mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; - mDragWindowHandle.ownerPid = WindowManagerService.MY_PID; - mDragWindowHandle.ownerUid = WindowManagerService.MY_UID; - mDragWindowHandle.scaleFactor = 1.0f; - // When dragging the window around, we do not want to steal focus for the window. - mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE; - - // The drag window cannot receive new touches. - mDragWindowHandle.touchableRegion.setEmpty(); - - // Pause rotations before a drag. - ProtoLog.d(WM_DEBUG_ORIENTATION, "Pausing rotation during re-position"); - mDisplayContent.getDisplayRotation().pause(); - - // Notify InputMonitor to take mDragWindowHandle. - return mService.mTaskPositioningController.showInputSurface(win.getDisplayId()) - .thenRun(() -> { - // The global lock is held by the callers of register but released before the async - // results are waited on. We must acquire the lock in this callback to ensure thread - // safety. - synchronized (mService.mGlobalLock) { - final Rect displayBounds = mTmpRect; - displayContent.getBounds(displayBounds); - final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics(); - mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics); - mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics); - mMaxVisibleSize.set(displayBounds.width(), displayBounds.height()); - - mDragEnded = false; - - try { - mClientCallback = win.mClient.asBinder(); - mClientCallback.linkToDeath(this, 0 /* flags */); - } catch (RemoteException e) { - // The caller has died, so clean up TaskPositioningController. - mService.mTaskPositioningController.finishTaskPositioning(); - return; - } - mWindow = win; - mTask = win.getTask(); - } - }); - } - - void unregister() { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "Unregistering task positioner"); - } - - if (mClientChannel == null) { - Slog.e(TAG, "Task positioner not registered"); - return; - } - - mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId()); - mService.mInputManager.removeInputChannel(mClientChannel.getToken()); - - mInputEventReceiver.dispose(); - mInputEventReceiver = null; - mClientChannel.dispose(); - mClientChannel = null; - - mDragWindowHandle = null; - mDragApplicationHandle = null; - mDragEnded = true; - - // Notify InputMonitor to remove mDragWindowHandle. - mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); - - // Resume rotations after a drag. - ProtoLog.d(WM_DEBUG_ORIENTATION, "Resuming rotation after re-position"); - mDisplayContent.getDisplayRotation().resume(); - mDisplayContent = null; - if (mClientCallback != null) { - mClientCallback.unlinkToDeath(this, 0 /* flags */); - } - mWindow = null; - } - - /** - * Starts moving or resizing the task. This method should be only called from - * {@link TaskPositioningController#startPositioningLocked} or unit tests. - */ - void startDrag(boolean resize, boolean preserveOrientation, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "startDrag: win=" + mWindow + ", resize=" + resize - + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", " - + startY + "}"); - } - // Use the bounds of the task which accounts for - // multiple app windows. Don't use any bounds from win itself as it - // may not be the same size as the task. - final Rect startBounds = mTmpRect; - mTask.getBounds(startBounds); - - mCtrlType = CTRL_NONE; - mStartDragX = startX; - mStartDragY = startY; - mPreserveOrientation = preserveOrientation; - - if (resize) { - if (startX < startBounds.left) { - mCtrlType |= CTRL_LEFT; - } - if (startX > startBounds.right) { - mCtrlType |= CTRL_RIGHT; - } - if (startY < startBounds.top) { - mCtrlType |= CTRL_TOP; - } - if (startY > startBounds.bottom) { - mCtrlType |= CTRL_BOTTOM; - } - mResizing = mCtrlType != CTRL_NONE; - } - - // In case of !isDockedInEffect we are using the union of all task bounds. These might be - // made up out of multiple windows which are only partially overlapping. When that happens, - // the orientation from the window of interest to the entire stack might diverge. However - // for now we treat them as the same. - mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); - mWindowOriginalBounds.set(startBounds); - - // Notify the app that resizing has started, even though we haven't received any new - // bounds yet. This will guarantee that the app starts the backdrop renderer before - // configuration changes which could cause an activity restart. - if (mResizing) { - notifyMoveLocked(startX, startY); - - // The WindowPositionerEventReceiver callbacks are delivered on the same handler so this - // initial resize is always guaranteed to happen before subsequent drag resizes. - mService.mH.post(() -> { - mService.mAtmService.resizeTask( - mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED); - }); - } - - // Make sure we always have valid drag bounds even if the drag ends before any move events - // have been handled. - mWindowDragBounds.set(startBounds); - } - - private void endDragLocked() { - mResizing = false; - mTask.setDragResizing(false); - } - - /** Returns true if the move operation should be ended. */ - @VisibleForTesting - boolean notifyMoveLocked(float x, float y) { - if (DEBUG_TASK_POSITIONING) { - Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}"); - } - - if (mCtrlType != CTRL_NONE) { - resizeDrag(x, y); - mTask.setDragResizing(true); - return false; - } - - // This is a moving or scrolling operation. - // Only allow to move in stable area so the target window won't be covered by system bar. - // Though {@link Task#resolveOverrideConfiguration} should also avoid the case. - mDisplayContent.getStableRect(mTmpRect); - // The task may be put in a limited display area. - mTmpRect.intersect(mTask.getRootTask().getParent().getBounds()); - - int nX = (int) x; - int nY = (int) y; - if (!mTmpRect.contains(nX, nY)) { - // For a moving operation we allow the pointer to go out of the stack bounds, but - // use the clamped pointer position for the drag bounds computation. - nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right); - nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom); - } - - updateWindowDragBounds(nX, nY, mTmpRect); - return false; - } - - /** - * The user is drag - resizing the window. - * - * @param x The x coordinate of the current drag coordinate. - * @param y the y coordinate of the current drag coordinate. - */ - @VisibleForTesting - void resizeDrag(float x, float y) { - updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY, - mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight, - mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape)); - } - - private void updateDraggedBounds(Rect newBounds) { - mWindowDragBounds.set(newBounds); - - checkBoundsForOrientationViolations(mWindowDragBounds); - } - - /** - * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set). - * - * @param bounds The bounds to be checked. - */ - private void checkBoundsForOrientationViolations(Rect bounds) { - // When using debug check that we are not violating the given constraints. - if (DEBUG_ORIENTATION_VIOLATIONS) { - if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) { - Slog.e(TAG, "Orientation violation detected! should be " - + (mStartOrientationWasLandscape ? "landscape" : "portrait") - + " but is the other"); - } else { - Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height()); - } - if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) { - Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth - + ", " + bounds.width() + ") Height(min,is)=(" - + mMinVisibleHeight + ", " + bounds.height() + ")"); - } - if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) { - Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x - + ", " + bounds.width() + ") Height(min,is)=(" - + mMaxVisibleSize.y + ", " + bounds.height() + ")"); - } - } - } - - private void updateWindowDragBounds(int x, int y, Rect rootTaskBounds) { - final int offsetX = Math.round(x - mStartDragX); - final int offsetY = Math.round(y - mStartDragY); - mWindowDragBounds.set(mWindowOriginalBounds); - // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible. - final int maxLeft = rootTaskBounds.right - mMinVisibleWidth; - final int minLeft = rootTaskBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width(); - - // Vertically, the top mMinVisibleHeight of the window should remain visible. - // (This assumes that the window caption bar is at the top of the window). - final int minTop = rootTaskBounds.top; - final int maxTop = rootTaskBounds.bottom - mMinVisibleHeight; - - mWindowDragBounds.offsetTo( - Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft), - Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop)); - - if (DEBUG_TASK_POSITIONING) Slog.d(TAG, - "updateWindowDragBounds: " + mWindowDragBounds); - } - - public String toShortString() { - return TAG; - } - - static void setFactory(Factory factory) { - sFactory = factory; - } - - static TaskPositioner create(WindowManagerService service) { - if (sFactory == null) { - sFactory = new Factory() {}; - } - - return sFactory.create(service); - } - - @Override - public void binderDied() { - mService.mTaskPositioningController.finishTaskPositioning(); - } - - interface Factory { - default TaskPositioner create(WindowManagerService service) { - return new TaskPositioner(service); - } - } -} diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java deleted file mode 100644 index 6f548ab01d74..000000000000 --- a/services/core/java/com/android/server/wm/TaskPositioningController.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; -import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import android.annotation.Nullable; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Slog; -import android.view.Display; -import android.view.IWindow; -import android.view.InputWindowHandle; -import android.view.SurfaceControl; - -import java.util.concurrent.CompletableFuture; - -/** - * Controller for task positioning by drag. - */ -class TaskPositioningController { - private final WindowManagerService mService; - private SurfaceControl mInputSurface; - private DisplayContent mPositioningDisplay; - - private @Nullable TaskPositioner mTaskPositioner; - - private final Rect mTmpClipRect = new Rect(); - - boolean isPositioningLocked() { - return mTaskPositioner != null; - } - - final SurfaceControl.Transaction mTransaction; - - InputWindowHandle getDragWindowHandleLocked() { - return mTaskPositioner != null ? mTaskPositioner.mDragWindowHandle : null; - } - - TaskPositioningController(WindowManagerService service) { - mService = service; - mTransaction = service.mTransactionFactory.get(); - } - - void hideInputSurface(int displayId) { - if (mPositioningDisplay != null && mPositioningDisplay.getDisplayId() == displayId - && mInputSurface != null) { - mTransaction.hide(mInputSurface).apply(); - } - } - - /** - * @return a future that completes after window info is sent. - */ - CompletableFuture<Void> showInputSurface(int displayId) { - if (mPositioningDisplay == null || mPositioningDisplay.getDisplayId() != displayId) { - return completedFuture(null); - } - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); - if (mInputSurface == null) { - mInputSurface = mService.makeSurfaceBuilder(dc.getSession()) - .setContainerLayer() - .setName("Drag and Drop Input Consumer") - .setCallsite("TaskPositioningController.showInputSurface") - .setParent(dc.getOverlayLayer()) - .build(); - } - - final InputWindowHandle h = getDragWindowHandleLocked(); - if (h == null) { - Slog.w(TAG_WM, "Drag is in progress but there is no " - + "drag window handle."); - return completedFuture(null); - } - - final Display display = dc.getDisplay(); - final Point p = new Point(); - display.getRealSize(p); - mTmpClipRect.set(0, 0, p.x, p.y); - - CompletableFuture<Void> result = new CompletableFuture<>(); - mTransaction.show(mInputSurface) - .setInputWindowInfo(mInputSurface, h) - .setLayer(mInputSurface, Integer.MAX_VALUE) - .setPosition(mInputSurface, 0, 0) - .setCrop(mInputSurface, mTmpClipRect) - .addWindowInfosReportedListener(() -> result.complete(null)) - .apply(); - return result; - } - - boolean startMovingTask(IWindow window, float startX, float startY) { - WindowState win = null; - CompletableFuture<Boolean> startPositioningLockedFuture; - synchronized (mService.mGlobalLock) { - win = mService.windowForClientLocked(null, window, false); - startPositioningLockedFuture = - startPositioningLocked( - win, false /*resize*/, false /*preserveOrientation*/, startX, startY); - } - - try { - if (!startPositioningLockedFuture.get()) { - return false; - } - } catch (Exception exception) { - Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future", - exception); - return false; - } - - synchronized (mService.mGlobalLock) { - mService.mAtmService.setFocusedTask(win.getTask().mTaskId); - } - return true; - } - - void handleTapOutsideTask(DisplayContent displayContent, int x, int y) { - mService.mH.post(() -> { - Task task; - CompletableFuture<Boolean> startPositioningLockedFuture; - synchronized (mService.mGlobalLock) { - task = displayContent.findTaskForResizePoint(x, y); - if (task == null || !task.isResizeable()) { - // The task is not resizable, so don't do anything when the user drags the - // the resize handles. - return; - } - startPositioningLockedFuture = - startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/, - task.preserveOrientationOnResize(), x, y); - } - - try { - if (!startPositioningLockedFuture.get()) { - return; - } - } catch (Exception exception) { - Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future", - exception); - return; - } - - synchronized (mService.mGlobalLock) { - mService.mAtmService.setFocusedTask(task.mTaskId); - } - }); - } - - private CompletableFuture<Boolean> startPositioningLocked(WindowState win, boolean resize, - boolean preserveOrientation, float startX, float startY) { - if (DEBUG_TASK_POSITIONING) - Slog.d(TAG_WM, "startPositioningLocked: " - + "win=" + win + ", resize=" + resize + ", preserveOrientation=" - + preserveOrientation + ", {" + startX + ", " + startY + "}"); - - if (win == null || win.mActivityRecord == null) { - Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win); - return completedFuture(false); - } - if (win.mInputChannel == null) { - Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, " - + " probably being removed"); - return completedFuture(false); - } - - final DisplayContent displayContent = win.getDisplayContent(); - if (displayContent == null) { - Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win); - return completedFuture(false); - } - mPositioningDisplay = displayContent; - - mTaskPositioner = TaskPositioner.create(mService); - return mTaskPositioner.register(displayContent, win).thenApply(unused -> { - // The global lock is held by the callers of startPositioningLocked but released before - // the async results are waited on. We must acquire the lock in this callback to ensure - // thread safety. - synchronized (mService.mGlobalLock) { - // We need to grab the touch focus so that the touch events during the - // resizing/scrolling are not sent to the app. 'win' is the main window - // of the app, it may not have focus since there might be other windows - // on top (eg. a dialog window). - WindowState transferTouchFromWin = win; - if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win - && displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) { - transferTouchFromWin = displayContent.mCurrentFocus; - } - if (!mService.mInputManager.transferTouchGesture( - transferTouchFromWin.mInputChannel.getToken(), - mTaskPositioner.mClientChannel.getToken())) { - Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus"); - cleanUpTaskPositioner(); - return false; - } - - mTaskPositioner.startDrag(resize, preserveOrientation, startX, startY); - return true; - } - }); - } - - public void finishTaskPositioning(IWindow window) { - if (mTaskPositioner != null && mTaskPositioner.mClientCallback == window.asBinder()) { - finishTaskPositioning(); - } - } - - void finishTaskPositioning() { - // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to - // dispose the receiver on the same thread to avoid race conditions. - mService.mAnimationHandler.post(() -> { - if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning"); - - synchronized (mService.mGlobalLock) { - cleanUpTaskPositioner(); - mPositioningDisplay = null; - } - }); - } - - private void cleanUpTaskPositioner() { - final TaskPositioner positioner = mTaskPositioner; - if (positioner == null) { - return; - } - - // We need to assign task positioner to null first to indicate that we're finishing task - // positioning. - mTaskPositioner = null; - positioner.unregister(); - } -} diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f6a68d58ea27..65bc9a226f55 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -745,6 +745,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mController.isAnimating()) { dc.enableHighPerfTransition(true); } + mController.dispatchLegacyAppTransitionPending(dc.mDisplayId); } /** @@ -1618,7 +1619,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mTransitionTracer.logAbortedTransition(this); // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); - mController.dispatchLegacyAppTransitionCancelled(); + mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays); invokeTransitionEndedListeners(); } @@ -1766,7 +1767,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } for (int i = 0; i < mTargets.size(); ++i) { - final DisplayArea da = mTargets.get(i).mContainer.asDisplayArea(); + final WindowContainer<?> wc = mTargets.get(i).mContainer; + final WallpaperWindowToken wp = wc.asWallpaperToken(); + if (wp != null) { + // If on a rotation leash, the wallpaper token surface needs to be shown explicitly + // because shell only gets the leash and the wallpaper token surface is not allowed + // to be changed by non-transition logic until the transition is finished. + if (Flags.ensureWallpaperInTransitions() && wp.isVisibleRequested() + && wp.getFixedRotationLeash() != null) { + transaction.show(wp.mSurfaceControl); + } + continue; + } + final DisplayArea<?> da = wc.asDisplayArea(); if (da == null) continue; if (da.isVisibleRequested()) { mController.mValidateDisplayVis.remove(da); @@ -2168,14 +2181,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { && !wallpaperIsOwnTarget(wallpaper)) { wallpaper.setVisibleRequested(false); } - if (showWallpaper && Flags.ensureWallpaperInTransitions() - && wallpaper.isVisibleRequested() - && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) { - // If on a rotation leash, we need to explicitly show the wallpaper surface - // because shell only gets the leash and we don't allow non-transition logic - // to touch the surfaces until the transition is over. - t.show(wallpaper.getSurfaceControl()); - } } } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f4ff404c2bff..1df251cf3225 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -42,6 +42,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; +import android.view.Display; import android.view.WindowManager; import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; @@ -326,7 +327,6 @@ class TransitionController { mCollectingTransition.startCollecting(timeoutMs); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s", mCollectingTransition); - dispatchLegacyAppTransitionPending(); } void registerTransitionPlayer(@Nullable ITransitionPlayer player, @@ -1347,31 +1347,54 @@ class TransitionController { mLegacyListeners.remove(listener); } - void dispatchLegacyAppTransitionPending() { + private static boolean shouldDispatchLegacyListener( + WindowManagerInternal.AppTransitionListener listener, int displayId) { + // INVALID_DISPLAY means that it is a global listener. + return listener.mDisplayId == Display.INVALID_DISPLAY || listener.mDisplayId == displayId; + } + + void dispatchLegacyAppTransitionPending(int displayId) { for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionPendingLocked(); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionPendingLocked(); + } } } void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) { + final long now = SystemClock.uptimeMillis(); for (int i = 0; i < mLegacyListeners.size(); ++i) { - // TODO(shell-transitions): handle (un)occlude transition. - mLegacyListeners.get(i).onAppTransitionStartingLocked( - SystemClock.uptimeMillis() + statusBarTransitionDelay, - AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + for (int j = 0; j < info.getRootCount(); ++j) { + final int displayId = info.getRoot(j).getDisplayId(); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionStartingLocked( + now + statusBarTransitionDelay, + AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + } + } } } void dispatchLegacyAppTransitionFinished(ActivityRecord ar) { for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token); + final WindowManagerInternal.AppTransitionListener listener = mLegacyListeners.get(i); + if (shouldDispatchLegacyListener(listener, ar.getDisplayId())) { + listener.onAppTransitionFinishedLocked(ar.token); + } } } - void dispatchLegacyAppTransitionCancelled() { - for (int i = 0; i < mLegacyListeners.size(); ++i) { - mLegacyListeners.get(i).onAppTransitionCancelledLocked( - false /* keyguardGoingAwayCancelled */); + void dispatchLegacyAppTransitionCancelled(ArrayList<DisplayContent> targetDisplays) { + for (int i = 0; i < targetDisplays.size(); ++i) { + final int displayId = targetDisplays.get(i).mDisplayId; + for (int j = 0; j < mLegacyListeners.size(); ++j) { + final var listener = mLegacyListeners.get(j); + if (shouldDispatchLegacyListener(listener, displayId)) { + listener.onAppTransitionCancelledLocked(false /* keyguardGoingAwayCancelled */); + } + } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index 42b556f77ab6..61253602c066 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -47,7 +47,6 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_LAYOUT_REPEATS = false; static final boolean DEBUG_WINDOW_TRACE = false; static final boolean DEBUG_TASK_MOVEMENT = false; - static final boolean DEBUG_TASK_POSITIONING = false; static final boolean DEBUG_ROOT_TASK = false; static final boolean DEBUG_DISPLAY = false; static final boolean DEBUG_POWER = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 2ea1cf88447a..132e1ee99914 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -244,6 +244,22 @@ public abstract class WindowManagerInternal { public static abstract class AppTransitionListener { /** + * The display this listener is interested in. If it is INVALID_DISPLAY, then which display + * should be notified depends on the dispatcher. + */ + public final int mDisplayId; + + /** Let transition controller decide which display should receive the callbacks. */ + public AppTransitionListener() { + this(Display.INVALID_DISPLAY); + } + + /** It will listen the transition on the given display. */ + public AppTransitionListener(int displayId) { + mDisplayId = displayId; + } + + /** * Called when an app transition is being setup and about to be executed. */ public void onAppTransitionPendingLocked() {} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index acd8b3f1dbc3..f65eea0797bf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -54,7 +54,6 @@ import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; @@ -97,6 +96,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; +import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; @@ -1070,7 +1070,6 @@ public class WindowManagerService extends IWindowManager.Stub /** Whether or not a layout can cause a wake up when theater mode is enabled. */ boolean mAllowTheaterModeWakeFromLayout; - final TaskPositioningController mTaskPositioningController; final DragDropController mDragDropController; /** For frozen screen animations. */ @@ -1428,7 +1427,6 @@ public class WindowManagerService extends IWindowManager.Stub mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); - mTaskPositioningController = new TaskPositioningController(this); mDragDropController = new DragDropController(this, mH.getLooper()); mHighRefreshRateDenylist = HighRefreshRateDenylist.create(context.getResources()); @@ -9379,40 +9377,82 @@ public class WindowManagerService extends IWindowManager.Stub } /** - * Move focus to the adjacent embedded activity if the adjacent activity is more recently - * created or has a window more recently added. + * Returns the Activity that has the most recently created window in the adjacent activities + * if any. */ - boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) { - final TaskFragment taskFragment = focusedWindow.getTaskFragment(); + @NonNull + ActivityRecord getMostRecentActivityInAdjacent(@NonNull ActivityRecord focusedActivity) { + final TaskFragment taskFragment = focusedActivity.getTaskFragment(); if (taskFragment == null) { - // Skip if not an Activity window. - return false; + // Return if activity no attached. + return focusedActivity; } if (!Flags.embeddedActivityBackNavFlag()) { - // Skip if flag is not enabled. - return false; + // Return if flag is not enabled. + return focusedActivity; } - if (!focusedWindow.mActivityRecord.isEmbedded()) { - // Skip if the focused activity is not embedded - return false; + if (!focusedActivity.isEmbedded()) { + // Return if the focused activity is not embedded. + return focusedActivity; } final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment(); final ActivityRecord adjacentTopActivity = adjacentTaskFragment != null ? adjacentTaskFragment.topRunningActivity() : null; if (adjacentTopActivity == null) { - return false; + // Return if no adjacent activity. + return focusedActivity; } if (adjacentTopActivity.getLastWindowCreateTime() - < focusedWindow.mActivityRecord.getLastWindowCreateTime()) { - // Skip if the current focus activity has more recently active window. + < focusedActivity.getLastWindowCreateTime()) { + // Return if the current focus activity has more recently active window. + return focusedActivity; + } + + return adjacentTopActivity; + } + + @NonNull + WindowState getMostRecentUsedEmbeddedWindowForBack(@NonNull WindowState focusedWindow) { + final ActivityRecord focusedActivity = focusedWindow.getActivityRecord(); + if (focusedActivity == null) { + // Not an Activity. + return focusedWindow; + } + + final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent( + focusedActivity); + if (mostRecentActivityInAdjacent == focusedActivity) { + // Already be the most recent window. + return focusedWindow; + } + + // Looks for a candidate focused window on the adjacent Activity for the back event. + final WindowState candidate = + mostRecentActivityInAdjacent.getDisplayContent().findFocusedWindow( + mostRecentActivityInAdjacent); + return candidate != null ? candidate : focusedWindow; + } + + /** + * Move focus to the adjacent embedded activity if the adjacent activity is more recently + * created or has a window more recently added. + * <p> + * Returns {@code true} if the focused window is changed. Otherwise, returns {@code false}. + */ + boolean moveFocusToAdjacentEmbeddedWindow(@NonNull WindowState focusedWindow) { + final ActivityRecord activity = focusedWindow.getActivityRecord(); + if (activity == null) { return false; } - moveFocusToActivity(adjacentTopActivity); + final ActivityRecord mostRecentActivityInAdjacent = getMostRecentActivityInAdjacent( + activity); + + moveFocusToActivity(mostRecentActivityInAdjacent); return !focusedWindow.isFocused(); } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 12c50739f66b..984caf1c692b 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1674,6 +1674,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Otherwise if other places send wpc.getConfiguration() to client, the configuration may // be ignored due to the seq is older. resolvedConfig.seq = newParentConfig.seq; + + if (mConfigActivityRecord != null) { + // Let the activity decide whether to apply the size override. + return; + } + final DisplayContent displayContent = mAtm.mWindowManager != null + ? mAtm.mWindowManager.getDefaultDisplayContentLocked() + : null; + applySizeOverrideIfNeeded( + displayContent, + mInfo, + newParentConfig, + resolvedConfig, + false /* optsOutEdgeToEdge */, + false /* hasFixedRotationTransform */, + false /* hasCompatDisplayInsets */); } void dispatchConfiguration(@NonNull Configuration config) { diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 21f7eca5627a..04d5c03e64a6 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -56,7 +56,10 @@ abstract class WindowTracing { static WindowTracing createDefaultAndStartLooper(WindowManagerService service, Choreographer choreographer) { - return new WindowTracingLegacy(service, choreographer); + if (!android.tracing.Flags.perfettoWmTracing()) { + return new WindowTracingLegacy(service, choreographer); + } + return new WindowTracingPerfetto(service, choreographer); } protected WindowTracing(WindowManagerService service, Choreographer choreographer, diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java new file mode 100644 index 000000000000..3d2c0d3f79b9 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java @@ -0,0 +1,204 @@ +/* + * 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.wm; + +import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT; + +import android.annotation.NonNull; +import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig; +import android.internal.perfetto.protos.WindowmanagerConfig.WindowManagerConfig; +import android.tracing.perfetto.CreateTlsStateArgs; +import android.tracing.perfetto.DataSource; +import android.tracing.perfetto.DataSourceInstance; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.tracing.perfetto.StartCallbackArguments; +import android.tracing.perfetto.StopCallbackArguments; +import android.util.Log; +import android.util.proto.ProtoInputStream; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +public final class WindowTracingDataSource extends DataSource<WindowTracingDataSource.Instance, + WindowTracingDataSource.TlsState, Void> { + public static final String DATA_SOURCE_NAME = "android.windowmanager"; + + public static class TlsState { + public final Config mConfig; + public final AtomicBoolean mIsStarting = new AtomicBoolean(true); + + private TlsState(Config config) { + mConfig = config; + } + } + + public static class Config { + public final @WindowTraceLogLevel int mLogLevel; + public final boolean mLogOnFrame; + + private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) { + mLogLevel = logLevel; + mLogOnFrame = logOnFrame; + } + } + + public abstract static class Instance extends DataSourceInstance { + public final Config mConfig; + + public Instance(DataSource dataSource, int instanceIndex, Config config) { + super(dataSource, instanceIndex); + mConfig = config; + } + } + + private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true); + private static final int CONFIG_VALUE_UNSPECIFIED = 0; + private static final String TAG = "WindowTracingDataSource"; + + @NonNull + private final Consumer<Config> mOnStartCallback; + @NonNull + private final Consumer<Config> mOnStopCallback; + + public WindowTracingDataSource(@NonNull Consumer<Config> onStart, + @NonNull Consumer<Config> onStop) { + super(DATA_SOURCE_NAME); + mOnStartCallback = onStart; + mOnStopCallback = onStop; + + Producer.init(InitArguments.DEFAULTS); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + register(params); + } + + @Override + public Instance createInstance(ProtoInputStream configStream, int instanceIndex) { + final Config config = parseDataSourceConfig(configStream); + + return new Instance(this, instanceIndex, config != null ? config : CONFIG_DEFAULT) { + @Override + protected void onStart(StartCallbackArguments args) { + mOnStartCallback.accept(mConfig); + } + + @Override + protected void onStop(StopCallbackArguments args) { + mOnStopCallback.accept(mConfig); + } + }; + } + + @Override + public TlsState createTlsState( + CreateTlsStateArgs<Instance> args) { + try (Instance dsInstance = args.getDataSourceInstanceLocked()) { + if (dsInstance == null) { + // Datasource instance has been removed + return new TlsState(CONFIG_DEFAULT); + } + return new TlsState(dsInstance.mConfig); + } + } + + private Config parseDataSourceConfig(ProtoInputStream stream) { + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() != (int) DataSourceConfig.WINDOWMANAGER_CONFIG) { + continue; + } + return parseWindowManagerConfig(stream); + } + Log.w(TAG, "Received start request without config parameters. Will use defaults."); + } catch (IOException e) { + throw new RuntimeException("Failed to parse DataSourceConfig", e); + } + return null; + } + + private Config parseWindowManagerConfig(ProtoInputStream stream) { + int parsedLogLevel = CONFIG_VALUE_UNSPECIFIED; + int parsedLogFrequency = CONFIG_VALUE_UNSPECIFIED; + + try { + final long token = stream.start(DataSourceConfig.WINDOWMANAGER_CONFIG); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) WindowManagerConfig.LOG_LEVEL: + parsedLogLevel = stream.readInt(WindowManagerConfig.LOG_LEVEL); + break; + case (int) WindowManagerConfig.LOG_FREQUENCY: + parsedLogFrequency = stream.readInt(WindowManagerConfig.LOG_FREQUENCY); + break; + default: + Log.w(TAG, "Unrecognized WindowManagerConfig field number: " + + stream.getFieldNumber()); + } + } + stream.end(token); + } catch (IOException e) { + throw new RuntimeException("Failed to parse WindowManagerConfig", e); + } + + @WindowTraceLogLevel int logLevel; + switch(parsedLogLevel) { + case CONFIG_VALUE_UNSPECIFIED: + Log.w(TAG, "Unspecified log level. Defaulting to TRIM"); + logLevel = WindowTraceLogLevel.TRIM; + break; + case WindowManagerConfig.LOG_LEVEL_VERBOSE: + logLevel = WindowTraceLogLevel.ALL; + break; + case WindowManagerConfig.LOG_LEVEL_DEBUG: + logLevel = WindowTraceLogLevel.TRIM; + break; + case WindowManagerConfig.LOG_LEVEL_CRITICAL: + logLevel = WindowTraceLogLevel.CRITICAL; + break; + default: + Log.w(TAG, "Unrecognized log level. Defaulting to TRIM"); + logLevel = WindowTraceLogLevel.TRIM; + break; + } + + boolean logOnFrame; + switch(parsedLogFrequency) { + case CONFIG_VALUE_UNSPECIFIED: + Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'"); + logOnFrame = true; + break; + case WindowManagerConfig.LOG_FREQUENCY_FRAME: + logOnFrame = true; + break; + case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION: + logOnFrame = false; + break; + default: + Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'"); + logOnFrame = true; + break; + } + + return new Config(logLevel, logOnFrame); + } +} diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java new file mode 100644 index 000000000000..653b6dac1537 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java @@ -0,0 +1,163 @@ +/* + * 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.wm; + +import android.annotation.Nullable; +import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket; +import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl; +import android.os.ShellCommand; +import android.os.SystemClock; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Choreographer; + +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicInteger; + +class WindowTracingPerfetto extends WindowTracing { + private static final String TAG = "WindowTracing"; + + private final AtomicInteger mCountSessionsOnFrame = new AtomicInteger(); + private final AtomicInteger mCountSessionsOnTransaction = new AtomicInteger(); + private final WindowTracingDataSource mDataSource = new WindowTracingDataSource( + this::onStart, this::onStop); + + WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) { + super(service, choreographer, service.mGlobalLock); + } + + @Override + void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { + logAndPrintln(pw, "Log level must be configured through perfetto"); + } + + @Override + void setLogFrequency(boolean onFrame, PrintWriter pw) { + logAndPrintln(pw, "Log frequency must be configured through perfetto"); + } + + @Override + void setBufferCapacity(int capacity, PrintWriter pw) { + logAndPrintln(pw, "Buffer capacity must be configured through perfetto"); + } + + @Override + boolean isEnabled() { + return (mCountSessionsOnFrame.get() + mCountSessionsOnTransaction.get()) > 0; + } + + @Override + int onShellCommand(ShellCommand shell) { + PrintWriter pw = shell.getOutPrintWriter(); + pw.println("Shell commands are ignored." + + " Any type of action should be performed through perfetto."); + return -1; + } + + @Override + String getStatus() { + return "Status: " + + ((isEnabled()) ? "Enabled" : "Disabled") + + "\n" + + "Sessions logging 'on frame': " + mCountSessionsOnFrame.get() + + "\n" + + "Sessions logging 'on transaction': " + mCountSessionsOnTransaction.get() + + "\n"; + } + + @Override + protected void startTraceInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing must be started through perfetto"); + } + + @Override + protected void stopTraceInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing must be stopped through perfetto"); + } + + @Override + protected void saveForBugreportInternal(@Nullable PrintWriter pw) { + logAndPrintln(pw, "Tracing snapshot for bugreport must be handled through perfetto"); + } + + @Override + protected void log(String where) { + try { + boolean isStartLogEvent = where == WHERE_START_TRACING; + boolean isOnFrameLogEvent = where == WHERE_ON_FRAME; + + mDataSource.trace((context) -> { + WindowTracingDataSource.Config dataSourceConfig = + context.getCustomTlsState().mConfig; + + if (isStartLogEvent) { + boolean isDataSourceStarting = context.getCustomTlsState() + .mIsStarting.compareAndSet(true, false); + if (!isDataSourceStarting) { + return; + } + } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) { + return; + } + + ProtoOutputStream os = context.newTracePacket(); + long timestamp = SystemClock.elapsedRealtimeNanos(); + os.write(TracePacket.TIMESTAMP, timestamp); + final long tokenWinscopeExtensions = + os.start(TracePacket.WINSCOPE_EXTENSIONS); + final long tokenExtensionsField = + os.start(WinscopeExtensionsImpl.WINDOWMANAGER); + dumpToProto(os, dataSourceConfig.mLogLevel, where, timestamp); + os.end(tokenExtensionsField); + os.end(tokenWinscopeExtensions); + }); + } catch (Exception e) { + Log.wtf(TAG, "Exception while tracing state", e); + } + } + + @Override + protected boolean shouldLogOnFrame() { + return mCountSessionsOnFrame.get() > 0; + } + + @Override + protected boolean shouldLogOnTransaction() { + return mCountSessionsOnTransaction.get() > 0; + } + + private void onStart(WindowTracingDataSource.Config config) { + if (config.mLogOnFrame) { + mCountSessionsOnFrame.incrementAndGet(); + } else { + mCountSessionsOnTransaction.incrementAndGet(); + } + + Log.i(TAG, "Started with logLevel: " + config.mLogLevel + + " logOnFrame: " + config.mLogOnFrame); + log(WHERE_START_TRACING); + } + + private void onStop(WindowTracingDataSource.Config config) { + if (config.mLogOnFrame) { + mCountSessionsOnFrame.decrementAndGet(); + } else { + mCountSessionsOnTransaction.decrementAndGet(); + } + Log.i(TAG, "Stopped"); + } +} diff --git a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java index 4211764085b1..3559e620a350 100644 --- a/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java +++ b/services/core/java/com/android/server/wm/utils/DesktopModeFlagsUtil.java @@ -47,16 +47,13 @@ public enum DesktopModeFlagsUtil { Flags::enableDesktopWindowingWallpaperActivity, /* shouldOverrideByDevOption= */ true); private static final String TAG = "DesktopModeFlagsUtil"; - private static final String SYSTEM_PROPERTY_OVERRIDE_KEY = - "sys.wmshell.desktopmode.dev_toggle_override"; - // Function called to obtain aconfig flag value. private final Supplier<Boolean> mFlagFunction; // Whether the flag state should be affected by developer option. private final boolean mShouldOverrideByDevOption; // Local cache for toggle override, which is initialized once on its first access. It needs to - // be refreshed only on reboots as overridden state takes effect on reboots. + // be refreshed only on reboots as overridden state is expected to take effect on reboots. private static ToggleOverride sCachedToggleOverride; DesktopModeFlagsUtil(Supplier<Boolean> flagFunction, boolean shouldOverrideByDevOption) { @@ -67,9 +64,6 @@ public enum DesktopModeFlagsUtil { /** * Determines state of flag based on the actual flag and desktop mode developer option * overrides. - * - * Note: this method makes sure that a constant developer toggle overrides is read until - * reboot. */ public boolean isEnabled(Context context) { if (!Flags.showDesktopWindowingDevOption() @@ -102,49 +96,15 @@ public enum DesktopModeFlagsUtil { } /** - * Returns {@link ToggleOverride} from a non-persistent system property if present. Otherwise - * initializes the system property by reading Settings.Global. + * Returns {@link ToggleOverride} from Settings.Global set by toggle. */ private ToggleOverride getToggleOverrideFromSystem(Context context) { - // A non-persistent System Property is used to store override to ensure it remains - // constant till reboot. - String overrideProperty = System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, null); - ToggleOverride overrideFromSystemProperties = convertToToggleOverride(overrideProperty); - - // If valid system property, return it - if (overrideFromSystemProperties != null) { - return overrideFromSystemProperties; - } - - // Fallback when System Property is not present (just after reboot) or not valid (user - // manually changed the value): Read from Settings.Global int settingValue = Settings.Global.getInt( context.getContentResolver(), Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, OVERRIDE_UNSET.getSetting() ); - ToggleOverride overrideFromSettingsGlobal = - ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET); - // Initialize System Property - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(settingValue)); - return overrideFromSettingsGlobal; - } - - /** - * Converts {@code intString} into {@link ToggleOverride}. Return {@code null} if - * {@code intString} does not correspond to a {@link ToggleOverride}. - */ - private static @Nullable ToggleOverride convertToToggleOverride( - @Nullable String intString - ) { - if (intString == null) return null; - try { - int intValue = Integer.parseInt(intString); - return ToggleOverride.fromSetting(intValue, null); - } catch (NumberFormatException e) { - Log.w(TAG, "Unknown toggleOverride int " + intString); - return null; - } + return ToggleOverride.fromSetting(settingValue, OVERRIDE_UNSET); } /** Override state of desktop mode developer option toggle. */ diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8cc7383b9bf6..7cb8ace697ec 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1378,7 +1378,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Clear always-on configuration if it wasn't set by the admin. if (adminConfiguredVpnPkg == null) { - mInjector.getVpnManager().setAlwaysOnVpnPackageForUser(userId, null, false, null); + VpnManager vpnManager = mInjector.getVpnManager(); + if (vpnManager != null) { + vpnManager.setAlwaysOnVpnPackageForUser(userId, null, false, null); + } } // Clear app authorizations to establish VPNs. When DISALLOW_CONFIG_VPN is enforced apps @@ -1789,6 +1792,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mContext.getSystemService(ConnectivityManager.class); } + @Nullable VpnManager getVpnManager() { return mContext.getSystemService(VpnManager.class); } @@ -7704,8 +7708,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } // If some package is uninstalled after the check above, it will be ignored by CM. - if (!mInjector.getVpnManager().setAlwaysOnVpnPackageForUser( - userId, vpnPackage, lockdown, lockdownAllowlist)) { + VpnManager vpnManager = mInjector.getVpnManager(); + if (vpnManager == null + || !mInjector.getVpnManager().setAlwaysOnVpnPackageForUser( + userId, vpnPackage, lockdown, lockdownAllowlist)) { throw new UnsupportedOperationException(); } }); @@ -7753,8 +7759,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + VpnManager vpnManager = mInjector.getVpnManager(); + if (vpnManager == null) { + return null; + } return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId())); + () -> vpnManager.getAlwaysOnVpnPackageForUser(caller.getUserId())); } @Override @@ -7781,8 +7791,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } + VpnManager vpnManager = mInjector.getVpnManager(); + if (vpnManager == null) { + return false; + } return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getVpnManager().isVpnLockdownEnabled(caller.getUserId())); + () -> vpnManager.isVpnLockdownEnabled(caller.getUserId())); } @Override @@ -7804,8 +7818,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + VpnManager vpnManager = mInjector.getVpnManager(); + if (vpnManager == null) { + return null; + } return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId())); + () -> vpnManager.getVpnLockdownAllowlist(caller.getUserId())); } private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java index cbd28475bc26..6e038f9b67a0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.VerifierDeviceIdentity; import android.net.wifi.WifiManager; import android.os.Build; @@ -77,13 +78,14 @@ class EnterpriseSpecificIdCalculator { mMeid = meid; mSerialNumber = Build.getSerial(); WifiManager wifiManager = context.getSystemService(WifiManager.class); - Preconditions.checkState(wifiManager != null, "Unable to access WiFi service"); - final String[] macAddresses = wifiManager.getFactoryMacAddresses(); - if (macAddresses == null || macAddresses.length == 0) { - mMacAddress = ""; - } else { - mMacAddress = macAddresses[0]; + String macAddress = ""; + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { + final String[] macAddresses = wifiManager.getFactoryMacAddresses(); + if (macAddresses != null && macAddresses.length > 0) { + macAddress = macAddresses[0]; + } } + mMacAddress = macAddress; } private static String getPaddedTruncatedString(String input, int maxLength) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index db4b171152a7..9e8811f419a2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -153,6 +153,7 @@ import com.android.server.contentsuggestions.ContentSuggestionsManagerService; import com.android.server.contextualsearch.ContextualSearchManagerService; import com.android.server.coverage.CoverageService; import com.android.server.cpu.CpuMonitorService; +import com.android.server.crashrecovery.CrashRecoveryModule; import com.android.server.credentials.CredentialManagerService; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.devicepolicy.DevicePolicyManagerService; @@ -381,8 +382,6 @@ public final class SystemServer implements Dumpable { + "OnDevicePersonalizationSystemService$Lifecycle"; private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS = "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle"; - private static final String CRASHRECOVERY_MODULE_LIFECYCLE_CLASS = - "com.android.server.crashrecovery.CrashRecoveryModule$Lifecycle"; /* @@ -2939,7 +2938,7 @@ public final class SystemServer implements Dumpable { if (Flags.refactorCrashrecovery()) { t.traceBegin("StartCrashRecoveryModule"); - mSystemServiceManager.startService(CRASHRECOVERY_MODULE_LIFECYCLE_CLASS); + mSystemServiceManager.startService(CrashRecoveryModule.Lifecycle.class); t.traceEnd(); } else { if (Flags.recoverabilityDetection()) { diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index 996daf5a5f68..95ee958f3ce4 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -19,6 +19,7 @@ package com.android.server.permission.access.util import android.os.FileUtils import android.util.AtomicFile import android.util.Slog +import com.android.server.security.FileIntegrity; import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -49,6 +50,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { writeInlined(block) val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") + reserveFile.delete() try { FileInputStream(baseFile).use { inputStream -> FileOutputStream(reserveFile).use { outputStream -> @@ -59,6 +61,12 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } catch (e: Exception) { Slog.e("AccessPersistence", "Failed to write $reserveFile", e) } + try { + FileIntegrity.setUpFsVerity(baseFile) + FileIntegrity.setUpFsVerity(reserveFile) + } catch (e: Exception) { + Slog.e("AccessPersistence", "Failed to verity-protect runtime-permissions", e) + } } /** Write to an [AtomicFile] and close everything safely when done. */ diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 3ed6ad78343b..acdbbdee7d67 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -398,13 +398,13 @@ public final class ProfcollectForwardingService extends SystemService { if (randomNum >= traceFrequency) { return; } - // For a small percentage a traces, we collect the initialization behavior. - boolean traceInitialization = ThreadLocalRandom.current().nextInt(10) < 1; - int traceDelay = traceInitialization ? 0 : 1000; - String traceTag = traceInitialization ? "camera_init" : "camera"; + final int traceDelay = 1000; + final int traceDuration = 5000; + final String traceTag = "camera"; BackgroundThread.get().getThreadHandler().postDelayed(() -> { try { - mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider"); + mIProfcollect.trace_process(traceTag, "android.hardware.camera.provider", + traceDuration); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage()); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java index 63224bb2aa3f..c54ff5fb2f69 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/AdditionalSubtypeUtilsTest.java @@ -25,7 +25,7 @@ import android.util.AtomicFile; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; @@ -54,7 +54,8 @@ public final class AdditionalSubtypeUtilsTest { // Save & load. AtomicFile atomicFile = new AtomicFile( - new File(InstrumentationRegistry.getContext().getCacheDir(), "subtypes.xml")); + new File(InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), + "subtypes.xml")); AdditionalSubtypeUtils.saveToFile(AdditionalSubtypeMap.of(allSubtypes), InputMethodMap.of(methodMap), atomicFile); AdditionalSubtypeMap loadedSubtypes = AdditionalSubtypeUtils.loadFromFile(atomicFile); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 267ce26515d7..ec9bfa7200c6 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -59,6 +59,7 @@ import android.window.ImeOnBackInvokedDispatcher; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IInputMethodSession; @@ -269,8 +270,15 @@ public class InputMethodManagerServiceTestBase { LocalServices.removeServiceForTest(InputMethodManagerInternal.class); lifecycle.onStart(); - // Emulate that the user initialization is done. + // Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml. + // TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it. AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId); + final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext, + mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(mCallingUserId, settings); + + // Emulate that the user initialization is done. mInputMethodManagerService.getUserData(mCallingUserId).mBackgroundLoadLatch.countDown(); // After this boot phase, services can broadcast Intents. @@ -283,6 +291,8 @@ public class InputMethodManagerServiceTestBase { @After public void tearDown() { + InputMethodSettingsRepository.remove(mCallingUserId); + if (mInputMethodManagerService != null) { mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 2857619c70d3..3cf895ee7204 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -40,7 +40,7 @@ import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import androidx.annotation.NonNull; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.inputmethod.StartInputFlags; diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java index 05c243fda2b8..36baacc529d5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -301,7 +303,7 @@ public class BrightnessThrottlerTest { new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(List.of(level)); final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); - final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); + final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); final BrightnessThrottler throttler = createThrottlerSupportedWithTempSensor(data, tempSensor); assertTrue(throttler.deviceSupportsThrottling()); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index d2686372f550..2b03dc4f78ad 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -31,6 +31,7 @@ import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.server.display.ExternalDisplayPolicy.ENABLE_ON_CONNECT; import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -2423,7 +2424,7 @@ public class DisplayManagerServiceTest { String testSensorType = "testType"; Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName); - SensorData sensorData = new SensorData(testSensorType, testSensorName, + SensorData sensorData = createSensorData(testSensorType, testSensorName, /* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f); when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index c6aea5a290e9..8ed38a6d0cad 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -85,7 +86,6 @@ import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; import com.android.server.display.config.HighBrightnessModeData; import com.android.server.display.config.HysteresisLevels; -import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.layout.Layout; @@ -2159,13 +2159,13 @@ public final class DisplayPowerControllerTest { when(displayDeviceMock.getNameLocked()).thenReturn(displayName); when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); when(displayDeviceConfigMock.getProximitySensor()).thenReturn( - new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); + createSensorData(Sensor.STRING_TYPE_PROXIMITY)); when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( - new SensorData()); + createSensorData()); when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( - new SensorData(Sensor.STRING_TYPE_LIGHT, null)); + createSensorData(Sensor.STRING_TYPE_LIGHT)); when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) .thenReturn(new int[0]); when(displayDeviceConfigMock.getDefaultDozeBrightness()) diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java index ebd6614aba14..29f07227a12d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.display; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,7 +38,6 @@ import android.view.Display; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.server.display.config.SensorData; import com.android.server.testutils.OffsettableClock; import org.junit.Before; @@ -75,7 +75,7 @@ public final class DisplayPowerProximityStateControllerTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); when(mDisplayDeviceConfig.getProximitySensor()).thenReturn( - new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); + createSensorData(Sensor.STRING_TYPE_PROXIMITY)); setUpProxSensor(); DisplayPowerProximityStateController.Injector injector = new DisplayPowerProximityStateController.Injector() { @@ -165,7 +165,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData()); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(), mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY, @@ -176,7 +176,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault() throws Exception { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData()); when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn( TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity")); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( @@ -189,7 +189,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay() throws Exception { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(createSensorData()); when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn( TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity")); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( @@ -216,7 +216,7 @@ public final class DisplayPowerProximityStateControllerTest { public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception { DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class); when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn( - new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); + createSensorData(Sensor.STRING_TYPE_PROXIMITY)); Sensor newProxSensor = TestUtils.createSensor( Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f); when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))) diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index e04716ed80f6..0ce9233b4b6f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -353,7 +353,8 @@ public class BrightnessClamperControllerTest { public void test_notifiesExternalListener_aggregatedStateChanged() { doAnswer((invocation) -> { ModifiersAggregatedState argument = invocation.getArgument(0); - argument.mHdrHbmEnabled = true; + // we need to do changes in AggregatedState to trigger onChange + argument.mMaxHdrBrightness = 0.5f; return null; }).when(mMockStatefulModifier).applyStateChange(any()); mTestInjector.mCapturedChangeListener.onChanged(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java index 34f352e7bf54..9d16594fae93 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java @@ -16,6 +16,8 @@ package com.android.server.display.brightness.clamper; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -189,7 +191,7 @@ public class BrightnessThermalClamperTest { final int severity = PowerManager.THERMAL_STATUS_SEVERE; IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); // Update config to listen to display type sensor. - final SensorData tempSensor = new SensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); + final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); final TestThermalData thermalData = new TestThermalData( DISPLAY_ID, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt index e9ec8112c399..0ed96ae52491 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt @@ -18,29 +18,39 @@ package com.android.server.display.brightness.clamper import android.hardware.display.DisplayManagerInternal import android.os.IBinder +import android.os.PowerManager.BRIGHTNESS_MAX import android.util.Spline import android.view.SurfaceControlHdrLayerInfoListener import androidx.test.filters.SmallTest import com.android.server.display.DisplayBrightnessState +import com.android.server.display.DisplayBrightnessState.BRIGHTNESS_NOT_SET +import com.android.server.display.DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET import com.android.server.display.DisplayDeviceConfig import com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState import com.android.server.display.brightness.clamper.HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO import com.android.server.display.brightness.clamper.HdrBrightnessModifier.Injector +import com.android.server.display.config.HdrBrightnessData import com.android.server.display.config.createHdrBrightnessData +import com.android.server.testutils.OffsettableClock import com.android.server.testutils.TestHandler import com.google.common.truth.Truth.assertThat + import org.junit.Test import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +private const val SEND_TIME_TOLERANCE: Long = 100 + @SmallTest class HdrBrightnessModifierTest { - private val testHandler = TestHandler(null) + private val stoppedClock = OffsettableClock.Stopped() + private val testHandler = TestHandler(null, stoppedClock) private val testInjector = TestInjector() private val mockChangeListener = mock<ClamperChangeListener>() private val mockDisplayDeviceConfig = mock<DisplayDeviceConfig>() @@ -51,7 +61,6 @@ class HdrBrightnessModifierTest { private lateinit var modifier: HdrBrightnessModifier private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder) - private val dummyHdrData = createHdrBrightnessData() @Test fun `change listener is not called on init`() { @@ -70,8 +79,7 @@ class HdrBrightnessModifierTest { @Test fun `hdr listener not registered on init if hdr data is missing`() { - whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null) - modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData) + initHdrModifier(null) testHandler.flush() @@ -108,129 +116,274 @@ class HdrBrightnessModifierTest { @Test fun `test NO_HDR mode`() { initHdrModifier() - - whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + // screen size = 10_000 + setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData( minimumHdrPercentOfScreenForNbm = 0.5f, minimumHdrPercentOfScreenForHbm = 0.7f, sdrToHdrRatioSpline = mockSpline )) - // screen size = 10_000 - modifier.onDisplayChanged(createDisplayDeviceData( - mockDisplayDeviceConfig, mockDisplayBinder, - width = 100, - height = 100 - )) - testHandler.flush() + // hdr size = 900 val desiredMaxHdrRatio = 8f - val hdrWidth = 30 - val hdrHeight = 30 - testInjector.registeredHdrListener!!.onHdrInfoChanged( - mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio - ) - testHandler.flush() - - val modifierState = ModifiersAggregatedState() - modifier.applyStateChange(modifierState) - - assertThat(modifierState.mHdrHbmEnabled).isFalse() - assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(DEFAULT_MAX_HDR_SDR_RATIO) - assertThat(modifierState.mSdrHdrRatioSpline).isNull() - - val stateBuilder = DisplayBrightnessState.builder() - modifier.apply(mockRequest, stateBuilder) + setupHdrLayer(width = 30, height = 30, maxHdrRatio = desiredMaxHdrRatio) - verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any()) - assertThat(stateBuilder.hdrBrightness).isEqualTo(DisplayBrightnessState.BRIGHTNESS_NOT_SET) + assertModifierState() } @Test fun `test NBM_HDR mode`() { initHdrModifier() - whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + // screen size = 10_000 + val transitionPoint = 0.55f + setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData( minimumHdrPercentOfScreenForNbm = 0.5f, minimumHdrPercentOfScreenForHbm = 0.7f, + transitionPoint = transitionPoint, sdrToHdrRatioSpline = mockSpline )) + // hdr size = 5_100 + val desiredMaxHdrRatio = 8f + setupHdrLayer(width = 100, height = 51, maxHdrRatio = desiredMaxHdrRatio) + + whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( + 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(0.85f) + + assertModifierState( + maxBrightness = transitionPoint, + hdrRatio = desiredMaxHdrRatio, + hdrBrightness = transitionPoint, + spline = mockSpline + ) + } + + @Test + fun `test HBM_HDR mode`() { + initHdrModifier() // screen size = 10_000 - modifier.onDisplayChanged(createDisplayDeviceData( - mockDisplayDeviceConfig, mockDisplayBinder, - width = 100, - height = 100 + setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData( + minimumHdrPercentOfScreenForNbm = 0.5f, + minimumHdrPercentOfScreenForHbm = 0.7f, + transitionPoint = 0.55f, + sdrToHdrRatioSpline = mockSpline )) - testHandler.flush() - // hdr size = 5_100 + // hdr size = 7_100 val desiredMaxHdrRatio = 8f - val hdrWidth = 100 - val hdrHeight = 51 - testInjector.registeredHdrListener!!.onHdrInfoChanged( - mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio + setupHdrLayer(width = 100, height = 71, maxHdrRatio = desiredMaxHdrRatio) + + val expectedHdrBrightness = 0.92f + whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( + 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness) + + assertModifierState( + hdrRatio = desiredMaxHdrRatio, + hdrBrightness = expectedHdrBrightness, + spline = mockSpline ) - testHandler.flush() + } - val modifierState = ModifiersAggregatedState() - modifier.applyStateChange(modifierState) + @Test + fun `test display change no HDR content`() { + initHdrModifier() + setupDisplay(width = 100, height = 100) + assertModifierState() + clearInvocations(mockChangeListener) + // display change, new instance of HdrBrightnessData + setupDisplay(width = 100, height = 100) + + assertModifierState() + verify(mockChangeListener, never()).onChanged() + } + + @Test + fun `test display change with HDR content`() { + initHdrModifier() + setupDisplay(width = 100, height = 100) + setupHdrLayer(width = 100, height = 100, maxHdrRatio = 5f) + assertModifierState( + hdrBrightness = 0f, + hdrRatio = 5f, + spline = mockSpline + ) + clearInvocations(mockChangeListener) + // display change, new instance of HdrBrightnessData + setupDisplay(width = 100, height = 100) + + assertModifierState( + hdrBrightness = 0f, + hdrRatio = 5f, + spline = mockSpline + ) + // new instance of HdrBrightnessData received, notify listener + verify(mockChangeListener).onChanged() + } + + @Test + fun `test ambient lux decrease above maxBrightnessLimits no HDR`() { + initHdrModifier() + modifier.setAmbientLux(1000f) + setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData( + maxBrightnessLimits = mapOf(Pair(500f, 0.6f)) + )) + + modifier.setAmbientLux(500f) + // verify debounce is not scheduled + assertThat(testHandler.hasMessagesOrCallbacks()).isFalse() + + assertModifierState() + verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any()) + } + + @Test + fun `test ambient lux decrease above maxBrightnessLimits with HDR`() { + initHdrModifier() + modifier.setAmbientLux(1000f) + setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData( + maxBrightnessLimits = mapOf(Pair(500f, 0.6f)), + sdrToHdrRatioSpline = mockSpline + )) + setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f) - assertThat(modifierState.mHdrHbmEnabled).isFalse() - assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio) - assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline) + modifier.setAmbientLux(500f) - val expectedHdrBrightness = 0.85f + // verify debounce is not scheduled + assertThat(testHandler.hasMessagesOrCallbacks()).isFalse() + + val hdrBrightnessFromSdr = 0.83f whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( - 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness) - val stateBuilder = DisplayBrightnessState.builder() - modifier.apply(mockRequest, stateBuilder) + 0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr) - assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness) + assertModifierState( + hdrBrightness = hdrBrightnessFromSdr, + spline = mockSpline, + hdrRatio = 8f + ) } @Test - fun `test HBM_HDR mode`() { + fun `test ambient lux decrease below maxBrightnessLimits no HDR`() { + initHdrModifier() + modifier.setAmbientLux(1000f) + setupDisplay(width = 100, height = 100, hdrBrightnessData = createHdrBrightnessData( + maxBrightnessLimits = mapOf(Pair(500f, 0.6f)) + )) + + modifier.setAmbientLux(499f) + // verify debounce is not scheduled + assertThat(testHandler.hasMessagesOrCallbacks()).isFalse() + + assertModifierState() + verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any()) + } + + @Test + fun `test ambient lux decrease below maxBrightnessLimits with HDR`() { initHdrModifier() - whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + modifier.setAmbientLux(1000f) + val maxBrightness = 0.6f + val brightnessDecreaseDebounceMillis = 2800L + val animationRate = 0.01f + setupDisplay(width = 200, height = 200, hdrBrightnessData = createHdrBrightnessData( + maxBrightnessLimits = mapOf(Pair(500f, maxBrightness)), + brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis, + screenBrightnessRampDecrease = animationRate, + sdrToHdrRatioSpline = mockSpline, + )) + setupHdrLayer(width = 200, height = 200, maxHdrRatio = 8f) + + modifier.setAmbientLux(499f) + + val hdrBrightnessFromSdr = 0.83f + whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( + 0f, 8f, mockSpline)).thenReturn(hdrBrightnessFromSdr) + // debounce with brightnessDecreaseDebounceMillis, no changes to the state just yet + assertModifierState( + hdrBrightness = hdrBrightnessFromSdr, + spline = mockSpline, + hdrRatio = 8f + ) + + // verify debounce is scheduled + assertThat(testHandler.hasMessagesOrCallbacks()).isTrue() + val msgInfo = testHandler.pendingMessages.peek() + assertSendTime(brightnessDecreaseDebounceMillis, msgInfo!!.sendTime) + clearInvocations(mockChangeListener) + + // triggering debounce, state changes + testHandler.flush() + + verify(mockChangeListener).onChanged() + + assertModifierState( + hdrBrightness = maxBrightness, + spline = mockSpline, + hdrRatio = 8f, + maxBrightness = maxBrightness, + animationRate = animationRate + ) + } + + private fun setupHdrLayer(width: Int = 100, height: Int = 100, maxHdrRatio: Float = 0.8f) { + testInjector.registeredHdrListener!!.onHdrInfoChanged( + mockDisplayBinder, 1, width, height, 0, maxHdrRatio + ) + testHandler.flush() + } + + private fun setupDisplay( + width: Int = 100, + height: Int = 100, + hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData( minimumHdrPercentOfScreenForNbm = 0.5f, minimumHdrPercentOfScreenForHbm = 0.7f, + transitionPoint = 0.68f, sdrToHdrRatioSpline = mockSpline - )) - // screen size = 10_000 + ) + ) { + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData) modifier.onDisplayChanged(createDisplayDeviceData( mockDisplayDeviceConfig, mockDisplayBinder, - width = 100, - height = 100 + width = width, + height = height )) testHandler.flush() - // hdr size = 7_100 - val desiredMaxHdrRatio = 8f - val hdrWidth = 100 - val hdrHeight = 71 - testInjector.registeredHdrListener!!.onHdrInfoChanged( - mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio - ) + } + + private fun initHdrModifier(hdrBrightnessData: HdrBrightnessData? = createHdrBrightnessData()) { + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(hdrBrightnessData) + modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData) testHandler.flush() + } + + // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis() + // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis() + // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between + // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added. + private fun assertSendTime(expectedTime: Long, sendTime: Long) { + assertThat(sendTime).isAtMost(expectedTime) + assertThat(sendTime).isGreaterThan(expectedTime - SEND_TIME_TOLERANCE) + } + private fun assertModifierState( + maxBrightness: Float = BRIGHTNESS_MAX, + hdrRatio: Float = DEFAULT_MAX_HDR_SDR_RATIO, + spline: Spline? = null, + hdrBrightness: Float = BRIGHTNESS_NOT_SET, + animationRate: Float = CUSTOM_ANIMATION_RATE_NOT_SET + ) { val modifierState = ModifiersAggregatedState() modifier.applyStateChange(modifierState) - assertThat(modifierState.mHdrHbmEnabled).isTrue() - assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio) - assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline) + assertThat(modifierState.mMaxHdrBrightness).isEqualTo(maxBrightness) + assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(hdrRatio) + assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(spline) - val expectedHdrBrightness = 0.83f - whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( - 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness) val stateBuilder = DisplayBrightnessState.builder() modifier.apply(mockRequest, stateBuilder) - assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness) - } - - private fun initHdrModifier() { - whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(dummyHdrData) - modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData) - testHandler.flush() + assertThat(stateBuilder.hdrBrightness).isEqualTo(hdrBrightness) + assertThat(stateBuilder.customAnimationRate).isEqualTo(animationRate) } - internal class TestInjector : Injector() { var registeredHdrListener: SurfaceControlHdrLayerInfoListener? = null var registeredToken: IBinder? = null diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt index b742d021f2e9..f59e1275d2ce 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/LightSensorControllerTest.kt @@ -26,6 +26,7 @@ import com.android.server.display.TestUtils import com.android.server.display.brightness.clamper.LightSensorController.Injector import com.android.server.display.brightness.clamper.LightSensorController.LightSensorListener import com.android.server.display.config.SensorData +import com.android.server.display.config.createSensorData import com.android.server.display.utils.AmbientFilter import org.junit.Before import org.mockito.kotlin.any @@ -51,7 +52,7 @@ class LightSensorControllerTest { private val mockAmbientFilter: AmbientFilter = mock() private val testInjector = TestInjector() - private val dummySensorData = SensorData() + private val dummySensorData = createSensorData() private lateinit var controller: LightSensorController diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt index 3b3d6f74da50..c7580331c841 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt @@ -24,6 +24,17 @@ import java.io.ByteArrayOutputStream import java.io.OutputStreamWriter import org.xmlpull.v1.XmlSerializer +@JvmOverloads +fun createSensorData( + type: String? = null, + name: String? = null, + minRefreshRate: Float = 0f, + maxRefreshRate: Float = Float.POSITIVE_INFINITY, + supportedModes: List<SupportedModeData> = emptyList() +): SensorData { + return SensorData(type, name, minRefreshRate, maxRefreshRate, supportedModes) +} + fun createRefreshRateData( defaultRefreshRate: Int = 60, defaultPeakRefreshRate: Int = 60, @@ -46,6 +57,7 @@ fun createHdrBrightnessData( screenBrightnessRampIncrease: Float = 0.02f, brightnessDecreaseDebounceMillis: Long = 3000, screenBrightnessRampDecrease: Float = 0.04f, + transitionPoint: Float = 0.65f, minimumHdrPercentOfScreenForNbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT, minimumHdrPercentOfScreenForHbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT, allowInLowPowerMode: Boolean = false, @@ -57,6 +69,7 @@ fun createHdrBrightnessData( screenBrightnessRampIncrease, brightnessDecreaseDebounceMillis, screenBrightnessRampDecrease, + transitionPoint, minimumHdrPercentOfScreenForNbm, minimumHdrPercentOfScreenForHbm, allowInLowPowerMode, diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt index 19c6924dfa5b..917c681a0d95 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt @@ -16,6 +16,7 @@ package com.android.server.display.config +import android.os.PowerManager import android.util.Spline.createSpline import androidx.test.filters.SmallTest import com.android.server.display.DisplayBrightnessState @@ -42,7 +43,7 @@ class HdrBrightnessDataTest { ) } - val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) + val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f } assertThat(hdrBrightnessData).isNotNull() assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(3000) @@ -54,6 +55,7 @@ class HdrBrightnessDataTest { assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(500f, 0.6f) assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(600f, 0.7f) + assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(PowerManager.BRIGHTNESS_MAX) assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo( HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT ) @@ -79,10 +81,13 @@ class HdrBrightnessDataTest { ) } - val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) + val transitionPoint = 0.6f + val hdrBrightnessData = + HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint } assertThat(hdrBrightnessData).isNotNull() - assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f) + assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint) + assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f) assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f) assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse() @@ -100,7 +105,9 @@ class HdrBrightnessDataTest { ) } - val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) + val transitionPoint = 0.6f + val hdrBrightnessData = + HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint } assertThat(hdrBrightnessData).isNotNull() assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(0) @@ -112,6 +119,7 @@ class HdrBrightnessDataTest { assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(0) + assertThat(hdrBrightnessData.hbmTransitionPoint).isEqualTo(transitionPoint) assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f) assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f) assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse() @@ -125,7 +133,7 @@ class HdrBrightnessDataTest { fun `test HdrBrightnessData configuration no configuration`() { val displayConfiguration = createDisplayConfiguration() - val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) + val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) { 0.6f } assertThat(hdrBrightnessData).isNull() } @@ -144,10 +152,13 @@ class HdrBrightnessDataTest { ) } - val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration) + val transitionPoint = 0.6f + val hdrBrightnessData = + HdrBrightnessData.loadConfig(displayConfiguration) { transitionPoint } assertThat(hdrBrightnessData).isNotNull() - assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f) + assertThat(hdrBrightnessData!!.hbmTransitionPoint).isEqualTo(transitionPoint) + assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f) assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.6f) assertThat(hdrBrightnessData.allowInLowPowerMode).isTrue() diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java index 6e2d954c05a8..c0f5e7a69af7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java @@ -16,6 +16,8 @@ package com.android.server.display.utils; +import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; @@ -65,7 +67,7 @@ public class SensorUtilsTest { @Test public void testNoSensorManager() { - Sensor result = SensorUtils.findSensor(null, new SensorData(), Sensor.TYPE_LIGHT); + Sensor result = SensorUtils.findSensor(null, createSensorData(), Sensor.TYPE_LIGHT); assertNull(result); } @@ -123,7 +125,7 @@ public class SensorUtilsTest { when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors); when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor); - SensorData sensorData = new SensorData(sensorType, sensorName); + SensorData sensorData = createSensorData(sensorType, sensorName); Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType); diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java index 99968d5117c7..9da695a4effb 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamAccessibilityTest.java @@ -19,10 +19,12 @@ package com.android.server.dreams; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import android.content.Context; import android.content.res.Resources; @@ -44,9 +46,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; -import java.util.Collections; - @SmallTest @RunWith(AndroidJUnit4.class) public class DreamAccessibilityTest { @@ -73,7 +72,8 @@ public class DreamAccessibilityTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mDreamAccessibility = new DreamAccessibility(mContext, mView); + Runnable mDismissCallback = () -> {}; + mDreamAccessibility = new DreamAccessibility(mContext, mView, mDismissCallback); when(mContext.getResources()).thenReturn(mResources); when(mResources.getString(R.string.dream_accessibility_action_click)) @@ -84,80 +84,55 @@ public class DreamAccessibilityTest { */ @Test public void testConfigureAccessibilityActions() { - when(mAccessibilityNodeInfo.getActionList()).thenReturn(new ArrayList<>()); + when(mView.getAccessibilityDelegate()).thenReturn(null); - mDreamAccessibility.updateAccessibilityConfiguration(false); + mDreamAccessibility.updateAccessibilityConfiguration(); verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); + View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor + .getValue(); capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo); verify(mAccessibilityNodeInfo).addAction(argThat(action -> - action.getId() == AccessibilityNodeInfo.ACTION_CLICK + action.getId() == AccessibilityNodeInfo.ACTION_DISMISS && TextUtils.equals(action.getLabel(), CUSTOM_ACTION))); } /** - * Test to verify the configuration of accessibility actions within a view delegate, - * specifically checking the removal of an existing click action and addition - * of a new custom action. + * Test to verify no accessibility configuration is added if one exist. */ @Test - public void testConfigureAccessibilityActions_RemovesExistingClickAction() { - AccessibilityNodeInfo.AccessibilityAction existingAction = - new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, - EXISTING_ACTION); - when(mAccessibilityNodeInfo.getActionList()) - .thenReturn(Collections.singletonList(existingAction)); - - mDreamAccessibility.updateAccessibilityConfiguration(false); - - verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); - - capturedDelegate.onInitializeAccessibilityNodeInfo(mView, mAccessibilityNodeInfo); + public void testNotAddingDuplicateAccessibilityConfiguration() { + View.AccessibilityDelegate existingDelegate = mock(View.AccessibilityDelegate.class); + when(mView.getAccessibilityDelegate()).thenReturn(existingDelegate); - verify(mAccessibilityNodeInfo).removeAction(existingAction); - verify(mAccessibilityNodeInfo).addAction(argThat(action -> - action.getId() == AccessibilityNodeInfo.ACTION_CLICK - && TextUtils.equals(action.getLabel(), CUSTOM_ACTION))); + mDreamAccessibility.updateAccessibilityConfiguration(); + verify(mView, never()).setAccessibilityDelegate(any()); } /** - * Test to verify the removal of a custom accessibility action within a view delegate. + * Test to verify dismiss callback is called */ @Test - public void testRemoveCustomAccessibilityAction() { + public void testPerformAccessibilityAction() { + Runnable mockDismissCallback = mock(Runnable.class); + DreamAccessibility dreamAccessibility = new DreamAccessibility(mContext, + mView, mockDismissCallback); - AccessibilityNodeInfo.AccessibilityAction existingAction = - new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, - EXISTING_ACTION); - when(mAccessibilityNodeInfo.getActionList()) - .thenReturn(Collections.singletonList(existingAction)); + dreamAccessibility.updateAccessibilityConfiguration(); - mDreamAccessibility.updateAccessibilityConfiguration(false); verify(mView).setAccessibilityDelegate(mAccessibilityDelegateArgumentCaptor.capture()); - View.AccessibilityDelegate capturedDelegate = - mAccessibilityDelegateArgumentCaptor.getValue(); - when(mView.getAccessibilityDelegate()).thenReturn(capturedDelegate); - clearInvocations(mView); + View.AccessibilityDelegate capturedDelegate = mAccessibilityDelegateArgumentCaptor + .getValue(); - mDreamAccessibility.updateAccessibilityConfiguration(true); - verify(mView).setAccessibilityDelegate(null); - } + boolean result = capturedDelegate.performAccessibilityAction(mView, + AccessibilityNodeInfo.ACTION_DISMISS, null); - /** - * Test to verify the removal of custom accessibility action is not called if delegate is not - * set by the dreamService. - */ - @Test - public void testRemoveCustomAccessibility_DoesNotRemoveDelegateNotSetByDreamAccessibility() { - mDreamAccessibility.updateAccessibilityConfiguration(true); - verify(mView, never()).setAccessibilityDelegate(any()); + assertTrue(result); + verify(mockDismissCallback).run(); } + } diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index f2b4136c51ed..b2a5b02c49e1 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -59,6 +59,7 @@ android_ravenwood_test { name: "PowerStatsTestsRavenwood", static_libs: [ "services.core", + "platformprotosnano", "coretests-aidl", "ravenwood-junit", "truth", diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java index ac1f7d0e345f..37d8f2f74850 100644 --- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.os; +package com.android.server.power.stats; import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE; @@ -23,39 +23,262 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; +import android.os.Process; import android.os.UidBatteryConsumer; import android.os.nano.BatteryUsageStatsAtomsProto; import android.os.nano.BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage; +import android.platform.test.ravenwood.RavenwoodRule; +import android.util.StatsEvent; import androidx.test.filters.SmallTest; +import com.android.server.am.BatteryStatsService; + import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import org.junit.Rule; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; - @SmallTest -public class BatteryUsageStatsPulledTest { +public class BatteryUsageStatsAtomTest { + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); private static final int UID_0 = 1000; private static final int UID_1 = 2000; private static final int UID_2 = 3000; private static final int UID_3 = 4000; - private static final int[] UID_USAGE_TIME_PROCESS_STATES = { - BatteryConsumer.PROCESS_STATE_FOREGROUND, - BatteryConsumer.PROCESS_STATE_BACKGROUND, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE - }; @Test - public void testGetStatsProto() { + public void testAtom_BatteryUsageStatsPerUid() { + final BatteryUsageStats bus = buildBatteryUsageStats(); + BatteryStatsService.FrameworkStatsLogger statsLogger = + mock(BatteryStatsService.FrameworkStatsLogger.class); + + List<StatsEvent> actual = new ArrayList<>(); + new BatteryStatsService.StatsPerUidLogger(statsLogger).logStats(bus, actual); + + // Device-wide totals + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "cpu", + 30000.0f, + 20100.0f, + 20300L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "camera", + 30000.0f, + 20150.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + Process.INVALID_UID, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "CustomConsumer1", + 30000.0f, + 20200.0f, + 20400L + ); + + // Per-proc state estimates for UID_0 + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "screen", + 1650.0f, + 300.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "cpu", + 1650.0f, + 400.0f, + 600L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "cpu", + 1650.0f, + 9100.0f, + 8100L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "cpu", + 1650.0f, + 9200.0f, + 8200L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, + 0L, + "cpu", + 1650.0f, + 9300.0f, + 8400L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_CACHED, + 0L, + "cpu", + 1650.0f, + 9400.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "CustomConsumer1", + 1650.0f, + 450.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "CustomConsumer1", + 1650.0f, + 450.0f, + 0L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + 1000L, + "CustomConsumer2", + 1650.0f, + 500.0f, + 800L + ); + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_0, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + 2000L, + "CustomConsumer2", + 1650.0f, + 500.0f, + 800L + ); + + // Nothing for UID_1, because its power consumption is 0 + + // Only "screen" is populated for UID_2 + verify(statsLogger).buildStatsEvent( + 1000L, + 20000L, + 10000L, + 20, + 1234L, + UID_2, + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + 0L, + "screen", + 766.0f, + 766.0f, + 0L + ); + + verifyNoMoreInteractions(statsLogger); + } + + @Test + public void testAtom_BatteryUsageStatsAtomsProto() { final BatteryUsageStats bus = buildBatteryUsageStats(); final byte[] bytes = bus.getStatsProto(); BatteryUsageStatsAtomsProto proto; @@ -68,9 +291,7 @@ public class BatteryUsageStatsPulledTest { assertEquals(bus.getStatsStartTimestamp(), proto.sessionStartMillis); assertEquals(bus.getStatsEndTimestamp(), proto.sessionEndMillis); - assertEquals( - bus.getStatsEndTimestamp() - bus.getStatsStartTimestamp(), - proto.sessionDurationMillis); + assertEquals(10000, proto.sessionDurationMillis); assertEquals(bus.getDischargePercentage(), proto.sessionDischargePercentage); assertEquals(bus.getDischargeDurationMs(), proto.dischargeDurationMillis); @@ -90,8 +311,8 @@ public class BatteryUsageStatsPulledTest { final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers(); uidConsumers.sort((a, b) -> a.getUid() - b.getUid()); - final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto - = proto.uidBatteryConsumers; + final BatteryUsageStatsAtomsProto.UidBatteryConsumer[] uidConsumersProto = + proto.uidBatteryConsumers; Arrays.sort(uidConsumersProto, (a, b) -> a.uid - b.uid); // UID_0 - After sorting, UID_0 should be in position 0 for both data structures @@ -186,6 +407,12 @@ public class BatteryUsageStatsPulledTest { } } + private static final int[] UID_USAGE_TIME_PROCESS_STATES = { + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.PROCESS_STATE_BACKGROUND, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE + }; + private void assertSameUidBatteryConsumer( android.os.UidBatteryConsumer uidConsumer, BatteryUsageStatsAtomsProto.UidBatteryConsumer uidConsumerProto, @@ -195,10 +422,10 @@ public class BatteryUsageStatsPulledTest { assertEquals("Uid consumers had mismatched uids", uid, uidConsumer.getUid()); assertEquals("For uid " + uid, - uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND), + uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_FOREGROUND), uidConsumerProto.timeInForegroundMillis); assertEquals("For uid " + uid, - uidConsumer.getTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND), + uidConsumer.getTimeInProcessStateMs(BatteryConsumer.PROCESS_STATE_BACKGROUND), uidConsumerProto.timeInBackgroundMillis); for (int processState : UID_USAGE_TIME_PROCESS_STATES) { final long timeInStateMillis = uidConsumer.getTimeInProcessStateMs(processState); @@ -261,11 +488,15 @@ public class BatteryUsageStatsPulledTest { new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"}, /* includePowerModels */ true, /* includeProcessStats */ true, + /* includeScreenStateData */ false, + /* includePowerStateData */ false, /* minConsumedPowerThreshold */ 0) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) .setDischargeDurationMs(1234) - .setStatsStartTimestamp(1000); + .setStatsStartTimestamp(1000) + .setStatsEndTimestamp(20000) + .setStatsDuration(10000); final UidBatteryConsumer.Builder uidBuilder = builder .getOrCreateUidBatteryConsumerBuilder(UID_0) .setPackageWithHighestDrain("myPackage0") @@ -345,7 +576,7 @@ public class BatteryUsageStatsPulledTest { @Test public void testLargeAtomTruncated() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[0], true, false, 0); + new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0); // If not truncated, this BatteryUsageStats object would generate a proto buffer // significantly larger than 50 Kb for (int i = 0; i < 3000; i++) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 6edfedee9e5b..624b18948c49 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -397,10 +397,14 @@ public class BatteryUsageStatsRule implements TestRule { & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0; final boolean includeProcessStateData = (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0; + final boolean includeScreenStateData = (query.getFlags() + & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE) != 0; + final boolean includePowerStateData = (query.getFlags() + & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0; final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold(); BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( customPowerComponentNames, includePowerModels, includeProcessStateData, - minConsumedPowerThreshold); + includeScreenStateData, includePowerStateData, minConsumedPowerThreshold); SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats(); for (int i = 0; i < uidStats.size(); i++) { builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index a3f0770ec8ba..52bb5e839ca2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java @@ -31,6 +31,8 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static java.util.regex.Pattern.quote; + import android.os.AggregateBatteryConsumer; import android.os.BatteryConsumer; import android.os.BatteryUsageStats; @@ -91,7 +93,7 @@ public class BatteryUsageStatsTest { final Parcel parcel = Parcel.obtain(); parcel.writeParcelable(outBatteryUsageStats, 0); - assertThat(parcel.dataSize()).isLessThan(12000); + assertThat(parcel.dataSize()).isLessThan(100000); parcel.setDataPosition(0); @@ -161,15 +163,47 @@ public class BatteryUsageStatsTest { assertThat(dump).contains("Computed drain: 30000"); assertThat(dump).contains("actual drain: 1000-2000"); assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms"); - assertThat(dump).contains("cpu(fg): 2333 apps: 1333 duration: 3s 332ms"); - assertThat(dump).contains("cpu(bg): 2444 apps: 1444 duration: 4s 442ms"); - assertThat(dump).contains("cpu(fgs): 2555 apps: 1555 duration: 5s 552ms"); - assertThat(dump).contains("cpu(cached): 123 apps: 123 duration: 456ms"); assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms"); - assertThat(dump).contains("UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123 " - + "( screen=300 cpu=400 (600ms) cpu:fg=1777 (7s 771ms) cpu:bg=1888 (8s 881ms) " - + "cpu:fgs=1999 (9s 991ms) cpu:cached=123 (456ms) FOO=500 )"); - assertThat(dump).contains("User 42: 30.0 ( cpu=10.0 (30ms) FOO=20.0 )"); + assertThat(dump).containsMatch(quote("(on battery, screen on)") + "\\s*" + + "cpu: 2333 apps: 1333 duration: 3s 332ms"); + assertThat(dump).containsMatch(quote("(not on battery, screen on)") + "\\s*" + + "cpu: 2555 apps: 1555 duration: 5s 552ms"); + assertThat(dump).containsMatch(quote("(on battery, screen off/doze)") + "\\s*" + + "cpu: 2444 apps: 1444 duration: 4s 442ms"); + assertThat(dump).containsMatch(quote("(not on battery, screen off/doze)") + "\\s*" + + "cpu: 123 apps: 123 duration: 456ms"); + assertThat(dump).containsMatch( + "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*" + + quote("screen=300 cpu=5787 (27s 99ms) cpu:fg=1777 (7s 771ms) " + + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) " + + "cpu:cached=123 (456ms) FOO=500") + "\\s*" + + quote("(on battery, screen on)") + "\\s*" + + quote("cpu:fg=1777 (7s 771ms)")); + assertThat(dump).containsMatch("User 42: 30.0\\s*" + + quote("cpu=10.0 (30ms) FOO=20.0")); + } + + @Test + public void testDumpNoScreenOrPowerState() { + final BatteryUsageStats stats = buildBatteryUsageStats1(true, false, false).build(); + final StringWriter out = new StringWriter(); + try (PrintWriter pw = new PrintWriter(out)) { + stats.dump(pw, " "); + } + final String dump = out.toString(); + + assertThat(dump).contains("Capacity: 4000"); + assertThat(dump).contains("Computed drain: 30000"); + assertThat(dump).contains("actual drain: 1000-2000"); + assertThat(dump).contains("cpu: 20100 apps: 10100 duration: 20s 300ms"); + assertThat(dump).contains("FOO: 20200 apps: 10200 duration: 20s 400ms"); + assertThat(dump).containsMatch( + "UID 271: 1200 fg: 1777 bg: 1888 fgs: 1999 cached: 123\\s*" + + quote("screen=300 cpu=5787 (600ms) cpu:fg=1777 (7s 771ms) " + + "cpu:bg=1888 (8s 881ms) cpu:fgs=1999 (9s 991ms) " + + "cpu:cached=123 (456ms) FOO=500")); + assertThat(dump).containsMatch("User 42: 30.0\\s*" + + quote("cpu=10.0 (30ms) FOO=20.0")); } @Test @@ -186,9 +220,8 @@ public class BatteryUsageStatsTest { public void testAdd() { final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build(); final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build(); - final BatteryUsageStats sum = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0) .add(stats1) .add(stats2) .build(); @@ -200,14 +233,14 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == APP_UID1) { assertUidBatteryConsumer(uidBatteryConsumer, 2124, null, - 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 745, + 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 11772, POWER_MODEL_UNDEFINED, 956, 1167, 1478, true, 3554, 3776, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982, 444, 1110); } else if (uidBatteryConsumer.getUid() == APP_UID2) { assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar", - 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444, + 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5985, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777, true, 1777, 1888, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991, @@ -229,7 +262,7 @@ public class BatteryUsageStatsTest { @Test public void testAdd_customComponentMismatch() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0); + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0); final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build(); assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); @@ -238,7 +271,7 @@ public class BatteryUsageStatsTest { @Test public void testAdd_processStateDataMismatch() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0); + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0); final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build(); assertThrows(IllegalArgumentException.class, () -> builder.add(stats)); @@ -259,15 +292,23 @@ public class BatteryUsageStatsTest { parser.setInput(in, StandardCharsets.UTF_8.name()); final BatteryUsageStats fromXml = BatteryUsageStats.createFromXml(parser); + System.out.println("stats = " + stats); + System.out.println("fromXml = " + fromXml); assertBatteryUsageStats1(fromXml, true); } private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer) { + return buildBatteryUsageStats1(includeUserBatteryConsumer, true, true); + } + + private BatteryUsageStats.Builder buildBatteryUsageStats1(boolean includeUserBatteryConsumer, + boolean includeScreenState, boolean includePowerState) { final MockClock clocks = new MockClock(); final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, 0) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, + includeScreenState, includePowerState, 0) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) @@ -312,7 +353,7 @@ public class BatteryUsageStatsTest { final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(customPowerComponentNames, true, - includeProcessStateData, 0); + includeProcessStateData, true, true, 0); builder.setDischargePercentage(30) .setDischargedPowerRange(1234, 2345) .setStatsStartTimestamp(2000) @@ -371,9 +412,15 @@ public class BatteryUsageStatsTest { .setUsageDurationForCustomComponentMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); if (builder.isProcessStateDataNeeded()) { - final BatteryConsumer.Key cpuFgKey = uidBuilder.getKey( - BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key cpuFgKey = builder.isScreenStateDataNeeded() + ? uidBuilder.getKey( + BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND, + BatteryConsumer.SCREEN_STATE_ON, + BatteryConsumer.POWER_STATE_BATTERY) + : uidBuilder.getKey( + BatteryConsumer.POWER_COMPONENT_CPU, + BatteryConsumer.PROCESS_STATE_FOREGROUND); final BatteryConsumer.Key cpuBgKey = uidBuilder.getKey( BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND); @@ -401,9 +448,9 @@ public class BatteryUsageStatsTest { private void addAggregateBatteryConsumer(BatteryUsageStats.Builder builder, int scope, double consumedPower, int cpuPower, int customComponentPower, int cpuDuration, - int customComponentDuration, double cpuPowerForeground, long cpuDurationForeground, - double cpuPowerBackground, long cpuDurationBackground, double cpuPowerFgs, - long cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) { + int customComponentDuration, double cpuPowerBatScrOn, long cpuDurationBatScrOn, + double cpuPowerBatScrOff, long cpuDurationBatScrOff, double cpuPowerChgScrOn, + long cpuDurationChgScrOn, double cpuPowerChgScrOff, long cpuDurationChgScrOff) { final AggregateBatteryConsumer.Builder aggBuilder = builder.getAggregateBatteryConsumerBuilder(scope) .setConsumedPower(consumedPower) @@ -417,32 +464,40 @@ public class BatteryUsageStatsTest { .setUsageDurationForCustomComponentMillis( BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentDuration); - if (builder.isProcessStateDataNeeded()) { - final BatteryConsumer.Key cpuFgKey = aggBuilder.getKey( + if (builder.isPowerStateDataNeeded() || builder.isScreenStateDataNeeded()) { + final BatteryConsumer.Key cpuBatScrOn = aggBuilder.getKey( BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND); - final BatteryConsumer.Key cpuBgKey = aggBuilder.getKey( + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + BatteryConsumer.SCREEN_STATE_ON, + BatteryConsumer.POWER_STATE_BATTERY); + final BatteryConsumer.Key cpuBatScrOff = aggBuilder.getKey( BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_BACKGROUND); - final BatteryConsumer.Key cpuFgsKey = aggBuilder.getKey( + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + BatteryConsumer.SCREEN_STATE_OTHER, + BatteryConsumer.POWER_STATE_BATTERY); + final BatteryConsumer.Key cpuChgScrOn = aggBuilder.getKey( BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); - final BatteryConsumer.Key cpuCachedKey = aggBuilder.getKey( + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + BatteryConsumer.SCREEN_STATE_ON, + BatteryConsumer.POWER_STATE_OTHER); + final BatteryConsumer.Key cpuChgScrOff = aggBuilder.getKey( BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_CACHED); + BatteryConsumer.PROCESS_STATE_UNSPECIFIED, + BatteryConsumer.SCREEN_STATE_OTHER, + BatteryConsumer.POWER_STATE_OTHER); aggBuilder - .setConsumedPower(cpuFgKey, cpuPowerForeground, + .setConsumedPower(cpuBatScrOn, cpuPowerBatScrOn, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuFgKey, cpuDurationForeground) - .setConsumedPower(cpuBgKey, cpuPowerBackground, + .setUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn) + .setConsumedPower(cpuBatScrOff, cpuPowerBatScrOff, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuBgKey, cpuDurationBackground) - .setConsumedPower(cpuFgsKey, cpuPowerFgs, + .setUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff) + .setConsumedPower(cpuChgScrOn, cpuPowerChgScrOn, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuFgsKey, cpuDurationFgs) - .setConsumedPower(cpuCachedKey, cpuPowerCached, + .setUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn) + .setConsumedPower(cpuChgScrOff, cpuPowerChgScrOff, BatteryConsumer.POWER_MODEL_POWER_PROFILE) - .setUsageDurationMillis(cpuCachedKey, cpuDurationCached); + .setUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff); } } @@ -456,7 +511,7 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == APP_UID1) { assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo", - 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400, + 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 5787, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800, true, 1777, 1888, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456); @@ -568,54 +623,53 @@ public class BatteryUsageStatsTest { .isEqualTo(totalPowerCached); } - final BatteryConsumer.Key cpuFgKey = uidBatteryConsumer.getKey( + final BatteryConsumer.Dimensions cpuFg = new BatteryConsumer.Dimensions( BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND); if (processStateDataIncluded) { - assertThat(cpuFgKey).isNotNull(); - assertThat(uidBatteryConsumer.getConsumedPower(cpuFgKey)) + assertThat(uidBatteryConsumer.getConsumedPower(cpuFg)) .isEqualTo(cpuPowerForeground); - assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgKey)) + assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFg)) .isEqualTo(cpuDurationForeground); } else { - assertThat(cpuFgKey).isNull(); + assertThat(uidBatteryConsumer.getConsumedPower(cpuFg)).isEqualTo(0); } - final BatteryConsumer.Key cpuBgKey = uidBatteryConsumer.getKey( + final BatteryConsumer.Dimensions cpuBg = new BatteryConsumer.Dimensions( BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_BACKGROUND); if (processStateDataIncluded) { - assertThat(cpuBgKey).isNotNull(); - assertThat(uidBatteryConsumer.getConsumedPower(cpuBgKey)) + assertThat(uidBatteryConsumer.getConsumedPower(cpuBg)) .isEqualTo(cpuPowerBackground); - assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBgKey)) + assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuBg)) .isEqualTo(cpuDurationBackground); } else { - assertThat(cpuBgKey).isNull(); + assertThat(uidBatteryConsumer.getConsumedPower(cpuBg)) + .isEqualTo(0); } - final BatteryConsumer.Key cpuFgsKey = uidBatteryConsumer.getKey( + final BatteryConsumer.Dimensions cpuFgs = new BatteryConsumer.Dimensions( BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); if (processStateDataIncluded) { - assertThat(cpuFgsKey).isNotNull(); - assertThat(uidBatteryConsumer.getConsumedPower(cpuFgsKey)) + assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs)) .isEqualTo(cpuPowerFgs); - assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgsKey)) + assertThat(uidBatteryConsumer.getUsageDurationMillis(cpuFgs)) .isEqualTo(cpuDurationFgs); } else { - assertThat(cpuFgsKey).isNotNull(); + assertThat(uidBatteryConsumer.getConsumedPower(cpuFgs)) + .isEqualTo(0); } - final BatteryConsumer.Key cachedKey = uidBatteryConsumer.getKey( + final BatteryConsumer.Dimensions cached = new BatteryConsumer.Dimensions( BatteryConsumer.POWER_COMPONENT_CPU, BatteryConsumer.PROCESS_STATE_CACHED); if (processStateDataIncluded) { - assertThat(cachedKey).isNotNull(); - assertThat(uidBatteryConsumer.getConsumedPower(cachedKey)) + assertThat(uidBatteryConsumer.getConsumedPower(cached)) .isEqualTo(cpuPowerCached); - assertThat(uidBatteryConsumer.getUsageDurationMillis(cachedKey)) + assertThat(uidBatteryConsumer.getUsageDurationMillis(cached)) .isEqualTo(cpuDurationCached); } else { - assertThat(cpuFgsKey).isNotNull(); + assertThat(uidBatteryConsumer.getConsumedPower(cached)) + .isEqualTo(0); } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index 644ae4717eb1..005ceee703a8 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -130,7 +130,7 @@ public class CpuPowerStatsCollectorValidationTest { boolean inCpuSection = false; for (int i = 0; i < lines.length; i++) { if (!inCpuSection) { - if (lines[i].startsWith("CpuPowerStatsCollector")) { + if (lines[i].startsWith("cpu (1)")) { inCpuSection = true; } } else if (lines[i].startsWith(" ")) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java index 7bec13f653e7..1621d47d62b5 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerStatsTest.java @@ -149,9 +149,9 @@ public class CustomEnergyConsumerPowerStatsTest { .isEqualTo(20000); assertThat(ps2.uidStats.size()).isEqualTo(2); assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID1), 0)) - .isEqualTo(14000); + .isEqualTo(4000); assertThat(POWER_STATS_LAYOUT.getUidConsumedEnergy(ps2.uidStats.get(APP_UID2), 0)) - .isEqualTo(21000); + .isEqualTo(6000); } @Test @@ -209,8 +209,8 @@ public class CustomEnergyConsumerPowerStatsTest { assertThat(POWER_STATS_LAYOUT.getDevicePowerEstimate(deviceStats)) .isWithin(PRECISION).of(expectedPower * 0.75); - // UID1: estimated power = 14,000 uC = 0.00388 mAh - expectedPower = 0.00388; + // UID1: estimated power = 4,000 uC = 0.00111 mAh + expectedPower = 0.00111; ps2.getUidStats(uidStats, APP_UID1, states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) @@ -221,8 +221,8 @@ public class CustomEnergyConsumerPowerStatsTest { assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) .isWithin(PRECISION).of(expectedPower * 0.75); - // UID2: estimated power = 21,000 uC = 0.00583 mAh - expectedPower = 0.00583; + // UID2: estimated power = 6,000 uC = 0.00166 mAh + expectedPower = 0.00167; ps2.getUidStats(uidStats, APP_UID2, states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); assertThat(POWER_STATS_LAYOUT.getUidPowerEstimate(uidStats)) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java index 32bfb2cd3507..7f7967ba4d7b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsExporterTest.java @@ -19,7 +19,6 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.mock; import android.os.AggregateBatteryConsumer; @@ -131,9 +130,20 @@ public class PowerStatsExporterTest { @Test public void breakdownByProcState_fullRange() throws Exception { + breakdownByProcState_fullRange(false, false); + } + + @Test + public void breakdownByProcStateScreenAndPower_fullRange() throws Exception { + breakdownByProcState_fullRange(true, true); + } + + private void breakdownByProcState_fullRange(boolean includeScreenStateData, + boolean includePowerStateData) throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( new String[]{"cu570m"}, /* includePowerModels */ false, - /* includeProcessStateData */ true, /* powerThreshold */ 0); + /* includeProcessStateData */ true, includeScreenStateData, + includePowerStateData, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 1000, 10000); BatteryUsageStats actual = builder.build(); @@ -177,7 +187,7 @@ public class PowerStatsExporterTest { public void breakdownByProcState_subRange() throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( new String[]{"cu570m"}, /* includePowerModels */ false, - /* includeProcessStateData */ true, /* powerThreshold */ 0); + /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 3700, 6700); BatteryUsageStats actual = builder.build(); @@ -209,7 +219,7 @@ public class PowerStatsExporterTest { public void combinedProcessStates() throws Exception { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( new String[]{"cu570m"}, /* includePowerModels */ false, - /* includeProcessStateData */ false, /* powerThreshold */ 0); + /* includeProcessStateData */ false, true, true, /* powerThreshold */ 0); exportAggregatedPowerStats(builder, 1000, 10000); BatteryUsageStats actual = builder.build(); @@ -229,13 +239,13 @@ public class PowerStatsExporterTest { UidBatteryConsumer uidScope = actual.getUidBatteryConsumers().stream() .filter(us -> us.getUid() == APP_UID1).findFirst().orElse(null); // There shouldn't be any per-procstate data - assertThrows( - IllegalArgumentException.class, - () -> uidScope.getConsumedPower(new BatteryConsumer.Dimensions( + for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) { + if (procState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + assertThat(uidScope.getConsumedPower(new BatteryConsumer.Dimensions( BatteryConsumer.POWER_COMPONENT_CPU, - BatteryConsumer.PROCESS_STATE_FOREGROUND))); - - + BatteryConsumer.PROCESS_STATE_FOREGROUND))).isEqualTo(0); + } + } actual.close(); } diff --git a/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java new file mode 100644 index 000000000000..f698bea1a199 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/autofill/HelperTest.java @@ -0,0 +1,113 @@ +/* + * 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.autofill; + +import static com.android.server.autofill.Helper.SaveInfoStats; +import static com.android.server.autofill.Helper.getSaveInfoStatsFromFillResponses; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import android.service.autofill.FillResponse; +import android.service.autofill.SaveInfo; +import android.util.SparseArray; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class HelperTest { + + @Test + public void testGetSaveInfoStatsFromFillResponses_nullFillResponses() { + SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(null); + + assertThat(saveInfoStats.saveInfoCount).isEqualTo(-1); + assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(-1); + } + + @Test + public void testGetSaveInfoStatsFromFillResponses_emptyFillResponseSparseArray() { + SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(new SparseArray<>()); + + assertThat(saveInfoStats.saveInfoCount).isEqualTo(0); + assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0); + } + + @Test + public void testGetSaveInfoStatsFromFillResponses_singleResponseWithoutSaveInfo() { + FillResponse.Builder fillResponseBuilder = new FillResponse.Builder(); + // Add client state to satisfy the sanity check in FillResponseBuilder.build() + Bundle clientState = new Bundle(); + fillResponseBuilder.setClientState(clientState); + FillResponse testFillResponse = fillResponseBuilder.build(); + + SparseArray<FillResponse> testFillResponses = new SparseArray<>(); + testFillResponses.put(0, testFillResponse); + + SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses); + + assertThat(saveInfoStats.saveInfoCount).isEqualTo(0); + assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(0); + } + + @Test + public void testGetSaveInfoStatsFromFillResponses_singleResponseWithSaveInfo() { + FillResponse.Builder fillResponseBuilder = new FillResponse.Builder(); + SaveInfo.Builder saveInfoBuilder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC); + fillResponseBuilder.setSaveInfo(saveInfoBuilder.build()); + FillResponse testFillResponse = fillResponseBuilder.build(); + + SparseArray<FillResponse> testFillResponses = new SparseArray<>(); + testFillResponses.put(0, testFillResponse); + + SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses); + + assertThat(saveInfoStats.saveInfoCount).isEqualTo(1); + assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(1); + } + + @Test + public void testGetSaveInfoStatsFromFillResponses_multipleResponseWithDifferentTypeSaveInfo() { + FillResponse.Builder fillResponseBuilder1 = new FillResponse.Builder(); + SaveInfo.Builder saveInfoBuilder1 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC); + fillResponseBuilder1.setSaveInfo(saveInfoBuilder1.build()); + FillResponse testFillResponse1 = fillResponseBuilder1.build(); + + FillResponse.Builder fillResponseBuilder2 = new FillResponse.Builder(); + SaveInfo.Builder saveInfoBuilder2 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS); + fillResponseBuilder2.setSaveInfo(saveInfoBuilder2.build()); + FillResponse testFillResponse2 = fillResponseBuilder2.build(); + + FillResponse.Builder fillResponseBuilder3 = new FillResponse.Builder(); + SaveInfo.Builder saveInfoBuilder3 = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_ADDRESS); + fillResponseBuilder3.setSaveInfo(saveInfoBuilder3.build()); + FillResponse testFillResponse3 = fillResponseBuilder3.build(); + + SparseArray<FillResponse> testFillResponses = new SparseArray<>(); + testFillResponses.put(0, testFillResponse1); + testFillResponses.put(1, testFillResponse2); + testFillResponses.put(2, testFillResponse3); + + SaveInfoStats saveInfoStats = getSaveInfoStatsFromFillResponses(testFillResponses); + + // Save info count is 3. Since two save info share the same save data type, the distinct + // save data type count is 2. + assertThat(saveInfoStats.saveInfoCount).isEqualTo(3); + assertThat(saveInfoStats.saveDataTypeCount).isEqualTo(2); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0f385325e525..a4222ff5650b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -1518,7 +1518,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); @@ -1540,7 +1541,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); @@ -1564,7 +1566,8 @@ public class BiometricServiceTest { mBiometricService.onStart(); when(mTrustManager.isInSignificantPlace()).thenReturn(false); - when(mBiometricService.mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())) + when(mBiometricService.mSettingObserver + .getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(anyInt())) .thenReturn(true); setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java index b831ef5c30bc..240da9fe46bd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java @@ -90,7 +90,8 @@ public class PreAuthInfoTest { when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt())) .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE); when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); - when(mSettingObserver.getMandatoryBiometricsEnabledForUser(anyInt())).thenReturn(true); + when(mSettingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser( + anyInt())).thenReturn(true); when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index 316b5faf2a68..689b241f0faa 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -364,6 +365,39 @@ public class MediaProjectionManagerServiceTest { @EnableFlags(android.companion.virtualdevice.flags .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) @Test + public void testReuseProjection_keyguardNotLocked_startConsentDialog() + throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doNothing().when(mContext).startActivityAsUser(any(), any()); + doReturn(false).when(mKeyguardManager).isKeyguardLocked(); + + MediaProjectionManagerService.BinderService mediaProjectionBinderService = + mService.new BinderService(mContext); + mediaProjectionBinderService.requestConsentForInvalidProjection(projection); + + verify(mContext).startActivityAsUser(any(), any()); + } + + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test + public void testReuseProjection_keyguardLocked_noConsentDialog() throws NameNotFoundException { + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(true).when(mKeyguardManager).isKeyguardLocked(); + MediaProjectionManagerService.BinderService mediaProjectionBinderService = + mService.new BinderService(mContext); + mediaProjectionBinderService.requestConsentForInvalidProjection(projection); + + verify(mContext, never()).startActivityAsUser(any(), any()); + } + + @EnableFlags(android.companion.virtualdevice.flags + .Flags.FLAG_MEDIA_PROJECTION_KEYGUARD_RESTRICTIONS) + @Test public void testKeyguardLocked_stopsActiveProjection() throws Exception { MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); 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 4bbbc2b28dad..b07940ad8de3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -186,6 +186,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParserException; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -205,9 +208,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - @SmallTest @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWith(ParameterizedAndroidJunit4.class) @@ -5022,6 +5022,34 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(FLAG_MODES_API) + public void updateAutomaticZenRule_ruleChangedByUser_doesNotDeactivateRule_forWatch() { + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); + AutomaticZenRule rule = + new AutomaticZenRule.Builder("rule", CONDITION_ID) + .setConfigurationActivity(new ComponentName(mPkg, "cls")) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .build(); + String ruleId = + mZenModeHelper.addAutomaticZenRule( + mPkg, rule, UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID); + mZenModeHelper.setAutomaticZenRuleState( + ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + AutomaticZenRule updateWithDiff = + new AutomaticZenRule.Builder(rule).setTriggerDescription("Whenever").build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_USER, "reason", + CUSTOM_PKG_UID); + + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo( + CONDITION_TRUE); + } + + @Test @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) public void updateAutomaticZenRule_ruleDisabledByUser_doesNotReactivateOnReenable() { assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 9dac23f075e6..d7004e72bc52 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -1746,10 +1746,6 @@ public class VibrationThreadTest { assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: played normally after effect4, which may or may not have played. - - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId5)); - verifyCallbacksTriggered(vibrationId5, Vibration.Status.FINISHED); - assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), fakeVibrator.getEffectSegments(vibrationId5)); } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index ef944dbba3ca..5ae5677b9b53 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -47,6 +47,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Resources; @@ -103,6 +104,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.LocalServices; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; +import com.android.server.pm.BackgroundUserSoundNotifier; import org.junit.After; import org.junit.Before; @@ -809,6 +811,32 @@ public class VibratorManagerServiceTest { } @Test + @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) + public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable { + mockVibrators(1); + VibratorManagerService service = createSystemReadyService(); + + var vib = vibrate(service, + VibrationEffect.createWaveform(new long[]{100, 100, 100, 100}, 0), ALARM_ATTRS); + + assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + + + service.mIntentReceiver.onReceive(mContextSpy, new Intent( + BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)); + + assertTrue(waitUntil(s -> vib.hasEnded(), service, TEST_TIMEOUT_MILLIS)); + + var statsInfoCaptor = ArgumentCaptor.forClass(VibrationStats.StatsInfo.class); + verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS)) + .writeVibrationReportedAsync(statsInfoCaptor.capture()); + + VibrationStats.StatsInfo touchMetrics = statsInfoCaptor.getAllValues().get(0); + assertEquals(Vibration.Status.CANCELLED_BY_FOREGROUND_USER.getProtoEnumValue(), + touchMetrics.status); + } + + @Test public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { VibratorManagerService service = createSystemReadyService(); diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml index 88419e9c441b..1549b2de78c3 100644 --- a/services/tests/wmtests/res/xml/bookmarks.xml +++ b/services/tests/wmtests/res/xml/bookmarks.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2024 The Android Open Source Project +<!-- Copyright 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. @@ -14,6 +14,8 @@ limitations under the License. --> <bookmarks> + <!-- the key combinations for the following shortcuts must be in sync + with the key combinations sent by the test in ModifierShortcutTests.java --> <bookmark role="android.app.role.BROWSER" shortcut="b" /> @@ -38,4 +40,37 @@ <bookmark category="android.intent.category.APP_CALCULATOR" shortcut="u" /> + + <!-- The following shortcuts will not be invoked by tests but are here to + provide test coverage of parsing the different types of shortcut. --> + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="a" /> + <bookmark + package="com.test2" + class="com.test.BookmarkTest" + shortcut="d" /> + + <bookmark + role="android.app.role.BROWSER" + shortcut="b" + shift="true" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="c" + shift="true" /> + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="a" + shift="true" /> + + <!-- It's intended that this package/class will NOT resolve so we test the resolution + failure case. --> + <bookmark + package="com.test3" + class="com.test.BookmarkTest" + shortcut="f" /> + </bookmarks> diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java index 896edffee875..1c331166d317 100644 --- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -24,8 +24,8 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV import android.view.ViewConfiguration; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java index 8c375d413950..5533ff909c50 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 The Android Open Source Project + * Copyright 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. @@ -19,15 +19,22 @@ package com.android.server.policy; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.Handler; @@ -58,27 +65,56 @@ public class ModifierShortcutManagerTests { private Handler mHandler; private Context mContext; private Resources mResources; + private PackageManager mPackageManager; @Before public void setUp() { mHandler = new Handler(Looper.getMainLooper()); mContext = spy(getInstrumentation().getTargetContext()); mResources = spy(mContext.getResources()); + mPackageManager = spy(mContext.getPackageManager()); XmlResourceParser testBookmarks = mResources.getXml( com.android.frameworks.wmtests.R.xml.bookmarks); when(mContext.getResources()).thenReturn(mResources); + when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks); + try { + // Keep packageName / className in sync with + // services/tests/wmtests/res/xml/bookmarks.xml + ActivityInfo testActivityInfo = new ActivityInfo(); + testActivityInfo.applicationInfo = new ApplicationInfo(); + testActivityInfo.packageName = + testActivityInfo.applicationInfo.packageName = "com.test"; + + doReturn(testActivityInfo).when(mPackageManager).getActivityInfo( + eq(new ComponentName("com.test", "com.test.BookmarkTest")), anyInt()); + doThrow(new PackageManager.NameNotFoundException("com.test3")).when(mPackageManager) + .getActivityInfo(eq(new ComponentName("com.test3", "com.test.BookmarkTest")), + anyInt()); + } catch (PackageManager.NameNotFoundException ignored) { } + doReturn(new String[] { "com.test" }).when(mPackageManager) + .canonicalToCurrentPackageNames(aryEq(new String[] { "com.test2" })); + mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler); } @Test public void test_getApplicationLaunchKeyboardShortcuts() { + // Expected values here determined by the number of shortcuts defined in + // services/tests/wmtests/res/xml/bookmarks.xml + + // Total valid shortcuts. KeyboardShortcutGroup group = mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1); - assertEquals(8, group.getItems().size()); + assertEquals(13, group.getItems().size()); + + // Total valid shift shortcuts. + assertEquals(3, group.getItems().stream() + .filter(s -> s.getModifiers() == (KeyEvent.META_SHIFT_ON | KeyEvent.META_META_ON)) + .count()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java new file mode 100644 index 000000000000..ddd6d562abb1 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java @@ -0,0 +1,316 @@ +/* + * 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.wm; + +import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertEquals; + +import android.compat.testing.PlatformCompatChangeRule; +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.NonNull; + +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Test class for {@link AppCompatAspectRatioOverrides}. + * <p> + * Build/Install/Run: + * atest WmTests:AppCompatAspectRatioOverridesTest + */ +@Presubmit +@RunWith(WindowTestRunner.class) +public class AppCompatAspectRatioOverridesTest extends WindowTestsBase { + + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + + @Test + public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE); + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ false); + + robot.activity().createActivityWithComponent(); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + robot.checkShouldApplyUserFullscreenOverride(/* expected */ false); + }); + } + + + @Test + public void testShouldApplyUserFullscreenOverride_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioFullscreen(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_FULLSCREEN); + + robot.checkShouldApplyUserFullscreenOverride(/* expected */ true); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ true); + }); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_ignoreOrientation_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.prop().disable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldEnableUserAspectRatioSettings(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.prop().enable(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldEnableUserAspectRatioSettings(/* enabled */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ false); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ true); + }); + } + + @Test + public void testShouldApplyUserMinAspectRatioOverride_ignoreOrientation_returnsFalse() { + runTestScenario((robot)-> { + robot.conf().enableUserAppAspectRatioSettings(/* enabled */ false); + robot.activity().setIgnoreOrientationRequest(/* enabled */ true); + robot.activity().createActivityWithComponent(); + robot.activity().setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2); + + robot.checkShouldApplyUserMinAspectRatioOverride(/* expected */ false); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() { + runTestScenario((robot)-> { + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ true); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testShouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() { + runTestScenario((robot)-> { + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) + public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot)-> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatio(/* expected */ false); + }); + } + + /** + * Runs a test scenario providing a Robot. + */ + void runTestScenario(@NonNull Consumer<AspectRatioOverridesRobotTest> consumer) { + spyOn(mWm.mAppCompatConfiguration); + final AspectRatioOverridesRobotTest robot = + new AspectRatioOverridesRobotTest(mWm, mAtm, mSupervisor); + consumer.accept(robot); + } + + private static class AspectRatioOverridesRobotTest extends AppCompatRobotBase { + + AspectRatioOverridesRobotTest(@NonNull WindowManagerService wm, + @NonNull ActivityTaskManagerService atm, + @NonNull ActivityTaskSupervisor supervisor) { + super(wm, atm, supervisor); + } + + void checkShouldApplyUserFullscreenOverride(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldApplyUserFullscreenOverride()); + } + + void checkShouldEnableUserAspectRatioSettings(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldEnableUserAspectRatioSettings()); + } + + void checkShouldApplyUserMinAspectRatioOverride(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldApplyUserMinAspectRatioOverride()); + } + + void checkShouldOverrideMinAspectRatio(boolean expected) { + assertEquals(expected, getTopActivityAppCompatAspectRatioOverrides() + .shouldOverrideMinAspectRatio()); + } + + @NonNull + private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() { + return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides(); + } + } + +} diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index d8c7fb3594c1..de99f546ab07 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAME import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; @@ -36,6 +37,7 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.Rule; @@ -286,6 +288,88 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { }); } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ true); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ false); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() { + runTestScenario((robot) -> { + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + + @Test + @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) + public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() { + runTestScenario((robot) -> { + robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); + robot.activity().createActivityWithComponent(); + robot.activity().activateCameraInPolicy(/* isCameraActive */ true); + + robot.checkShouldOverrideMinAspectRatioForCamera(/* expected */ false); + }); + } + /** * Runs a test scenario providing a Robot. */ @@ -323,6 +407,11 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { .shouldApplyFreeformTreatmentForCameraCompat(), expected); } + void checkShouldOverrideMinAspectRatioForCamera(boolean expected) { + Assert.assertEquals(getAppCompatCameraOverrides() + .shouldOverrideMinAspectRatioForCamera(), expected); + } + void checkIsCameraActive(boolean active) { Assert.assertEquals(getAppCompatCameraOverrides().isCameraActive(), active); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java index d568eecfd1c5..361177f480a6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatComponentPropRobot.java @@ -32,27 +32,27 @@ import androidx.annotation.NonNull; */ class AppCompatComponentPropRobot { @NonNull - private final WindowManagerService mWm; + private final PackageManager mPackageManager; AppCompatComponentPropRobot(@NonNull WindowManagerService wm) { - mWm = wm; + mPackageManager = wm.mContext.getPackageManager(); + spyOn(mPackageManager); } void enable(@NonNull String propertyName) { - setPropertyValue(propertyName, /* enabled */ true); + setPropertyValue(propertyName, "", "", /* enabled */ true); } void disable(@NonNull String propertyName) { - setPropertyValue(propertyName, /* enabled */ false); + setPropertyValue(propertyName, "", "", /* enabled */ false); } - private void setPropertyValue(@NonNull String propertyName, boolean enabled) { + private void setPropertyValue(@NonNull String propertyName, @NonNull String packageName, + @NonNull String className, boolean enabled) { final PackageManager.Property property = new PackageManager.Property(propertyName, - /* value */ enabled, /* packageName */ "", /* className */ ""); - final PackageManager pm = mWm.mContext.getPackageManager(); - spyOn(pm); + /* value */ enabled, packageName, className); try { - doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); + doReturn(property).when(mPackageManager).getProperty(eq(propertyName), anyString()); } catch (PackageManager.NameNotFoundException e) { fail(e.getLocalizedMessage()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java index cb3cf6bd2a5c..0a1b16bfc3e9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java @@ -66,6 +66,4 @@ class AppCompatConfigurationRobot { doReturn(enabled).when(mAppCompatConfiguration) .isCameraCompatSplitScreenAspectRatioEnabled(); } - - } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 63c14b90958f..afa22bc5eae8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -67,6 +67,7 @@ import android.window.IOnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.OnBackInvokedDispatcher; +import android.window.TaskFragmentOrganizer; import android.window.TaskSnapshot; import android.window.WindowOnBackInvokedDispatcher; @@ -670,25 +671,29 @@ public class BackNavigationControllerTests extends WindowTestsBase { } @Test - public void testAdjacentFocusInActivityEmbedding() { + public void testBackOnMostRecentWindowInActivityEmbedding() { mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG); final Task task = createTask(mDefaultDisplay); - final TaskFragment primaryTf = createTaskFragmentWithActivity(task); - final TaskFragment secondaryTf = createTaskFragmentWithActivity(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment secondaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer); final ActivityRecord primaryActivity = primaryTf.getTopMostActivity(); final ActivityRecord secondaryActivity = secondaryTf.getTopMostActivity(); primaryTf.setAdjacentTaskFragment(secondaryTf); secondaryTf.setAdjacentTaskFragment(primaryTf); - final WindowState windowState = mock(WindowState.class); - windowState.mActivityRecord = primaryActivity; - doReturn(windowState).when(mWm).getFocusedWindowLocked(); - doReturn(primaryTf).when(windowState).getTaskFragment(); + final WindowState primaryWindow = mock(WindowState.class); + final WindowState secondaryWindow = mock(WindowState.class); + doReturn(primaryActivity).when(primaryWindow).getActivityRecord(); + doReturn(secondaryActivity).when(secondaryWindow).getActivityRecord(); doReturn(1L).when(primaryActivity).getLastWindowCreateTime(); doReturn(2L).when(secondaryActivity).getLastWindowCreateTime(); + doReturn(mDisplayContent).when(primaryActivity).getDisplayContent(); + doReturn(secondaryWindow).when(mDisplayContent).findFocusedWindow(eq(secondaryActivity)); - startBackNavigation(); - verify(mWm).moveFocusToActivity(eq(secondaryActivity)); + final WindowState mostRecentUsedWindow = + mWm.getMostRecentUsedEmbeddedWindowForBack(primaryWindow); + assertThat(mostRecentUsedWindow).isEqualTo(secondaryWindow); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index d318f0047c08..44c7057b2294 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -19,19 +19,12 @@ 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.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; -import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA; import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; -import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; -import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -119,8 +112,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mController = new LetterboxUiController(mWm, mActivity); } - - @Test public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() { final InsetsSource taskbar = new InsetsSource(/*id=*/ 0, @@ -320,164 +311,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { return mainWindow; } - // shouldApplyUser...Override - @Test - public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - /* value */ true); - - doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, - /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldApplyUserFullscreenOverride_returnsTrue() { - prepareActivityThatShouldApplyUserFullscreenOverride(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserFullscreenOverride()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() - throws Exception { - - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - - mController = new LetterboxUiController(mWm, mActivity); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientation_returnsFalse() - throws Exception { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - - mController = new LetterboxUiController(mWm, mActivity); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() - throws Exception { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldEnableUserAspectRatioSettings()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse() - throws Exception { - doReturn(false).when(mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); - mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() { - prepareActivityThatShouldApplyUserMinAspectRatioOverride(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - @Test - public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientation_returnsFalse() { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldApplyUserMinAspectRatioOverride()); - } - - private void prepareActivityForShouldApplyUserMinAspectRatioOverride( - boolean orientationRequest) { - spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(orientationRequest).when( - mAppCompatConfiguration).isUserAppAspectRatioSettingsEnabled(); - mDisplayContent.setIgnoreOrientationRequest(true); - doReturn(USER_MIN_ASPECT_RATIO_3_2) - .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) - .getUserMinAspectRatioOverrideCode(); - } - - private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { - prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true); - } - - private void prepareActivityThatShouldApplyUserFullscreenOverride() { - spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()); - doReturn(true).when(mAppCompatConfiguration).isUserAppAspectRatioFullscreenEnabled(); - mDisplayContent.setIgnoreOrientationRequest(true); - doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN) - .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides()) - .getUserMinAspectRatioOverrideCode(); - } - // shouldUseDisplayLandscapeNaturalOrientation @Test @@ -595,156 +428,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() { - mActivity = setUpActivityWithComponent(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - - assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() { - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO}) - public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides() - .shouldOverrideMinAspectRatio()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_overrideEnabled_returnsTrue() { - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsTrue() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertTrue(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(false).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyTrue_overrideDisabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true); - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_overrideDisabled_returnsFalse() { - mActivity = setUpActivityWithComponent(); - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyFalse_overrideEnabled_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - mActivity = setUpActivityWithComponent(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test - @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA}) - public void shouldOverrideMinAspectRatioForCamera_propertyFalse_noOverride_returnsFalse() - throws Exception { - mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false); - - mActivity = setUpActivityWithComponent(); - - doReturn(true).when(mActivity.mAppCompatController - .getAppCompatCameraOverrides()).isCameraActive(); - - assertFalse(mActivity.mAppCompatController.getAppCompatCameraOverrides() - .shouldOverrideMinAspectRatioForCamera()); - } - - @Test @EnableCompatChanges({FORCE_RESIZE_APP}) public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() { mController = new LetterboxUiController(mWm, mActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java index e3f8e8c54b73..8abf3f86c01e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java @@ -36,8 +36,8 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.server.wm.utils.CommonUtils; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java deleted file mode 100644 index d5356774ffdb..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT; -import static com.android.server.wm.WindowManagerService.dipToPixel; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; -import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.util.DisplayMetrics; -import android.util.Log; - -import androidx.test.filters.SmallTest; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link TaskPositioner} class. - * - * Build/Install/Run: - * atest WmTests:TaskPositionerTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskPositionerTests extends WindowTestsBase { - - private static final boolean DEBUGGING = false; - private static final String TAG = "TaskPositionerTest"; - - private static final int MOUSE_DELTA_X = 5; - private static final int MOUSE_DELTA_Y = 5; - - private int mMinVisibleWidth; - private int mMinVisibleHeight; - private TaskPositioner mPositioner; - - @Before - public void setUp() { - TaskPositioner.setFactory(null); - - final DisplayMetrics dm = mDisplayContent.getDisplayMetrics(); - - // This should be the same calculation as the TaskPositioner uses. - mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm); - mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm); - removeGlobalMinSizeRestriction(); - - final ActivityRecord activity = new ActivityBuilder(mAtm) - .setCreateTask(true) - .build(); - final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "window"); - mPositioner = new TaskPositioner(mWm); - mPositioner.register(mDisplayContent, win); - - win.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM); - } - - @After - public void tearDown() { - TaskPositioner.setFactory(null); - } - - @Test - public void testOverrideFactory() { - final boolean[] created = new boolean[1]; - created[0] = false; - TaskPositioner.setFactory(new TaskPositioner.Factory() { - @Override - public TaskPositioner create(WindowManagerService service) { - created[0] = true; - return null; - } - }); - - assertNull(TaskPositioner.create(mWm)); - assertTrue(created[0]); - } - - /** This tests that the window can move in all directions. */ - @Test - public void testMoveWindow() { - final Rect displayBounds = mDisplayContent.getBounds(); - final int windowSize = Math.min(displayBounds.width(), displayBounds.height()) / 2; - final int left = displayBounds.centerX() - windowSize / 2; - final int top = displayBounds.centerY() - windowSize / 2; - final Rect r = new Rect(left, top, left + windowSize, top + windowSize); - mPositioner.mTask.setBounds(r); - mPositioner.startDrag(false /* resizing */, false /* preserveOrientation */, left, top); - - // Move upper left. - mPositioner.notifyMoveLocked(left - MOUSE_DELTA_X, top - MOUSE_DELTA_Y); - r.offset(-MOUSE_DELTA_X, -MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Move bottom right. - mPositioner.notifyMoveLocked(left, top); - r.offset(MOUSE_DELTA_X, MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - } - - /** - * This tests that free resizing will allow to change the orientation as well - * as does some basic tests (e.g. dragging in Y only will keep X stable). - */ - @Test - public void testBasicFreeWindowResizing() { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - // Start a drag resize starting upper left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals( - new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Start a drag resize left and see that only the left coord changes.. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored. - */ - @Test - public void testFreeWindowResizingTestAllEdges() { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - // Drag upper left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, 0.0f); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX, - r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag upper right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertNotEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, midY); - mPositioner.resizeDrag(r.right + 100, 0.0f); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom right. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertNotEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, midX, - r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(r.right + 100, r.bottom + 100); - assertEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag bottom left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertNotEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - - // Drag left. - mPositioner.startDrag(true /* resizing */, false /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - mPositioner.resizeDrag(0.0f, r.bottom + 100); - assertNotEquals(r.left, mPositioner.getWindowDragBounds().left); - assertEquals(r.right, mPositioner.getWindowDragBounds().right); - assertEquals(r.top, mPositioner.getWindowDragBounds().top); - assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragTopLeft() { - final Rect r = new Rect(100, 220, 700, 520); - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the width. - mPositioner.resizeDrag(2000.0f, r.top); - final int w = mMinVisibleWidth; - final int h = Math.round(w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height. - mPositioner.resizeDrag(r.left, 2000.0f); - assertBoundsEquals( - new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragLeft() { - final Rect r = new Rect(100, 220, 700, 520); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(200.0f, midY); - assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right and see the height also shrinking. - mPositioner.resizeDrag(2000.0f, midY); - final int w = mMinVisibleWidth; - final int h = Math.round((float) w / MIN_ASPECT); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained landscape window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - public void testLandscapePreservedWindowResizingDragTop() { - final Rect r = new Rect(100, 220, 700, 520); - final int midX = (r.left + r.right) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, midX, - r.top - MOUSE_DELTA_Y); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = mMinVisibleHeight; - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top left corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragTopLeft() { - final Rect r = new Rect(330, 100, 630, 600); - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /*resizing*/, true /*preserveOrientation*/, - r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y); - assertBoundsEquals(r, mPositioner.getWindowDragBounds()); - - // Drag to a good landscape size. - mPositioner.resizeDrag(0.0f, 0.0f); - int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a good portrait size. - mPositioner.resizeDrag(400.0f, 0.0f); - assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to a too small size for the height and the the width shrinking. - mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f); - final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT)); - final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT)); - assertBoundsEquals( - new Rect(r.right - w, r.bottom - h, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the left corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragLeft() { - final Rect r = new Rect(330, 100, 630, 600); - final int midY = (r.top + r.bottom) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, - r.left - MOUSE_DELTA_X, midY); - - // Drag to the left. - mPositioner.resizeDrag(0.0f, midY); - int w = r.right - MOUSE_DELTA_X; - int h = Math.round(w * MIN_ASPECT); - assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the right. - mPositioner.resizeDrag(450.0f, midY); - assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag all the way to the right. - mPositioner.resizeDrag(2000.0f, midY); - w = mMinVisibleWidth; - h = Math.max(Math.round((float) w * MIN_ASPECT), r.height()); - assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(r.left, 0.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - } - - /** - * This tests that a constrained portrait window will keep the aspect and do the - * right things upon resizing when dragged from the top corner. - */ - @Test - public void testPortraitPreservedWindowResizingDragTop() { - final Rect r = new Rect(330, 100, 630, 600); - final int midX = (r.left + r.right) / 2; - mPositioner.mTask.setBounds(r, true); - - mPositioner.startDrag(true /* resizing */, true /* preserveOrientation */, midX, - r.top - MOUSE_DELTA_Y); - - // Drag to the left (no change). - mPositioner.resizeDrag(0.0f, r.top); - assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the right (no change). - mPositioner.resizeDrag(2000.0f, r.top); - assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the top. - mPositioner.resizeDrag(300.0f, 0.0f); - int h = r.bottom - MOUSE_DELTA_Y; - int w = Math.min(r.width(), Math.round(h / MIN_ASPECT)); - assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - - // Drag to the bottom. - mPositioner.resizeDrag(r.left, 1000.0f); - h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT)); - w = Math.round(h / MIN_ASPECT); - assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom), - mPositioner.getWindowDragBounds()); - } - - private static void assertBoundsEquals(Rect expected, Rect actual) { - if (DEBUGGING) { - if (!expected.equals(actual)) { - Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString() - + ") " + Log.getStackTraceString(new Throwable())); - } - } - assertEquals(expected, actual); - } - - @Test - public void testFinishingMovingWhenBinderDied() { - spyOn(mWm.mTaskPositioningController); - - mPositioner.startDrag(false, false, 0 /* startX */, 0 /* startY */); - verify(mWm.mTaskPositioningController, never()).finishTaskPositioning(); - mPositioner.binderDied(); - verify(mWm.mTaskPositioningController).finishTaskPositioning(); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java deleted file mode 100644 index bfc13d3d2ef2..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.platform.test.annotations.Presubmit; -import android.view.InputChannel; - -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for the {@link TaskPositioningController} class. - * - * Build/Install/Run: - * atest WmTests:TaskPositioningControllerTests - */ -@SmallTest -@Presubmit -@RunWith(WindowTestRunner.class) -public class TaskPositioningControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 1000; - - private TaskPositioningController mTarget; - private WindowState mWindow; - - @Before - public void setUp() throws Exception { - assertNotNull(mWm.mTaskPositioningController); - mTarget = mWm.mTaskPositioningController; - - when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true); - - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); - mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE); - mWindow.mInputChannel = new InputChannel(); - mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); - doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor(); - } - - @FlakyTest(bugId = 291067614) - @Test - public void testStartAndFinishPositioning() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testFinishPositioningWhenAppRequested() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - assertTrue(mTarget.startMovingTask(mWindow.mClient, 0, 0)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(mWindow.mClient); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testHandleTapOutsideTask() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - final DisplayContent content = mock(DisplayContent.class); - doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt()); - assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); - - mTarget.handleTapOutsideTask(content, 0, 0); - // Wait until the looper processes handleTapOutsideTask. - assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS)); - - assertTrue(mTarget.isPositioningLocked()); - assertNotNull(mTarget.getDragWindowHandleLocked()); - - mTarget.finishTaskPositioning(); - // Wait until the looper processes finishTaskPositioning. - assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - } - - @Test - public void testHandleTapOutsideNonResizableTask() { - assertFalse(mTarget.isPositioningLocked()); - assertNull(mTarget.getDragWindowHandleLocked()); - - final DisplayContent content = mock(DisplayContent.class); - doReturn(mWindow.getTask()).when(content).findTaskForResizePoint(anyInt(), anyInt()); - assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow()); - - mWindow.getTask().setResizeMode(RESIZE_MODE_UNRESIZEABLE); - - mTarget.handleTapOutsideTask(content, 0, 0); - // Wait until the looper processes handleTapOutsideTask. - assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS)); - - assertFalse(mTarget.isPositioningLocked()); - } - -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 7d01b79fe866..720457e24370 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1573,7 +1573,8 @@ public class TransitionTests extends WindowTestsBase { enteringAnimReports.clear(); doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(), anyBoolean()); final boolean[] wasInFinishingTransition = { false }; - controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() { + controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener( + mDisplayContent.mDisplayId) { @Override public void onAppTransitionFinishedLocked(IBinder token) { final ActivityRecord r = ActivityRecord.forToken(token); @@ -1582,6 +1583,14 @@ public class TransitionTests extends WindowTestsBase { } } }); + final boolean[] calledListenerOnOtherDisplay = { false }; + controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener( + mDisplayContent.mDisplayId + 1234) { + @Override + public void onAppTransitionFinishedLocked(IBinder token) { + calledListenerOnOtherDisplay[0] = true; + } + }); assertTrue(activity1.isVisible()); doReturn(false).when(task1).isTranslucent(null); doReturn(false).when(task1).isTranslucentForTransition(); @@ -1592,6 +1601,7 @@ public class TransitionTests extends WindowTestsBase { controller.finishTransition(closeTransition); assertTrue(wasInFinishingTransition[0]); + assertFalse(calledListenerOnOtherDisplay[0]); assertNull(controller.mFinishingTransition); assertTrue(activity2.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index e6648dad4bbe..0cb22ad47355 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -86,6 +87,7 @@ public class WindowProcessControllerTests extends WindowTestsBase { ApplicationInfo info = mock(ApplicationInfo.class); info.packageName = "test.package.name"; + doReturn(true).when(info).isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED); mWpc = new WindowProcessController( mAtm, info, null, 0, -1, null, mMockListener); mWpc.setThread(mock(IApplicationThread.class)); diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java index e5f2f89ccead..eda78cb40c5d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/utils/DesktopModeFlagsUtilTest.java @@ -63,9 +63,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { resetCache(); } - private static final String SYSTEM_PROPERTY_OVERRIDE_KEY = - "sys.wmshell.desktopmode.dev_toggle_override"; - @Test @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @@ -190,110 +187,6 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_noProperty_overrideOn_featureFlagOff_returnsTrueAndPropertyOn() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(OVERRIDE_ON.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_noProperty_overrideUnset_featureFlagOn_returnsTrueAndPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_noProperty_overrideUnset_featureFlagOff_returnsFalseAndPropertyUnset() { - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); - setOverride(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if not present - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyNotInt_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "abc"); - setOverride(OVERRIDE_OFF.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyInvalid_overrideOff_featureFlagOn_returnsFalseAndPropertyOff() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, "-2"); - setOverride(OVERRIDE_OFF.getSetting()); - - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - // Store System Property if currently invalid - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyOff_overrideOn_featureFlagOn_returnsFalseAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf( - OVERRIDE_OFF.getSetting())); - setOverride(OVERRIDE_ON.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_OFF.getSetting())); - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_propertyOn_overrideOff_featureFlagOff_returnsTrueAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, String.valueOf(OVERRIDE_ON.getSetting())); - setOverride(OVERRIDE_OFF.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf(OVERRIDE_ON.getSetting())); - } - - @Test - @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_propertyUnset_overrideOff_featureFlagOn_returnsTrueAndnoPropertyUpdate() { - System.setProperty(SYSTEM_PROPERTY_OVERRIDE_KEY, - String.valueOf(DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - setOverride(OVERRIDE_OFF.getSetting()); - - // Have a consistent override until reboot - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue(); - assertThat(System.getProperty(SYSTEM_PROPERTY_OVERRIDE_KEY)) - .isEqualTo(String.valueOf( - DesktopModeFlagsUtil.ToggleOverride.OVERRIDE_UNSET.getSetting())); - } - - @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY}) public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { @@ -452,8 +345,5 @@ public class DesktopModeFlagsUtilTest extends WindowTestsBase { "sCachedToggleOverride"); cachedToggleOverride.setAccessible(true); cachedToggleOverride.set(null, null); - - // Clear override cache stored in System property - System.clearProperty(SYSTEM_PROPERTY_OVERRIDE_KEY); } } diff --git a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl index 2dc8ffba6e96..460de8c8113d 100644 --- a/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl +++ b/telephony/java/android/telephony/satellite/stub/ProvisionSubscriberId.aidl @@ -25,4 +25,7 @@ parcelable ProvisionSubscriberId { /** carrier id */ int mCarrierId; + + /** apn */ + String mNiddApn; } diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index 08430f2f2744..4143f595f9a0 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest { private static BatteryUsageStats buildBatteryUsageStats() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, 0) + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index f367c38b06e9..06c2651b604d 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -48,6 +48,7 @@ android_test { "testables", "testng", "truth", + "ui-trace-collector", ], libs: [ "android.test.mock", diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml index 4a99bd4f1801..bc9322fbd3dc 100644 --- a/tests/Input/AndroidTest.xml +++ b/tests/Input/AndroidTest.xml @@ -22,6 +22,10 @@ <option name="shell-timeout" value="660s" /> <option name="test-timeout" value="600s" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/> + <!-- DefaultUITraceListener args --> + <option name="instrumentation-arg" key="skip_test_success_metrics" value="true"/> + <option name="instrumentation-arg" key="per_class" value="true"/> </test> <object class="com.android.tradefed.testtype.suite.module.TestFailureModuleController" type="module_controller"> @@ -32,6 +36,8 @@ <option name="pull-pattern-keys" value="input_.*" /> <!-- Pull files created by tests, like the output of screenshot tests --> <option name="directory-keys" value="/sdcard/Download/InputTests" /> + <!-- Pull perfetto traces from DefaultUITraceListener --> + <option name="pull-pattern-keys" value="perfetto_file_path*" /> <option name="collect-on-run-ended-only" value="false" /> </metrics_collector> </configuration> diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index b6672a0e2f4b..fad94d45c85d 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -698,7 +698,7 @@ public class PerfettoProtoLogImplTest { traceMonitor.start(); mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, - "My test null string: %s", null); + "My test null string: %s", (Object) null); } finally { traceMonitor.stop(mWriter); } |