1 /* <lambda>null2 * 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 android.input.cts 18 19 import android.Manifest 20 import android.app.Activity 21 import android.app.ActivityOptions 22 import android.app.Instrumentation 23 import android.content.Context 24 import android.content.pm.PackageManager 25 import android.graphics.PixelFormat 26 import android.graphics.Point 27 import android.hardware.display.DisplayManager 28 import android.hardware.display.VirtualDisplay 29 import android.media.ImageReader 30 import android.os.Handler 31 import android.os.Looper 32 import android.os.SystemClock 33 import android.server.wm.CtsWindowInfoUtils 34 import android.server.wm.WindowManagerStateHelper 35 import android.support.test.uiautomator.UiDevice 36 import android.view.Display 37 import android.view.InputDevice 38 import android.view.MotionEvent 39 import android.view.MotionEvent.ACTION_DOWN 40 import android.view.MotionEvent.ACTION_UP 41 import android.view.View 42 import android.view.ViewTreeObserver 43 import androidx.test.core.app.ActivityScenario 44 import androidx.test.ext.junit.rules.ActivityScenarioRule 45 import androidx.test.ext.junit.runners.AndroidJUnit4 46 import androidx.test.filters.MediumTest 47 import androidx.test.platform.app.InstrumentationRegistry 48 import com.android.compatibility.common.util.PollingCheck 49 import com.android.compatibility.common.util.SystemUtil 50 import com.android.compatibility.common.util.UserHelper 51 import com.android.compatibility.common.util.WindowUtil 52 import com.google.common.truth.Truth.assertThat 53 import java.lang.AutoCloseable 54 import java.time.Duration 55 import java.util.Arrays 56 import java.util.concurrent.CountDownLatch 57 import java.util.concurrent.TimeUnit 58 import org.junit.After 59 import org.junit.Assert.fail 60 import org.junit.Assume.assumeFalse 61 import org.junit.Assume.assumeTrue 62 import org.junit.Before 63 import org.junit.Ignore 64 import org.junit.Rule 65 import org.junit.Test 66 import org.junit.runner.RunWith 67 68 private const val TOUCH_MODE_PROPAGATION_TIMEOUT_MILLIS: Long = 5000 // 5 sec 69 70 @MediumTest 71 @RunWith(AndroidJUnit4::class) 72 class TouchModeTest { 73 private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() 74 private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) 75 private var virtualDisplay: VirtualDisplay? = null 76 private var imageReader: ImageReader? = null 77 78 @get:Rule 79 val activityRule = ActivityScenarioRule<Activity>(Activity::class.java) 80 private lateinit var activity: Activity 81 private lateinit var targetContext: Context 82 private lateinit var displayManager: DisplayManager 83 private lateinit var userHelper: UserHelper 84 private var secondScenario: ActivityScenario<Activity>? = null 85 86 @Before 87 fun setUp() { 88 targetContext = instrumentation.targetContext 89 userHelper = UserHelper(targetContext) 90 // This test is not applicable to background users. Tests may rely on the ability to call 91 // Instrumentation#setInTouchMode, which only functions for the MAIN_DISPLAY. Tests for 92 // secondary background visible users, triggered with the 93 // secondary_user_on_secondary_display flag, will run on a secondary display and not the 94 // MAIN_DISPLAY. 95 assumeFalse(userHelper.isVisibleBackgroundUser) 96 displayManager = targetContext.getSystemService(DisplayManager::class.java) 97 activityRule.scenario.onActivity { 98 activity = it 99 } 100 WindowUtil.waitForFocus(activity) 101 instrumentation.setInTouchMode(false) 102 } 103 104 @After 105 fun tearDown() { 106 val scenario = secondScenario 107 if (scenario != null) { 108 scenario.close() 109 } 110 val display = virtualDisplay 111 if (display != null) { 112 display.release() 113 } 114 val reader = imageReader 115 if (reader != null) { 116 reader.close() 117 } 118 } 119 120 fun isInTouchMode(): Boolean { 121 return activity.window.decorView.isInTouchMode 122 } 123 124 fun isRunningActivitiesOnSecondaryDisplaysSupported(): Boolean { 125 return instrumentation.context.packageManager.hasSystemFeature( 126 PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS 127 ) 128 } 129 130 @Test 131 fun testFocusedWindowOwnerCanChangeTouchMode() { 132 instrumentation.setInTouchMode(true) 133 PollingCheck.waitFor({ isInTouchMode() }, "Expected to be in touch mode") 134 } 135 136 @Test 137 fun testOnTouchModeChangeNotification() { 138 val touchModeChangeListener = OnTouchModeChangeListenerImpl() 139 val observer = activity.window.decorView.rootView.viewTreeObserver 140 observer.addOnTouchModeChangeListener(touchModeChangeListener) 141 val newTouchMode = !isInTouchMode() 142 143 instrumentation.setInTouchMode(newTouchMode) 144 try { 145 assertThat(touchModeChangeListener.countDownLatch.await( 146 TOUCH_MODE_PROPAGATION_TIMEOUT_MILLIS, 147 TimeUnit.MILLISECONDS 148 )).isTrue() 149 } catch (e: InterruptedException) { 150 Thread.currentThread().interrupt() 151 throw RuntimeException(e) 152 } 153 154 assertThat(touchModeChangeListener.isInTouchMode).isEqualTo(newTouchMode) 155 } 156 157 private class OnTouchModeChangeListenerImpl : ViewTreeObserver.OnTouchModeChangeListener { 158 val countDownLatch = CountDownLatch(1) 159 var isInTouchMode = false 160 161 override fun onTouchModeChanged(mode: Boolean) { 162 isInTouchMode = mode 163 countDownLatch.countDown() 164 } 165 } 166 167 @Test 168 fun testNonFocusedWindowOwnerCannotChangeTouchMode() { 169 // It takes 400-500 milliseconds in average for DecorView to receive the touch mode changed 170 // event on 2021 hardware, so we set the timeout to 10x that. It's still possible that a 171 // test would fail, but we don't have a better way to check that an event does not occur. 172 // Due to the 2 expected touch mode events to occur, this test may take few seconds to run. 173 uiDevice.pressHome() 174 WindowManagerStateHelper().waitForAppTransitionIdleOnDisplay(activity.display.displayId) 175 PollingCheck.waitFor( 176 WindowUtil.WINDOW_FOCUS_TIMEOUT_MILLIS, 177 { !activity.hasWindowFocus() }, 178 "Activity expected to not receive focus" 179 ) 180 181 instrumentation.setInTouchMode(true) 182 183 SystemClock.sleep(TOUCH_MODE_PROPAGATION_TIMEOUT_MILLIS) 184 assertThat(isInTouchMode()).isFalse() 185 } 186 187 @Test 188 fun testDetachedViewReturnsDefaultTouchMode() { 189 val context = instrumentation.targetContext 190 val defaultInTouchMode = context.resources.getBoolean( 191 context.resources.getIdentifier("config_defaultInTouchMode", "bool", "android") 192 ) 193 194 val detachedView = View(activity) 195 196 // Detached view (view with mAttachInfo null) will just return the default touch mode value 197 assertThat(detachedView.isInTouchMode()).isEqualTo(defaultInTouchMode) 198 } 199 200 /** 201 * When per-display focus is disabled ({@code config_perDisplayFocusEnabled} is set to false), 202 * touch mode changes affect all displays. 203 * 204 * In this test, we tap the main display, and ensure that touch mode becomes 205 * true on both the main display and the secondary display 206 */ 207 @Test 208 @Ignore("b/371247555") 209 fun testTouchModeUpdate_PerDisplayFocusDisabled() { 210 assumeTrue(isRunningActivitiesOnSecondaryDisplaysSupported()) 211 assumeFalse( 212 "This test requires config_perDisplayFocusEnabled to be false", 213 targetContext.resources.getBoolean(targetContext.resources.getIdentifier( 214 "config_perDisplayFocusEnabled", 215 "bool", 216 "android", 217 )) 218 ) 219 220 val secondaryDisplayId = findOrCreateSecondaryDisplay() 221 222 touchDownOnDefaultDisplay().use { 223 PollingCheck.waitFor({ isInTouchMode() }, "Expected to be in touch mode") 224 assertSecondaryDisplayTouchModeState(secondaryDisplayId, isInTouch = true) 225 } 226 } 227 228 /** 229 * When per-display focus is enabled ({@code config_perDisplayFocusEnabled} is set to true), 230 * touch mode changes does not affect all displays. 231 * 232 * In this test, we tap the main display, and ensure that touch mode becomes 233 * true on main display only. Touch mode on secondary display must remain false. 234 */ 235 @Test 236 fun testTouchModeUpdate_PerDisplayFocusEnabled() { 237 assumeTrue(isRunningActivitiesOnSecondaryDisplaysSupported()) 238 assumeTrue( 239 "This test requires config_perDisplayFocusEnabled to be true", 240 targetContext.resources.getBoolean(targetContext.resources.getIdentifier( 241 "config_perDisplayFocusEnabled", 242 "bool", 243 "android" 244 )) 245 ) 246 247 val secondaryDisplayId = findOrCreateSecondaryDisplay() 248 touchDownOnDefaultDisplay().use { 249 PollingCheck.waitFor({ isInTouchMode() }, "Expected to be in touch mode") 250 assertSecondaryDisplayTouchModeState( 251 secondaryDisplayId, 252 isInTouch = false, 253 delayBeforeChecking = true 254 ) 255 } 256 } 257 258 /** 259 * Regardless of the {@code config_perDisplayFocusEnabled} value, 260 * touch mode changes does not affect displays with own focus. 261 * 262 * In this test, we tap the main display, and ensure that touch mode becomes 263 * true only on the main display. Touch mode on the secondary display must remain false because 264 * it maintains its own focus and touch mode. 265 */ 266 @Test 267 fun testTouchModeUpdate_DisplayHasOwnFocus() { 268 assumeTrue(isRunningActivitiesOnSecondaryDisplaysSupported()) 269 var secondaryDisplayId = 0 270 SystemUtil.runWithShellPermissionIdentity({ 271 secondaryDisplayId = createVirtualDisplay( 272 VIRTUAL_DISPLAY_FLAG_OWN_FOCUS or VIRTUAL_DISPLAY_FLAG_TRUSTED) 273 }, Manifest.permission.ADD_TRUSTED_DISPLAY) 274 275 touchDownOnDefaultDisplay().use { 276 PollingCheck.waitFor({ isInTouchMode() }, "Expected to be in touch mode") 277 assertSecondaryDisplayTouchModeState( 278 secondaryDisplayId, 279 isInTouch = false, 280 delayBeforeChecking = true 281 ) 282 } 283 } 284 285 private fun findOrCreateSecondaryDisplay(): Int { 286 // Pick a random secondary external display if there is any. 287 // A virtual display is only created if the device only has a single (default) display. 288 val display = Arrays.stream(displayManager.displays).filter { d -> 289 d.displayId != Display.DEFAULT_DISPLAY && d.type == Display.TYPE_EXTERNAL 290 }.findFirst() 291 if (display.isEmpty) { 292 return createVirtualDisplay(flags = 0) 293 } 294 return display.get().displayId 295 } 296 297 private fun assertSecondaryDisplayTouchModeState( 298 displayId: Int, 299 isInTouch: Boolean, 300 delayBeforeChecking: Boolean = false 301 ) { 302 if (delayBeforeChecking) { 303 SystemClock.sleep(TOUCH_MODE_PROPAGATION_TIMEOUT_MILLIS) 304 } 305 PollingCheck.waitFor(TOUCH_MODE_PROPAGATION_TIMEOUT_MILLIS) { 306 isSecondaryDisplayInTouchMode(displayId) == isInTouch 307 } 308 } 309 310 private fun isSecondaryDisplayInTouchMode(displayId: Int): Boolean { 311 if (secondScenario == null) { 312 launchSecondScenarioActivity(displayId) 313 } 314 val scenario = secondScenario 315 var inTouch: Boolean? = null 316 if (scenario != null) { 317 scenario.onActivity { 318 inTouch = it.window.decorView.isInTouchMode 319 } 320 } else { 321 fail("Fail to launch secondScenario") 322 } 323 return inTouch == true 324 } 325 326 private fun launchSecondScenarioActivity(displayId: Int) { 327 // Launch activity on the picked display 328 val bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle() 329 SystemUtil.runWithShellPermissionIdentity({ 330 secondScenario = ActivityScenario.launch(Activity::class.java, bundle) 331 }, Manifest.permission.INTERNAL_SYSTEM_WINDOW) 332 } 333 334 private fun touchDownOnDefaultDisplay(): AutoCloseable { 335 CtsWindowInfoUtils.waitForStableWindowGeometry(Duration.ofSeconds(5)) 336 val downTime = SystemClock.uptimeMillis() 337 val location = IntArray(2) 338 activity.getWindow().getDecorView().getLocationOnScreen(location) 339 val windowLocationOnScreen = Point(location[0], location[1]) 340 341 val down = MotionEvent.obtain( 342 downTime, 343 downTime, 344 ACTION_DOWN, 345 windowLocationOnScreen.x.toFloat(), 346 windowLocationOnScreen.y.toFloat(), 347 0 // metaState 348 ) 349 down.source = InputDevice.SOURCE_TOUCHSCREEN 350 val sync = true 351 instrumentation.uiAutomation.injectInputEvent(down, sync) 352 353 // Clean up by sending an up event so that we ensure gestures are injected consistently. 354 return AutoCloseable { 355 val upEventTime = SystemClock.uptimeMillis() 356 val up = MotionEvent.obtain( 357 downTime, 358 upEventTime, 359 ACTION_UP, 360 windowLocationOnScreen.x.toFloat(), 361 windowLocationOnScreen.y.toFloat(), 362 0 // metaState 363 ) 364 up.source = InputDevice.SOURCE_TOUCHSCREEN 365 instrumentation.uiAutomation.injectInputEvent(up, sync) 366 } 367 } 368 369 private fun createVirtualDisplay(flags: Int): Int { 370 val displayCreated = CountDownLatch(1) 371 displayManager.registerDisplayListener(object : DisplayManager.DisplayListener { 372 override fun onDisplayAdded(displayId: Int) {} 373 override fun onDisplayRemoved(displayId: Int) {} 374 override fun onDisplayChanged(displayId: Int) { 375 displayCreated.countDown() 376 displayManager.unregisterDisplayListener(this) 377 } 378 }, Handler(Looper.getMainLooper())) 379 imageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2) 380 val reader = imageReader 381 virtualDisplay = displayManager.createVirtualDisplay( 382 VIRTUAL_DISPLAY_NAME, WIDTH, HEIGHT, DENSITY, reader!!.surface, flags) 383 384 assertThat(displayCreated.await(5, TimeUnit.SECONDS)).isTrue() 385 assertThat(virtualDisplay).isNotNull() 386 instrumentation.setInTouchMode(false) 387 return virtualDisplay!!.display.displayId 388 } 389 390 companion object { 391 const val VIRTUAL_DISPLAY_NAME = "CtsVirtualDisplay" 392 const val WIDTH = 480 393 const val HEIGHT = 800 394 const val DENSITY = 160 395 396 /** See [DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS]. */ 397 const val VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 shl 14 398 399 /** See [DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED]. */ 400 const val VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 shl 10 401 } 402 } 403