1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.accessibilityservice.cts;
18 
19 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
20 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
21 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
22 import static android.accessibilityservice.cts.utils.CtsTestUtils.isAutomotive;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assume.assumeFalse;
28 
29 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
30 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
31 import android.accessibilityservice.AccessibilityService;
32 import android.accessibilityservice.AccessibilityService.MagnificationController;
33 import android.accessibilityservice.AccessibilityServiceInfo;
34 import android.accessibilityservice.MagnificationConfig;
35 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
36 import android.app.Instrumentation;
37 import android.app.UiAutomation;
38 import android.graphics.Rect;
39 import android.platform.test.annotations.AppModeFull;
40 import android.platform.test.annotations.Presubmit;
41 
42 import androidx.test.rule.ActivityTestRule;
43 import androidx.test.runner.AndroidJUnit4;
44 
45 import com.android.compatibility.common.util.CddTest;
46 import com.android.compatibility.common.util.TestUtils;
47 
48 import org.junit.AfterClass;
49 import org.junit.Before;
50 import org.junit.BeforeClass;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.rules.RuleChain;
54 import org.junit.runner.RunWith;
55 
56 import java.util.concurrent.atomic.AtomicBoolean;
57 
58 /**
59  * Class for testing {@See FullScreenMagnificationController}.
60  */
61 @AppModeFull
62 @RunWith(AndroidJUnit4.class)
63 @CddTest(requirements = {"3.10/C-1-1,C-1-2"})
64 @Presubmit
65 public class FullScreenMagnificationControllerTest {
66 
67     /** Maximum timeout while waiting for a config to be updated */
68     private static final int TIMEOUT_CONFIG_SECONDS = 15;
69     private static final int BOUNDS_TOLERANCE = 1;
70 
71     private static Instrumentation sInstrumentation;
72     private static UiAutomation sUiAutomation;
73     private StubMagnificationAccessibilityService mService;
74 
75     private final ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
76             new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
77 
78     private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
79             mMagnificationAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
80             StubMagnificationAccessibilityService.class, false);
81 
82     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
83             new AccessibilityDumpOnFailureRule();
84 
85     @Rule
86     public final RuleChain mRuleChain = RuleChain
87             .outerRule(mActivityRule)
88             .around(mMagnificationAccessibilityServiceRule)
89             .around(mDumpOnFailureRule);
90 
91     @BeforeClass
oneTimeSetup()92     public static void oneTimeSetup() {
93         sInstrumentation = androidx.test.platform.app.InstrumentationRegistry.getInstrumentation();
94         sUiAutomation = sInstrumentation.getUiAutomation();
95         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
96         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
97         sUiAutomation.setServiceInfo(info);
98     }
99 
100     @AfterClass
postTestTearDown()101     public static void postTestTearDown() {
102         sUiAutomation.destroy();
103     }
104 
105     @Before
setUp()106     public void setUp() throws Exception {
107         assumeFalse("Magnification is not supported on Automotive.",
108                 isAutomotive(sInstrumentation.getTargetContext()));
109 
110         mService = mMagnificationAccessibilityServiceRule.enableService();
111     }
112 
113     @Test
testActivityTransitions_fullscreenMagnifierMagnifying_zoomOut()114     public void testActivityTransitions_fullscreenMagnifierMagnifying_zoomOut() throws Exception {
115         // wait for the activity to be on screen
116         launchActivityAndWaitForItToBeOnscreen(sInstrumentation, sUiAutomation, mActivityRule);
117 
118         zoomIn(/* scale= */ 2.0f);
119         // transition to home screen
120         homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
121 
122         // we cannot identify if the always on feature flag is enabled here, so we cannot ensure if
123         // the magnifier should deactivate itself when transitions. At least we can still verify
124         // the fullscreen magnifier zooming out here.
125         assertThat(currentScale()).isEqualTo(1f);
126     }
127 
zoomIn(float scale)128     private void zoomIn(float scale) throws Exception {
129         final MagnificationController controller = mService.getMagnificationController();
130         final Rect rect = controller.getMagnificationRegion().getBounds();
131         final float x = rect.centerX();
132         final float y = rect.centerY();
133         final AtomicBoolean setConfig = new AtomicBoolean();
134 
135         final MagnificationConfig config = new MagnificationConfig.Builder()
136                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
137                 .setScale(scale)
138                 .setCenterX(x)
139                 .setCenterY(y).build();
140 
141         mService.runOnServiceSync(() -> {
142             setConfig.set(controller.setMagnificationConfig(config, false));
143         });
144         waitUntilMagnificationConfigEquals(controller, config);
145 
146         assertTrue("Failed to set config", setConfig.get());
147     }
148 
currentScale()149     private float currentScale() {
150         final MagnificationController controller = mService.getMagnificationController();
151         final MagnificationConfig config = controller.getMagnificationConfig();
152 
153         assertThat(config).isNotNull();
154 
155         return config.getScale();
156     }
157 
waitUntilMagnificationConfigEquals( AccessibilityService.MagnificationController controller, MagnificationConfig config)158     private void waitUntilMagnificationConfigEquals(
159             AccessibilityService.MagnificationController controller,
160             MagnificationConfig config) throws Exception {
161         TestUtils.waitUntil(
162                 "Failed to apply the config. expected: " + config + " , actual: "
163                         + controller.getMagnificationConfig(), TIMEOUT_CONFIG_SECONDS,
164                 () -> {
165                     final MagnificationConfig actualConfig = controller.getMagnificationConfig();
166                     // If expected config activated is false, we just need to verify the activated
167                     // value is the same. Otherwise, we need to check all the actual values are
168                     // equal to the expected values.
169                     if (config.isActivated()) {
170                         return actualConfig.getMode() == config.getMode()
171                                 && actualConfig.isActivated() == config.isActivated()
172                                 && Float.compare(actualConfig.getScale(), config.getScale()) == 0
173                                 && (Math.abs(actualConfig.getCenterX() - config.getCenterX())
174                                 <= BOUNDS_TOLERANCE)
175                                 && (Math.abs(actualConfig.getCenterY() - config.getCenterY())
176                                 <= BOUNDS_TOLERANCE);
177                     } else {
178                         return actualConfig.isActivated() == config.isActivated();
179                     }
180                 });
181     }
182 }
183