1 /*
2  * Copyright (C) 2021 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 package android.platform.test.rule
17 
18 import android.app.ActivityManager
19 import android.graphics.Rect
20 import android.platform.test.rule.Orientation.LANDSCAPE
21 import android.platform.test.rule.Orientation.NATURAL
22 import android.platform.test.rule.Orientation.PORTRAIT
23 import android.platform.test.rule.RotationUtils.clearOrientationOverride
24 import android.platform.test.rule.RotationUtils.setOrientationOverride
25 import android.platform.test.util.HealthTestingUtils.waitForValueToSettle
26 import android.platform.uiautomator_helpers.WaitUtils.ensureThat
27 import android.util.Log
28 import androidx.test.InstrumentationRegistry
29 import androidx.test.uiautomator.UiDevice
30 import com.android.launcher3.tapl.LauncherInstrumentation
31 import java.time.Duration
32 import org.junit.runner.Description
33 
34 /** Locks the orientation in Landscape before starting the test, and goes back to natural after. */
35 internal class LandscapeOrientationRule : BaseOrientationRule(LANDSCAPE)
36 
37 /** Locks the orientation in Portrait before starting the test, and goes back to natural after. */
38 internal class PortraitOrientationRule : BaseOrientationRule(PORTRAIT)
39 
40 class NaturalOrientationRule : BaseOrientationRule(NATURAL)
41 
42 /**
43  * Possible device orientations.
44  *
45  * See [UiDevice.orientation] for their definitions.
46  */
47 enum class Orientation {
48     LANDSCAPE,
49     PORTRAIT,
50     NATURAL
51 }
52 
53 /** Returns whether the device is landscape or portrait , based on display dimensions. */
54 val UiDevice.orientation: Orientation
55     get() =
56         if (displayWidth > displayHeight) {
57             LANDSCAPE
58         } else {
59             PORTRAIT
60         }
61 
62 val UiDevice.naturalOrientation: Orientation
63     get() {
64         if (isNaturalOrientation) {
65             return stableOrientation
66         }
67         return when (stableOrientation) {
68             LANDSCAPE -> PORTRAIT
69             PORTRAIT -> LANDSCAPE
70             else -> throw RuntimeException("Unexpected orientation: $stableOrientation.")
71         }
72     }
73 
74 // This makes sure that the orientation stabilised before returning it.
75 private val UiDevice.stableOrientation: Orientation
76     get() =
77         waitForValueToSettle(
<lambda>null78             /* errorMessage= */ { "Device orientation didn't settle" },
<lambda>null79             /* supplier */ { orientation },
80             /* minimumSettleTime= */ 1_000,
81             /* timeoutMs= */ 5_000
82         )
83 
84 /** Uses launcher rect to decide which rotation to apply to match [expectedOrientation]. */
85 sealed class BaseOrientationRule constructor(private val expectedOrientation: Orientation) :
86     TestWatcher() {
87 
startingnull88     override fun starting(description: Description) {
89         setOrientationOverride(expectedOrientation)
90     }
91 
finishednull92     override fun finished(description: Description) {
93         clearOrientationOverride()
94     }
95 }
96 
97 /** Provides a way to set and clear a rotation override. */
98 object RotationUtils {
99     private val device: UiDevice =
100         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
101 
<lambda>null102     private val launcher: LauncherInstrumentation by lazy { LauncherInstrumentation() }
103 
104     /**
105      * Sets device orientation to [expectedOrientation], according to [Rect.orientation] definition.
106      *
107      * Important: call [clearOrientationOverride] after the end of the test. If a single orientation
108      * is needed for the entire test, please use the TestRule [OrientationRule] that automatically
109      * takes care of cleaning the override. Those should be called only when the test needs to
110      * change orientation in the middle.
111      */
setOrientationOverridenull112     fun setOrientationOverride(
113         orientation: Orientation,
114         timeoutDuration: Duration = Duration.ofSeconds(10)
115     ) {
116         val expectedOrientation =
117             if (orientation == NATURAL) device.naturalOrientation else orientation
118         setEnableLauncherRotation(true)
119         if (device.stableOrientation == expectedOrientation) {
120             return
121         }
122 
123         // Change orientation might be called soon before the device is unfolded, and lose its
124         // effect after unfold
125         ensureThat(
126             "orientation is $expectedOrientation",
127             timeout = timeoutDuration,
128             ignoreException = true
129         ) {
130             changeOrientation()
131             device.stableOrientation == expectedOrientation
132         }
133 
134         log("Rotation override set to ${expectedOrientation.name}")
135     }
136 
changeOrientationnull137     private fun changeOrientation() {
138         if (device.isNaturalOrientation) {
139             device.setOrientationLeft()
140         } else {
141             device.setOrientationNatural()
142         }
143     }
144 
145     /** returns stable orientation, doesn't necessarily mean orientation needs to happen */
waitForOrientationToSettlenull146     fun waitForOrientationToSettle(): Orientation {
147         return device.stableOrientation
148     }
149 
clearOrientationOverridenull150     fun clearOrientationOverride() {
151         device.setOrientationNatural()
152         setEnableLauncherRotation(false)
153         device.unfreezeRotation()
154         waitForOrientationToSettle()
155         log("Rotation override cleared.")
156     }
157 
setEnableLauncherRotationnull158     private fun setEnableLauncherRotation(enable: Boolean) {
159         // Launcher is only allowed to enable rotation when the device in test harness mode.
160         if (ActivityManager.isRunningInTestHarness()) {
161             launcher.setEnableRotation(enable)
162         }
163     }
164 
lognull165     private fun log(message: String) = Log.d("RotationUtils", message)
166 }
167