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 com.android.wm.shell.flicker.service.splitscreen.scenarios
18 
19 import android.app.Instrumentation
20 import android.graphics.Point
21 import android.tools.common.NavBar
22 import android.tools.common.Rotation
23 import android.tools.device.helpers.WindowUtils
24 import android.tools.device.traces.parsers.WindowManagerStateHelper
25 import androidx.test.platform.app.InstrumentationRegistry
26 import androidx.test.uiautomator.UiDevice
27 import com.android.launcher3.tapl.LauncherInstrumentation
28 import com.android.wm.shell.flicker.service.Utils
29 import com.android.wm.shell.flicker.utils.SplitScreenUtils
30 import org.junit.After
31 import org.junit.Before
32 import org.junit.Ignore
33 import org.junit.Rule
34 import org.junit.Test
35 
36 @Ignore("Base Test Class")
37 abstract class SwitchAppByDoubleTapDivider
38 @JvmOverloads
39 constructor(val rotation: Rotation = Rotation.ROTATION_0) {
40     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
41     private val tapl = LauncherInstrumentation()
42     private val wmHelper = WindowManagerStateHelper(instrumentation)
43     private val device = UiDevice.getInstance(instrumentation)
44     private val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
45     private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
46 
47     @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
48 
49     @Before
50     fun setup() {
51         tapl.setEnableRotation(true)
52         tapl.setExpectedRotation(rotation.value)
53         tapl.workspace.switchToOverview().dismissAllTasks()
54 
55         SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
56     }
57 
58     @Test
59     open fun switchAppByDoubleTapDivider() {
60         SplitScreenUtils.doubleTapDividerToSwitch(device)
61         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
62 
63         waitForLayersToSwitch(wmHelper)
64         waitForWindowsToSwitch(wmHelper)
65     }
66 
67     @After
68     fun teardown() {
69         primaryApp.exit(wmHelper)
70         secondaryApp.exit(wmHelper)
71     }
72 
73     private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
74         wmHelper
75             .StateSyncBuilder()
76             .add("appWindowsSwitched") {
77                 val primaryAppWindow =
78                     it.wmState.visibleWindows.firstOrNull { window ->
79                         primaryApp.windowMatchesAnyOf(window)
80                     }
81                         ?: return@add false
82                 val secondaryAppWindow =
83                     it.wmState.visibleWindows.firstOrNull { window ->
84                         secondaryApp.windowMatchesAnyOf(window)
85                     }
86                         ?: return@add false
87 
88                 if (isLandscape(rotation)) {
89                     return@add if (isTablet()) {
90                         secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
91                     } else {
92                         primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
93                     }
94                 } else {
95                     return@add if (isTablet()) {
96                         primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
97                     } else {
98                         primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
99                     }
100                 }
101             }
102             .waitForAndVerify()
103     }
104 
105     private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
106         wmHelper
107             .StateSyncBuilder()
108             .add("appLayersSwitched") {
109                 val primaryAppLayer =
110                     it.layerState.visibleLayers.firstOrNull { window ->
111                         primaryApp.layerMatchesAnyOf(window)
112                     }
113                         ?: return@add false
114                 val secondaryAppLayer =
115                     it.layerState.visibleLayers.firstOrNull { window ->
116                         secondaryApp.layerMatchesAnyOf(window)
117                     }
118                         ?: return@add false
119 
120                 val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
121                 val secondaryVisibleRegion =
122                     secondaryAppLayer.visibleRegion?.bounds ?: return@add false
123 
124                 if (isLandscape(rotation)) {
125                     return@add if (isTablet()) {
126                         secondaryVisibleRegion.right <= primaryVisibleRegion.left
127                     } else {
128                         primaryVisibleRegion.right <= secondaryVisibleRegion.left
129                     }
130                 } else {
131                     return@add if (isTablet()) {
132                         primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
133                     } else {
134                         primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
135                     }
136                 }
137             }
138             .waitForAndVerify()
139     }
140 
141     private fun isLandscape(rotation: Rotation): Boolean {
142         val displayBounds = WindowUtils.getDisplayBounds(rotation)
143         return displayBounds.width > displayBounds.height
144     }
145 
146     private fun isTablet(): Boolean {
147         val sizeDp: Point = device.displaySizeDp
148         val LARGE_SCREEN_DP_THRESHOLD = 600
149         return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
150     }
151 }
152