Remove SlidingPaneLayout FoldingFeatureObserver

With SlidingPaneLayout now in Kotlin, FoldingFeatureObserver is no
longer needed to bridge coroutine APIs. Delete both it and its tests.

Test: existing tests
Change-Id: I5b235f41180b6bfd61f245e3ea4ed3f95682be15
diff --git a/slidingpanelayout/slidingpanelayout/build.gradle b/slidingpanelayout/slidingpanelayout/build.gradle
index 8195fc7..20d8ceb 100644
--- a/slidingpanelayout/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/slidingpanelayout/build.gradle
@@ -10,7 +10,7 @@
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.core:core-ktx:1.1.0")
     api("androidx.customview:customview:1.1.0")
-    implementation("androidx.window:window:1.0.0")
+    implementation("androidx.window:window:1.2.0-alpha03")
     implementation("androidx.transition:transition:1.4.1")
 
     androidTestImplementation(libs.testExtJunit)
@@ -19,7 +19,7 @@
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.truth)
     androidTestImplementation(project(':internal-testutils-runtime'))
-    androidTestImplementation("androidx.window:window-testing:1.0.0")
+    androidTestImplementation("androidx.window:window-testing:1.2.0-alpha03")
 }
 
 androidx {
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/FoldingFeatureObserverTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/FoldingFeatureObserverTest.kt
deleted file mode 100644
index 7137970..0000000
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/FoldingFeatureObserverTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.slidingpanelayout.widget
-
-import androidx.slidingpanelayout.widget.helpers.TestActivity
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoTracker
-import androidx.window.layout.WindowLayoutInfo
-import androidx.window.testing.layout.FoldingFeature
-import androidx.window.testing.layout.WindowLayoutInfoPublisherRule
-import org.junit.Assert.assertEquals
-import org.junit.Rule
-import org.junit.Test
-
-class FoldingFeatureObserverTest {
-    @get:Rule
-    val windowInfoPublisherRule: WindowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()
-
-    @get:Rule
-    val activityScenarioRule: ActivityScenarioRule =
-        ActivityScenarioRule(TestActivity::class.java)
-
-    @Test
-    fun testNoValuesBeforeSubscribe() {
-        val listener = TestListener()
-        activityScenarioRule.scenario.onActivity { activity ->
-            val observer = FoldingFeatureObserver(
-                WindowInfoTracker.getOrCreate(activity),
-                Runnable::run
-            )
-            val expected = FoldingFeature(activity = activity)
-            val info = WindowLayoutInfo(listOf(expected))
-
-            observer.setOnFoldingFeatureChangeListener(listener)
-            windowInfoPublisherRule.overrideWindowLayoutInfo(info)
-
-            listener.assertCount(0)
-        }
-    }
-
-    @Test
-    fun testRelaysValuesFromWindowInfoRepo() {
-        val listener = TestListener()
-        activityScenarioRule.scenario.onActivity { activity ->
-            val observer = FoldingFeatureObserver(
-                WindowInfoTracker.getOrCreate(activity),
-                Runnable::run
-            )
-            val expected = FoldingFeature(activity = activity)
-            val info = WindowLayoutInfo(listOf(expected))
-
-            observer.setOnFoldingFeatureChangeListener(listener)
-            observer.registerLayoutStateChangeCallback(activity)
-            windowInfoPublisherRule.overrideWindowLayoutInfo(info)
-
-            listener.assertValue(expected)
-        }
-    }
-
-    @Test
-    fun testRelaysValuesNotRelayedAfterUnsubscribed() {
-        val listener = TestListener()
-        activityScenarioRule.scenario.onActivity { activity ->
-            val observer = FoldingFeatureObserver(
-                WindowInfoTracker.getOrCreate(activity),
-                Runnable::run
-            )
-            val expected = FoldingFeature(activity = activity)
-            val info = WindowLayoutInfo(listOf(expected))
-
-            observer.setOnFoldingFeatureChangeListener(listener)
-            observer.registerLayoutStateChangeCallback(activity)
-            observer.unregisterLayoutStateChangeCallback()
-            windowInfoPublisherRule.overrideWindowLayoutInfo(info)
-
-            listener.assertCount(0)
-        }
-    }
-
-    private class TestListener : FoldingFeatureObserver.OnFoldingFeatureChangeListener {
-        private val features = mutableListOf()
-
-        override fun onFoldingFeatureChange(foldingFeature: FoldingFeature) {
-            features.add(foldingFeature)
-        }
-
-        fun assertCount(count: Int) {
-            assertEquals(count, features.size)
-        }
-
-        fun assertValue(expected: FoldingFeature) {
-            assertCount(1)
-            assertEquals(expected, features.first())
-        }
-    }
-}
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/FoldingFeatureObserver.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/FoldingFeatureObserver.kt
deleted file mode 100644
index 61aaae7..0000000
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/FoldingFeatureObserver.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.slidingpanelayout.widget
-
-import android.app.Activity
-import androidx.window.layout.FoldingFeature
-import androidx.window.layout.WindowInfoTracker
-import androidx.window.layout.WindowLayoutInfo
-import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.launch
-
-/**
- * A device folding feature observer is used to notify listener when there is a folding feature
- * change.
- */
-internal class FoldingFeatureObserver(
-    private val windowInfoTracker: WindowInfoTracker,
-    private val executor: Executor
-) {
-    private var job: Job? = null
-    private var onFoldingFeatureChangeListener: OnFoldingFeatureChangeListener? = null
-
-    /**
-     * Interface definition for a callback to be invoked when there is a folding feature change
-     */
-    internal interface OnFoldingFeatureChangeListener {
-        /**
-         * Callback method to update window layout when there is a folding feature change
-         */
-        fun onFoldingFeatureChange(foldingFeature: FoldingFeature)
-    }
-
-    /**
-     * Register a listener that can be notified when there is a folding feature change.
-     *
-     * @param onFoldingFeatureChangeListener The listener to be added
-     */
-    fun setOnFoldingFeatureChangeListener(
-        onFoldingFeatureChangeListener: OnFoldingFeatureChangeListener
-    ) {
-        this.onFoldingFeatureChangeListener = onFoldingFeatureChangeListener
-    }
-
-    /**
-     * Registers a callback for layout changes of the window for the supplied [Activity].
-     * Must be called only after the it is attached to the window.
-     */
-    fun registerLayoutStateChangeCallback(activity: Activity) {
-        job?.cancel()
-        job = CoroutineScope(executor.asCoroutineDispatcher()).launch {
-            windowInfoTracker.windowLayoutInfo(activity)
-                .mapNotNull { info -> getFoldingFeature(info) }
-                .distinctUntilChanged()
-                .collect { nextFeature ->
-                    onFoldingFeatureChangeListener?.onFoldingFeatureChange(nextFeature)
-                }
-        }
-    }
-
-    /**
-     * Unregisters a callback for window layout changes of the [Activity] window.
-     */
-    fun unregisterLayoutStateChangeCallback() {
-        job?.cancel()
-    }
-
-    private fun getFoldingFeature(windowLayoutInfo: WindowLayoutInfo): FoldingFeature? {
-        return windowLayoutInfo.displayFeatures
-            .firstOrNull { feature -> feature is FoldingFeature } as? FoldingFeature
-    }
-}
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index 0b16ec5..1aae7ac 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -19,9 +19,7 @@
 
 import android.R
 import android.annotation.SuppressLint
-import android.app.Activity
 import android.content.Context
-import android.content.ContextWrapper
 import android.graphics.Canvas
 import android.graphics.PixelFormat
 import android.graphics.Rect
@@ -45,6 +43,7 @@
 import androidx.annotation.Px
 import androidx.core.content.ContextCompat
 import androidx.core.graphics.Insets
+import androidx.core.os.HandlerCompat
 import androidx.core.view.AccessibilityDelegateCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
@@ -62,6 +61,13 @@
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
 
 private const val TAG = "SlidingPaneLayout"
 
@@ -190,17 +196,6 @@
     }
 }
 
-private fun getActivityOrNull(context: Context): Activity? {
-    var iterator: Context? = context
-    while (iterator is ContextWrapper) {
-        if (iterator is Activity) {
-            return iterator
-        }
-        iterator = iterator.baseContext
-    }
-    return null
-}
-
 private class TouchBlocker(view: View) : FrameLayout(view.context) {
     init {
         addView(view)
@@ -364,25 +359,12 @@
 
     private var foldingFeature: FoldingFeature? = null
 
-    private val mOnFoldingFeatureChangeListener =
-        object : FoldingFeatureObserver.OnFoldingFeatureChangeListener {
-            override fun onFoldingFeatureChange(foldingFeature: FoldingFeature) {
-                [email protected] = foldingFeature
-                // Start transition animation when folding feature changed
-                val changeBounds: Transition = ChangeBounds()
-                changeBounds.duration = 300L
-                changeBounds.interpolator = PathInterpolatorCompat.create(0.2f, 0f, 0f, 1f)
-                TransitionManager.beginDelayedTransition(this@SlidingPaneLayout, changeBounds)
-                requestLayout()
-            }
-        }
-
-    private var foldingFeatureObserver: FoldingFeatureObserver? = null
-
-    private fun setFoldingFeatureObserver(foldingFeatureObserver: FoldingFeatureObserver) {
-        this.foldingFeatureObserver = foldingFeatureObserver
-        foldingFeatureObserver.setOnFoldingFeatureChangeListener(mOnFoldingFeatureChangeListener)
-    }
+    /**
+     * [Job] that tracks the last launched coroutine running [whileAttachedToVisibleWindow].
+     * This is never set to `null`; the last job is always [joined][Job.join] prior to invoking
+     * [whileAttachedToVisibleWindow].
+     */
+    private var whileAttachedToVisibleWindowJob: Job? = null
 
     /**
      * Distance to parallax the lower pane by when the upper pane is in its
@@ -415,18 +397,16 @@
 
     private val isLayoutRtlSupport: Boolean
         get() = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
+
+    private val windowInfoTracker = WindowInfoTracker.getOrCreate(context)
+
     init {
         val density = context.resources.displayMetrics.density
         setWillNotDraw(false)
         ViewCompat.setAccessibilityDelegate(this, AccessibilityDelegate())
         ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES)
         dragHelper = ViewDragHelper.create(this, 0.5f, DragHelperCallback())
-        dragHelper.minVelocity =
-            MIN_FLING_VELOCITY * density
-        val repo: WindowInfoTracker = WindowInfoTracker.getOrCreate(context)
-        val mainExecutor = ContextCompat.getMainExecutor(context)
-        val foldingFeatureObserver = FoldingFeatureObserver(repo, mainExecutor)
-        setFoldingFeatureObserver(foldingFeatureObserver)
+        dragHelper.minVelocity = MIN_FLING_VELOCITY * density
     }
 
     /**
@@ -583,20 +563,44 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
         awaitingFirstLayout = true
-        if (foldingFeatureObserver != null) {
-            val activity = getActivityOrNull(context)
-            if (activity != null) {
-                foldingFeatureObserver!!.registerLayoutStateChangeCallback(activity)
+        whileAttachedToVisibleWindowJob?.cancel()
+    }
+
+    override fun onWindowVisibilityChanged(visibility: Int) {
+        super.onWindowVisibilityChanged(visibility)
+        val toJoin = whileAttachedToVisibleWindowJob?.apply { cancel() }
+        whileAttachedToVisibleWindowJob = if (visibility != VISIBLE) null else {
+            CoroutineScope(
+                HandlerCompat.createAsync(handler.looper).asCoroutineDispatcher()
+            ).launch(start = CoroutineStart.UNDISPATCHED) {
+                // Don't let two copies of this run concurrently
+                toJoin?.join()
+                whileAttachedToVisibleWindow()
             }
         }
     }
 
+    private suspend fun whileAttachedToVisibleWindow() {
+        windowInfoTracker.windowLayoutInfo(context)
+            .mapNotNull { info ->
+                info.displayFeatures.firstOrNull { it is FoldingFeature } as? FoldingFeature
+            }
+            .distinctUntilChanged()
+            .collect { nextFeature ->
+                foldingFeature = nextFeature
+                // Start transition animation when folding feature changed
+                val changeBounds: Transition = ChangeBounds()
+                changeBounds.duration = 300L
+                changeBounds.interpolator = PathInterpolatorCompat.create(0.2f, 0f, 0f, 1f)
+                TransitionManager.beginDelayedTransition(this@SlidingPaneLayout, changeBounds)
+                requestLayout()
+            }
+    }
+
     override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
+        whileAttachedToVisibleWindowJob?.cancel()
         awaitingFirstLayout = true
-        if (foldingFeatureObserver != null) {
-            foldingFeatureObserver!!.unregisterLayoutStateChangeCallback()
-        }
+        super.onDetachedFromWindow()
     }
 
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {