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