Merge "Correct handling of orientation." into androidx-main
diff --git a/glance/glance-appwidget/build.gradle b/glance/glance-appwidget/build.gradle
index 189616b..80123313 100644
--- a/glance/glance-appwidget/build.gradle
+++ b/glance/glance-appwidget/build.gradle
@@ -55,6 +55,7 @@
     testImplementation(libs.truth)
     testImplementation(libs.kotlinReflect)
 
+    androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
     androidTestImplementation(project(":test:screenshot:screenshot"))
     androidTestImplementation(project(":core:core-ktx"))
     androidTestImplementation(libs.espressoCore)
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
index a7c8848..bdcb084 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostRule.kt
@@ -17,20 +17,29 @@
 package androidx.glance.appwidget
 
 import android.Manifest
+import android.app.Activity
 import android.appwidget.AppWidgetHostView
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.pm.ActivityInfo
-import android.content.res.Configuration
+import android.os.Build
+import android.view.View
 import android.view.ViewTreeObserver
 import androidx.glance.unit.DpSize
 import androidx.glance.unit.dp
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
-import androidx.test.espresso.Espresso.onIdle
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.UiController
+import androidx.test.espresso.ViewAction
+import androidx.test.espresso.matcher.ViewMatchers.isRoot
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import androidx.test.uiautomator.UiDevice
+import org.hamcrest.Matcher
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
 import org.junit.runner.Description
@@ -50,24 +59,22 @@
     val landscapeSize: DpSize
         get() = mLandscapeSize
 
-    private val mUiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
+    private val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+    private val mUiAutomation = mInstrumentation.uiAutomation
 
     private val mActivityRule: ActivityScenarioRule<AppWidgetHostTestActivity> =
         ActivityScenarioRule(AppWidgetHostTestActivity::class.java)
 
+    private val mUiDevice = UiDevice.getInstance(mInstrumentation)
+
     // Ensure the screen starts in portrait and restore the orientation on leaving
     private val mOrientationRule = TestRule { base, _ ->
         object : Statement() {
             override fun evaluate() {
-                var orientation: Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-                mScenario.onActivity {
-                    orientation = it.resources.configuration.orientation.toActivityInfoOrientation()
-                    it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-                }
+                mUiDevice.freezeRotation()
+                mUiDevice.setOrientationNatural()
                 base.evaluate()
-                mScenario.onActivity {
-                    it.requestedOrientation = orientation
-                }
+                mUiDevice.unfreezeRotation()
             }
         }
     }
@@ -118,27 +125,14 @@
         onHostActivity { block(mHostView) }
     }
 
-    /** Change the orientation to landscape using [setOrientation] .*/
+    /** Change the orientation to landscape.*/
     fun setLandscapeOrientation() {
-        setOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+        onView(isRoot()).perform(orientationLandscape())
     }
 
-    /** Change the orientation to portrait using [setOrientation] .*/
+    /** Change the orientation to portrait.*/
     fun setPortraitOrientation() {
-        setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
-    }
-
-    /**
-     * Change the orientation of the screen, then update the view sizes and reapply the RemoteViews.
-     */
-    private fun setOrientation(orientation: Int) {
-        mScenario.onActivity { it.requestedOrientation = orientation }
-        onIdle()
-        mScenario.onActivity {
-            it.updateAllSizes()
-            it.reapplyRemoteViews()
-        }
-        onIdle()
+        onView(isRoot()).perform(orientationPortrait())
     }
 
     /**
@@ -202,11 +196,46 @@
             mHostView.childCount > 0
         }
     }
-}
 
-private fun Int.toActivityInfoOrientation(): Int =
-    if (this == Configuration.ORIENTATION_PORTRAIT) {
-        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-    } else {
-        ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-    }
\ No newline at end of file
+    private inner class OrientationChangeAction constructor(private val orientation: Int) :
+        ViewAction {
+        override fun getConstraints(): Matcher<View> = isRoot()
+
+        override fun getDescription() = "change orientation to $orientationName"
+
+        private val orientationName: String
+            get() =
+                if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+                    "landscape"
+                } else {
+                    "portrait"
+                }
+
+        override fun perform(uiController: UiController, view: View) {
+            uiController.loopMainThreadUntilIdle()
+            mActivityRule.scenario.onActivity { it.requestedOrientation = orientation }
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+                // Somehow, before Android S, changing the orientation doesn't trigger the
+                // onConfigurationChange
+                uiController.loopMainThreadUntilIdle()
+                mScenario.onActivity {
+                    it.updateAllSizes(it.resources.configuration.orientation)
+                    it.reapplyRemoteViews()
+                }
+            }
+            val resumedActivities: Collection<Activity> =
+                ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
+            if (resumedActivities.isEmpty()) {
+                throw RuntimeException("Could not change orientation")
+            }
+        }
+    }
+
+    private fun orientationLandscape(): ViewAction {
+        return OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+    }
+
+    private fun orientationPortrait(): ViewAction {
+        return OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+    }
+}
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
index 76e9f04..90e8cb8 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/AppWidgetHostTestActivity.kt
@@ -34,13 +34,12 @@
 import android.widget.FrameLayout
 import android.widget.RemoteViews
 import androidx.annotation.RequiresApi
-import androidx.core.text.layoutDirection
-import org.junit.Assert.fail
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import androidx.glance.appwidget.test.R
 import androidx.glance.unit.DpSize
+import org.junit.Assert.fail
 import java.util.Locale
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 @RequiresApi(26)
 class AppWidgetHostTestActivity : Activity() {
@@ -102,13 +101,14 @@
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
+        Log.i("YOLO", "Changing configuration, orientation = ${newConfig.orientation}")
         super.onConfigurationChanged(newConfig)
-        updateAllSizes()
+        updateAllSizes(newConfig.orientation)
         reapplyRemoteViews()
     }
 
-    fun updateAllSizes() {
-        mHostViews.forEach { it.updateSize() }
+    fun updateAllSizes(orientation: Int) {
+        mHostViews.forEach { it.updateSize(orientation) }
     }
 
     fun reapplyRemoteViews() {
@@ -173,11 +173,11 @@
     fun setSizes(portraitSize: DpSize, landscapeSize: DpSize) {
         mPortraitSize = portraitSize
         mLandscapeSize = landscapeSize
-        updateSize()
+        updateSize(resources.configuration.orientation)
     }
 
-    fun updateSize() {
-        val size = when (context.resources.configuration.orientation) {
+    fun updateSize(orientation: Int) {
+        val size = when (orientation) {
             Configuration.ORIENTATION_LANDSCAPE -> mLandscapeSize
             Configuration.ORIENTATION_PORTRAIT -> mPortraitSize
             else -> error("Unknown orientation ${context.resources.configuration.orientation}")