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) {