@@ -18,27 +18,29 @@ package androidx.test.espresso.device.action
1818
1919import android.app.Activity
2020import android.content.ComponentCallbacks
21- import android.content.ContentResolver
2221import android.content.Context
2322import android.content.pm.ActivityInfo.CONFIG_ORIENTATION
2423import android.content.res.Configuration
25- import android.database.ContentObserver
2624import android.os.Handler
2725import android.os.HandlerThread
2826import android.provider.Settings.System
2927import android.util.Log
3028import androidx.test.espresso.device.context.ActionContext
29+ import androidx.test.espresso.device.controller.DeviceControllerOperationException
30+ import androidx.test.espresso.device.util.SettingsObserver
3131import androidx.test.espresso.device.util.executeShellCommand
3232import androidx.test.espresso.device.util.getDeviceApiLevel
3333import androidx.test.espresso.device.util.getResumedActivityOrNull
3434import androidx.test.espresso.device.util.isConfigurationChangeHandled
3535import androidx.test.espresso.device.util.isRobolectricTest
36+ import androidx.test.espresso.device.util.isTestDeviceAnEmulator
3637import androidx.test.platform.device.DeviceController
3738import androidx.test.platform.device.UnsupportedDeviceOperationException
3839import androidx.test.runner.lifecycle.ActivityLifecycleCallback
3940import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
4041import androidx.test.runner.lifecycle.Stage
4142import java.util.concurrent.CountDownLatch
43+ import java.util.concurrent.TimeUnit
4244
4345/* * Action to set the test device to the provided screen orientation. */
4446internal class ScreenOrientationAction (val screenOrientation : ScreenOrientation ) : DeviceAction {
@@ -66,12 +68,7 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
6668 * @param deviceController the controller to use to interact with the device.
6769 */
6870 override fun perform (context : ActionContext , deviceController : DeviceController ) {
69- var currentOrientation =
70- context.applicationContext.getResources().getConfiguration().orientation
71- val requestedOrientation =
72- if (screenOrientation == ScreenOrientation .LANDSCAPE ) Configuration .ORIENTATION_LANDSCAPE
73- else Configuration .ORIENTATION_PORTRAIT
74- if (currentOrientation == requestedOrientation) {
71+ if (screenOrientation == getCurrentScreenOrientation(context)) {
7572 if (Log .isLoggable(TAG , Log .DEBUG )) {
7673 Log .d(TAG , " Device screen is already in the requested orientation, no need to rotate." )
7774 }
@@ -84,7 +81,8 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
8481 }
8582
8683 var oldAccelRotationSetting = getAccelerometerRotationSetting(context.applicationContext)
87- if (oldAccelRotationSetting != AccelerometerRotation .ENABLED ) {
84+ // For emulators, auto-rotate must be enabled. For physical devices, it must be disabled.
85+ if (isTestDeviceAnEmulator() && oldAccelRotationSetting != AccelerometerRotation .ENABLED ) {
8886 // Executing shell commands requires API 21+.
8987 if (getDeviceApiLevel() >= 21 ) {
9088 Log .d(TAG , " Enabling auto-rotate." )
@@ -94,6 +92,17 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
9492 " Screen orientation cannot be set on this device because auto-rotate is disabled. Please manually enable auto-rotate and try again."
9593 )
9694 }
95+ } else if (
96+ ! isTestDeviceAnEmulator() && oldAccelRotationSetting != AccelerometerRotation .DISABLED
97+ ) {
98+ if (getDeviceApiLevel() >= 21 ) {
99+ Log .d(TAG , " Disabling auto-rotate." )
100+ setAccelerometerRotation(AccelerometerRotation .DISABLED , context.applicationContext)
101+ } else {
102+ throw UnsupportedDeviceOperationException (
103+ " Screen orientation cannot be set on this device because auto-rotate is enabled. Please manually disable auto-rotate and try again."
104+ )
105+ }
97106 }
98107
99108 val currentActivity = getResumedActivityOrNull()
@@ -106,6 +115,9 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
106115 }
107116
108117 val latch: CountDownLatch = CountDownLatch (1 )
118+ val requestedOrientation =
119+ if (screenOrientation == ScreenOrientation .LANDSCAPE ) Configuration .ORIENTATION_LANDSCAPE
120+ else Configuration .ORIENTATION_PORTRAIT
109121
110122 if (currentActivity == null || configChangesHandled) {
111123 if (currentActivity == null ) {
@@ -124,6 +136,7 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
124136 latch.countDown()
125137 }
126138 }
139+
127140 override fun onLowMemory () {}
128141 }
129142 )
@@ -153,13 +166,18 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
153166 )
154167 }
155168 deviceController.setScreenOrientation(screenOrientation.orientation)
156- latch.await()
169+ latch.await(5 , TimeUnit . SECONDS )
157170 if (
158171 getDeviceApiLevel() >= 21 &&
159172 oldAccelRotationSetting != getAccelerometerRotationSetting(context.applicationContext)
160173 ) {
161174 setAccelerometerRotation(oldAccelRotationSetting, context.applicationContext)
162175 }
176+ if (getCurrentScreenOrientation(context) != screenOrientation) {
177+ throw DeviceControllerOperationException (
178+ " Device could not be set to the requested screen orientation."
179+ )
180+ }
163181 }
164182
165183 private fun getAccelerometerRotationSetting (context : Context ): AccelerometerRotation =
@@ -181,30 +199,26 @@ internal class ScreenOrientationAction(val screenOrientation: ScreenOrientation)
181199 SettingsObserver (runnableHandler, context, settingsLatch, System .ACCELEROMETER_ROTATION )
182200 settingsObserver.observe()
183201 executeShellCommand(" settings put system accelerometer_rotation ${accelerometerRotation.value} " )
184- settingsLatch.await()
202+ settingsLatch.await(5 , TimeUnit . SECONDS )
185203 settingsObserver.stopObserver()
186204 thread.quitSafely()
187- }
188205
189- private class SettingsObserver (
190- handler : Handler ,
191- val context : Context ,
192- val latch : CountDownLatch ,
193- val settingToObserve : String
194- ) : ContentObserver(handler) {
195- fun observe () {
196- val resolver: ContentResolver = context.getContentResolver()
197- resolver.registerContentObserver(System .getUriFor(settingToObserve), false , this )
198- }
199-
200- fun stopObserver () {
201- val resolver: ContentResolver = context.getContentResolver()
202- resolver.unregisterContentObserver(this )
206+ if (
207+ executeShellCommand(" settings get system accelerometer_rotation" ).trim().toInt() !=
208+ accelerometerRotation.value
209+ ) {
210+ throw DeviceControllerOperationException (
211+ " Device could not be set to the requested screen orientation."
212+ )
203213 }
214+ }
204215
205- override fun onChange (selfChange : Boolean ) {
206- latch.countDown()
207- }
216+ private fun getCurrentScreenOrientation (context : ActionContext ): ScreenOrientation {
217+ var currentOrientation =
218+ context.applicationContext.getResources().getConfiguration().orientation
219+ return if (currentOrientation == Configuration .ORIENTATION_LANDSCAPE )
220+ ScreenOrientation .LANDSCAPE
221+ else ScreenOrientation .PORTRAIT
208222 }
209223
210224 companion object {
0 commit comments