1 /* 2 * Copyright (C) 2022 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 com.android.systemui.accessibility.floatingmenu; 18 19 import static android.view.View.GONE; 20 import static android.view.View.VISIBLE; 21 import static android.view.WindowInsets.Type.displayCutout; 22 import static android.view.WindowInsets.Type.ime; 23 import static android.view.WindowInsets.Type.systemBars; 24 25 import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import static org.mockito.Mockito.doReturn; 30 import static org.mockito.Mockito.spy; 31 import static org.mockito.Mockito.verify; 32 import static org.mockito.Mockito.when; 33 34 import android.accessibilityservice.AccessibilityServiceInfo; 35 import android.content.ComponentName; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.ResolveInfo; 38 import android.content.pm.ServiceInfo; 39 import android.graphics.Insets; 40 import android.graphics.PointF; 41 import android.graphics.Rect; 42 import android.os.Build; 43 import android.os.UserHandle; 44 import android.provider.Settings; 45 import android.testing.AndroidTestingRunner; 46 import android.testing.TestableLooper; 47 import android.view.View; 48 import android.view.WindowInsets; 49 import android.view.WindowManager; 50 import android.view.WindowMetrics; 51 import android.view.accessibility.AccessibilityManager; 52 53 import androidx.test.filters.SmallTest; 54 55 import com.android.systemui.SysuiTestCase; 56 import com.android.systemui.util.settings.SecureSettings; 57 58 import org.junit.After; 59 import org.junit.Before; 60 import org.junit.Rule; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 import org.mockito.Mock; 64 import org.mockito.junit.MockitoJUnit; 65 import org.mockito.junit.MockitoRule; 66 67 import java.util.ArrayList; 68 import java.util.List; 69 70 /** Tests for {@link MenuViewLayer}. */ 71 @RunWith(AndroidTestingRunner.class) 72 @TestableLooper.RunWithLooper(setAsMainLooper = true) 73 @SmallTest 74 public class MenuViewLayerTest extends SysuiTestCase { 75 private static final String SELECT_TO_SPEAK_PACKAGE_NAME = "com.google.android.marvin.talkback"; 76 private static final String SELECT_TO_SPEAK_SERVICE_NAME = 77 "com.google.android.accessibility.selecttospeak.SelectToSpeakService"; 78 private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName( 79 SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME); 80 81 private static final int DISPLAY_WINDOW_WIDTH = 1080; 82 private static final int DISPLAY_WINDOW_HEIGHT = 2340; 83 private static final int STATUS_BAR_HEIGHT = 75; 84 private static final int NAVIGATION_BAR_HEIGHT = 125; 85 private static final int IME_HEIGHT = 350; 86 private static final int IME_TOP = 87 DISPLAY_WINDOW_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT - IME_HEIGHT; 88 89 private MenuViewLayer mMenuViewLayer; 90 private String mLastAccessibilityButtonTargets; 91 private String mLastEnabledAccessibilityServices; 92 private WindowMetrics mWindowMetrics; 93 private MenuView mMenuView; 94 private MenuAnimationController mMenuAnimationController; 95 96 @Rule 97 public MockitoRule mockito = MockitoJUnit.rule(); 98 99 @Mock 100 private IAccessibilityFloatingMenu mFloatingMenu; 101 102 @Mock 103 private SecureSettings mSecureSettings; 104 105 @Mock 106 private WindowManager mStubWindowManager; 107 108 @Mock 109 private AccessibilityManager mStubAccessibilityManager; 110 111 @Before setUp()112 public void setUp() throws Exception { 113 final Rect mDisplayBounds = new Rect(); 114 mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH, 115 DISPLAY_WINDOW_HEIGHT); 116 mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets())); 117 doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics(); 118 119 mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager, 120 mFloatingMenu, mSecureSettings); 121 mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); 122 mMenuAnimationController = mMenuView.getMenuAnimationController(); 123 124 mLastAccessibilityButtonTargets = 125 Settings.Secure.getStringForUser(mContext.getContentResolver(), 126 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT); 127 mLastEnabledAccessibilityServices = 128 Settings.Secure.getStringForUser(mContext.getContentResolver(), 129 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT); 130 131 mMenuViewLayer.onAttachedToWindow(); 132 Settings.Secure.putStringForUser(mContext.getContentResolver(), 133 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT); 134 Settings.Secure.putStringForUser(mContext.getContentResolver(), 135 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT); 136 } 137 138 @After tearDown()139 public void tearDown() throws Exception { 140 Settings.Secure.putStringForUser(mContext.getContentResolver(), 141 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets, 142 UserHandle.USER_CURRENT); 143 Settings.Secure.putStringForUser(mContext.getContentResolver(), 144 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices, 145 UserHandle.USER_CURRENT); 146 147 mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); 148 mMenuViewLayer.onDetachedFromWindow(); 149 } 150 151 @Test onAttachedToWindow_menuIsVisible()152 public void onAttachedToWindow_menuIsVisible() { 153 mMenuViewLayer.onAttachedToWindow(); 154 final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); 155 156 assertThat(menuView.getVisibility()).isEqualTo(VISIBLE); 157 } 158 159 @Test onAttachedToWindow_menuIsGone()160 public void onAttachedToWindow_menuIsGone() { 161 mMenuViewLayer.onDetachedFromWindow(); 162 final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); 163 164 assertThat(menuView.getVisibility()).isEqualTo(GONE); 165 } 166 167 @Test triggerDismissMenuAction_hideFloatingMenu()168 public void triggerDismissMenuAction_hideFloatingMenu() { 169 mMenuViewLayer.mDismissMenuAction.run(); 170 171 verify(mFloatingMenu).hide(); 172 } 173 174 @Test triggerDismissMenuAction_matchA11yButtonTargetsResult()175 public void triggerDismissMenuAction_matchA11yButtonTargetsResult() { 176 mMenuViewLayer.mDismissMenuAction.run(); 177 verify(mSecureSettings).putStringForUser( 178 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "", 179 UserHandle.USER_CURRENT); 180 } 181 182 @Test triggerDismissMenuAction_matchEnabledA11yServicesResult()183 public void triggerDismissMenuAction_matchEnabledA11yServicesResult() { 184 setupEnabledAccessibilityServiceList(); 185 186 mMenuViewLayer.mDismissMenuAction.run(); 187 final String value = Settings.Secure.getString(mContext.getContentResolver(), 188 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 189 190 assertThat(value).isEqualTo(""); 191 } 192 193 @Test triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus()194 public void triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus() { 195 setupEnabledAccessibilityServiceList(); 196 final List<String> stubShortcutTargets = new ArrayList<>(); 197 stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString()); 198 when(mStubAccessibilityManager.getAccessibilityShortcutTargets( 199 AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets); 200 201 mMenuViewLayer.mDismissMenuAction.run(); 202 final String value = Settings.Secure.getString(mContext.getContentResolver(), 203 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 204 205 assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString()); 206 } 207 208 @Test showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition()209 public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() { 210 final float menuTop = STATUS_BAR_HEIGHT + 100; 211 mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); 212 213 dispatchShowingImeInsets(); 214 215 assertThat(mMenuView.getTranslationX()).isEqualTo(0); 216 assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); 217 } 218 219 @Test showingImeInsetsChange_overlapOnIme_menuShownAboveIme()220 public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { 221 final float menuTop = IME_TOP + 100; 222 mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); 223 224 dispatchShowingImeInsets(); 225 226 final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); 227 assertThat(mMenuView.getTranslationX()).isEqualTo(0); 228 assertThat(menuBottom).isLessThan(IME_TOP); 229 } 230 231 @Test hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition()232 public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { 233 final float menuTop = IME_TOP + 200; 234 mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop)); 235 dispatchShowingImeInsets(); 236 237 dispatchHidingImeInsets(); 238 239 assertThat(mMenuView.getTranslationX()).isEqualTo(0); 240 assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop); 241 } 242 setupEnabledAccessibilityServiceList()243 private void setupEnabledAccessibilityServiceList() { 244 Settings.Secure.putString(mContext.getContentResolver(), 245 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 246 TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString()); 247 248 final ResolveInfo resolveInfo = new ResolveInfo(); 249 final ServiceInfo serviceInfo = new ServiceInfo(); 250 final ApplicationInfo applicationInfo = new ApplicationInfo(); 251 resolveInfo.serviceInfo = serviceInfo; 252 serviceInfo.applicationInfo = applicationInfo; 253 applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; 254 final AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo(); 255 accessibilityServiceInfo.setResolveInfo(resolveInfo); 256 accessibilityServiceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; 257 final List<AccessibilityServiceInfo> serviceInfoList = new ArrayList<>(); 258 accessibilityServiceInfo.setComponentName(TEST_SELECT_TO_SPEAK_COMPONENT_NAME); 259 serviceInfoList.add(accessibilityServiceInfo); 260 when(mStubAccessibilityManager.getEnabledAccessibilityServiceList( 261 AccessibilityServiceInfo.FEEDBACK_ALL_MASK)).thenReturn(serviceInfoList); 262 } 263 dispatchShowingImeInsets()264 private void dispatchShowingImeInsets() { 265 final WindowInsets fakeShowingImeInsets = fakeImeInsets(/* isImeVisible= */ true); 266 doReturn(fakeShowingImeInsets).when(mWindowMetrics).getWindowInsets(); 267 mMenuViewLayer.dispatchApplyWindowInsets(fakeShowingImeInsets); 268 } 269 dispatchHidingImeInsets()270 private void dispatchHidingImeInsets() { 271 final WindowInsets fakeHidingImeInsets = fakeImeInsets(/* isImeVisible= */ false); 272 doReturn(fakeHidingImeInsets).when(mWindowMetrics).getWindowInsets(); 273 mMenuViewLayer.dispatchApplyWindowInsets(fakeHidingImeInsets); 274 } 275 fakeDisplayInsets()276 private WindowInsets fakeDisplayInsets() { 277 return new WindowInsets.Builder() 278 .setVisible(systemBars() | displayCutout(), /* visible= */ true) 279 .setInsets(systemBars() | displayCutout(), 280 Insets.of(/* left= */ 0, STATUS_BAR_HEIGHT, /* right= */ 0, 281 NAVIGATION_BAR_HEIGHT)) 282 .build(); 283 } 284 fakeImeInsets(boolean isImeVisible)285 private WindowInsets fakeImeInsets(boolean isImeVisible) { 286 final int bottom = isImeVisible ? (IME_HEIGHT + NAVIGATION_BAR_HEIGHT) : 0; 287 return new WindowInsets.Builder() 288 .setVisible(ime(), isImeVisible) 289 .setInsets(ime(), 290 Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom)) 291 .build(); 292 } 293 } 294