diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java index e6014f4329c..8516d622206 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -126,6 +126,12 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { // Font weight adjustment for bold text. FontWeight.Bold - FontWeight.Normal = w700 - w400 = 300. private static final int BOLD_TEXT_WEIGHT_ADJUSTMENT = 300; + // Default transition animation scale (animations enabled) + private static final float DEFAULT_TRANSITION_ANIMATION_SCALE = 1.0f; + + // Transition animation scale when animations are disabled + private static final float DISABLED_TRANSITION_ANIMATION_SCALE = 0.0f; + /// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java private static int FIRST_RESOURCE_ID = 267386881; @@ -399,11 +405,13 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { return; } // Retrieve the current value of TRANSITION_ANIMATION_SCALE from the OS. - String value = - Settings.Global.getString( - contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE); + float value = + Settings.Global.getFloat( + contentResolver, + Settings.Global.TRANSITION_ANIMATION_SCALE, + DEFAULT_TRANSITION_ANIMATION_SCALE); - boolean shouldAnimationsBeDisabled = value != null && value.equals("0"); + boolean shouldAnimationsBeDisabled = value == DISABLED_TRANSITION_ANIMATION_SCALE; if (shouldAnimationsBeDisabled) { accessibilityFeatureFlags |= AccessibilityFeature.DISABLE_ANIMATIONS.value; } else { @@ -560,7 +568,7 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { if (shouldBold) { accessibilityFeatureFlags |= AccessibilityFeature.BOLD_TEXT.value; } else { - accessibilityFeatureFlags &= AccessibilityFeature.BOLD_TEXT.value; + accessibilityFeatureFlags &= ~AccessibilityFeature.BOLD_TEXT.value; } sendLatestAccessibilityFlagsToFlutter(); } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 389c329f96c..f86ea7a3b8d 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -13,6 +13,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -28,8 +29,10 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Rect; import android.os.Bundle; +import android.provider.Settings; import android.text.SpannableString; import android.text.SpannedString; import android.text.style.LocaleSpan; @@ -1346,6 +1349,31 @@ public class AccessibilityBridgeTest { /*platformViewsAccessibilityDelegate=*/ null); verify(mockChannel).setAccessibilityFeatures(1 << 3); + reset(mockChannel); + + // Now verify that clearing the BOLD_TEXT flag doesn't touch any of the other flags. + // Ensure the DISABLE_ANIMATION flag will be set + Settings.Global.putFloat(null, "transition_animation_scale", 0.0f); + // Ensure the BOLD_TEXT flag will be cleared + config.fontWeightAdjustment = 0; + + accessibilityBridge = + setUpBridge( + /*rootAccessibilityView=*/ mockRootView, + /*accessibilityChannel=*/ mockChannel, + /*accessibilityManager=*/ mockManager, + /*contentResolver=*/ null, + /*accessibilityViewEmbedder=*/ mockViewEmbedder, + /*platformViewsAccessibilityDelegate=*/ null); + + // setAccessibilityFeatures() will be called multiple times from AccessibilityBridge's + // constructor, verify that the latest argument is correct + ArgumentCaptor captor = ArgumentCaptor.forClass(Integer.class); + verify(mockChannel, atLeastOnce()).setAccessibilityFeatures(captor.capture()); + assertEquals(1 << 2 /* DISABLE_ANIMATION */, captor.getValue().intValue()); + + // Set back to default + Settings.Global.putFloat(null, "transition_animation_scale", 1.0f); } @Test @@ -2012,6 +2040,45 @@ public class AccessibilityBridgeTest { verify(accessibilityViewEmbedder).getRootNode(eq(embeddedView), eq(0), any(Rect.class)); } + @Test + public void testItSetsDisableAnimationsFlagBasedOnTransitionAnimationScale() { + AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); + ContentResolver mockContentResolver = mock(ContentResolver.class); + + AccessibilityBridge accessibilityBridge = + setUpBridge( + /*rootAccessibilityView=*/ null, + /*accessibilityChannel=*/ mockChannel, + /*accessibilityManager=*/ null, + /*contentResolver=*/ mockContentResolver, + /*accessibilityViewEmbedder=*/ null, + /*platformViewsAccessibilityDelegate=*/ null); + + // Capture the observer registered for Settings.Global.TRANSITION_ANIMATION_SCALE + ArgumentCaptor observerCaptor = ArgumentCaptor.forClass(ContentObserver.class); + verify(mockContentResolver) + .registerContentObserver( + eq(Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE)), + eq(false), + observerCaptor.capture()); + ContentObserver observer = observerCaptor.getValue(); + + // Initial state + verify(mockChannel).setAccessibilityFeatures(0); + reset(mockChannel); + + // Animations are disabled + Settings.Global.putFloat(mockContentResolver, "transition_animation_scale", 0.0f); + observer.onChange(false); + verify(mockChannel).setAccessibilityFeatures(1 << 2); + reset(mockChannel); + + // Animations are enabled + Settings.Global.putFloat(mockContentResolver, "transition_animation_scale", 1.0f); + observer.onChange(false); + verify(mockChannel).setAccessibilityFeatures(0); + } + @Test public void releaseDropsChannelMessageHandler() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class);