• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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