Merge "settings no hiding when voiceaccess or switchaccess is on" into main
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 572f6ff..1a37e2d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -977,4 +977,13 @@
     Width in pixels of the Side FPS sensor.
     -->
     <integer name="config_sfpsSensorWidth">200</integer>
+
+    <!--
+    They are service names that, if enabled, will cause the magnification settings button
+    to never hide after timeout.
+    -->
+    <string-array name="services_always_show_magnification_settings" translatable="false">
+        <item>com.android.switchaccess.SwitchAccessService</item>
+        <item>com.google.android.apps.accessibility.voiceaccess.JustSpeakService</item>
+    </string-array>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 84f1395..d9d9e37 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -20,12 +20,14 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.UiContext;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -49,6 +51,8 @@
 import com.android.systemui.res.R;
 
 import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
 
 /**
  * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of
@@ -315,10 +319,46 @@
                     DEFAULT_FADE_OUT_ANIMATION_DELAY_MS,
                     AccessibilityManager.FLAG_CONTENT_ICONS
                             | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+            if (shouldAlwaysShowSettings()) {
+                mUiTimeout = -1;
+            }
         }
         // Refresh the time slot of the fade-out task whenever this method is called.
         stopFadeOutAnimation();
-        mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mUiTimeout);
+        if (mUiTimeout >= 0) {
+            mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mUiTimeout);
+        }
+    }
+
+    private boolean shouldAlwaysShowSettings() {
+        try {
+            var serviceNamesArray = mContext.getResources().getStringArray(
+                    R.array.services_always_show_magnification_settings);
+            if (serviceNamesArray.length == 0) {
+                return false;
+            }
+            Set serviceNamesSet = Set.of(serviceNamesArray);
+
+            var serviceInfoList = mAccessibilityManager
+                    .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+            for (var serviceInfo : serviceInfoList) {
+                var serviceName = Optional.ofNullable(serviceInfo)
+                        .map(AccessibilityServiceInfo::getResolveInfo)
+                        .map(resolveInfo -> resolveInfo.serviceInfo)
+                        .map(resolvedServiceInfo -> resolvedServiceInfo.name)
+                        .orElse(null);
+                if (serviceName == null) {
+                    continue;
+                }
+
+                if (serviceNamesSet.contains(serviceName)) {
+                    return true;
+                }
+            }
+        } catch (Resources.NotFoundException nfe) {
+            // No-op. Do not crash for not finding resources.
+        }
+        return false;
     }
 
     private void stopFadeOutAnimation() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 8fd2bd6a..1a88545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -49,8 +49,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -71,8 +74,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
 
 import org.junit.After;
 import org.junit.Before;
@@ -186,6 +189,87 @@
     }
 
     @Test
+    public void showMagnificationButton_noA11yServicesRunning_postDelayedAnimationsWithTimeout() {
+        final int a11yTimeout = 12345;
+        when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
+                a11yTimeout);
+        when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt()))
+                .thenReturn(List.of());
+
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        verify(mAccessibilityManager).getRecommendedTimeoutMillis(
+                DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
+                        | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        verify(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), eq((long) a11yTimeout));
+    }
+
+    @Test
+    public void showMagnificationButton_voiceAccessRunning_noTimeout() {
+        var serviceInfo = createServiceInfoWithName(
+                "com.google.android.apps.accessibility.voiceaccess.JustSpeakService");
+        when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt()))
+                .thenReturn(List.of(serviceInfo));
+
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
+    public void showMagnificationButton_switchAccessRunning_noTimeout() {
+        var serviceInfo = createServiceInfoWithName(
+                "com.android.switchaccess.SwitchAccessService");
+        when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt()))
+                .thenReturn(List.of(serviceInfo));
+
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
+    public void showMagnificationButton_switchAccessAndVoiceAccessBothRunning_noTimeout() {
+        var switchAccessServiceInfo = createServiceInfoWithName(
+                "com.android.switchaccess.SwitchAccessService");
+        var voiceAccessServiceInfo = createServiceInfoWithName(
+                "com.google.android.apps.accessibility.voiceaccess.JustSpeakService");
+        when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt()))
+                .thenReturn(List.of(switchAccessServiceInfo, voiceAccessServiceInfo));
+
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong());
+    }
+
+    @Test
+    public void showMagnificationButton_someOtherServiceRunning_postDelayedAnimationsWithTimeout() {
+        final int a11yTimeout = 12345;
+        when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
+                a11yTimeout);
+        var serviceInfo1 = createServiceInfoWithName("com.test.someService1");
+        var serviceInfo2 = createServiceInfoWithName("com.test.someService2");
+        when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt()))
+                .thenReturn(List.of(serviceInfo1, serviceInfo2));
+
+        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+        verify(mAccessibilityManager).getRecommendedTimeoutMillis(
+                DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
+                        | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+        verify(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), eq((long) a11yTimeout));
+    }
+
+    private AccessibilityServiceInfo createServiceInfoWithName(String name) {
+        var resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.name = name;
+        var serviceInfo = new AccessibilityServiceInfo();
+        serviceInfo.setResolveInfo(resolveInfo);
+        return serviceInfo;
+    }
+
+    @Test
     public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
         mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
         executeFadeOutAnimation();