Merge "Fix: SlidingPaneLayout crash on certain devices" into androidx-main
diff --git a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt
index 6093130..0d192d9 100644
--- a/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt
+++ b/slidingpanelayout/slidingpanelayout/src/androidTest/java/androidx/slidingpanelayout/widget/SlidingPaneLayoutTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.slidingpanelayout.widget
 
+import android.content.Context
 import android.graphics.Canvas
 import android.graphics.ColorFilter
 import android.graphics.PixelFormat
@@ -292,6 +293,45 @@
         val view = inflater.inflate(R.layout.pane_spacing, null) as SlidingPaneLayout
         assertWithMessage("paneSpacing is inflated").that(view.paneSpacing).isEqualTo(24)
     }
+
+    @Test
+    fun testGenerateLayoutParams_fromViewGroupLayoutParams() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val spl = TestSlidingPaneLayout(context)
+        val layoutParams = spl.getGeneratedLayoutParams(ViewGroup.LayoutParams(10, 20))
+        assertThat(layoutParams).isInstanceOf(SlidingPaneLayout.LayoutParams::class.java)
+        assertThat(layoutParams.width).isEqualTo(10)
+        assertThat(layoutParams.height).isEqualTo(20)
+    }
+
+    @Test
+    fun testGenerateLayoutParams_fromMarginLayoutParams() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val spl = TestSlidingPaneLayout(context)
+        val layoutParams = spl.getGeneratedLayoutParams(ViewGroup.MarginLayoutParams(10, 20))
+        assertThat(layoutParams).isInstanceOf(SlidingPaneLayout.LayoutParams::class.java)
+        assertThat(layoutParams.width).isEqualTo(10)
+        assertThat(layoutParams.height).isEqualTo(20)
+    }
+
+    @Test
+    fun testGenerateLayoutParams_fromSlidingPaneLayoutLayoutParams() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val spl = TestSlidingPaneLayout(context)
+        val layoutParams = spl.getGeneratedLayoutParams(SlidingPaneLayout.LayoutParams(10, 20))
+        assertThat(layoutParams).isInstanceOf(SlidingPaneLayout.LayoutParams::class.java)
+        assertThat(layoutParams.width).isEqualTo(10)
+        assertThat(layoutParams.height).isEqualTo(20)
+    }
+
+    @Test
+    fun testGenerateLayoutParams_fromNull() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        val spl = TestSlidingPaneLayout(context)
+        // Do not crash when input is null
+        val layoutParams = spl.getGeneratedLayoutParams(null)
+        assertThat(layoutParams).isInstanceOf(SlidingPaneLayout.LayoutParams::class.java)
+    }
 }
 
 private fun View.measureAndLayout(width: Int, height: Int) {
@@ -326,3 +366,10 @@
         assertWithMessage("second child measure count").that(measureCount).isEqualTo(1)
     }
 }
+
+private class TestSlidingPaneLayout(context: Context) : SlidingPaneLayout(context) {
+    // ViewGroup.generateLayoutParams is protected.
+    fun getGeneratedLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
+        return generateLayoutParams(p)
+    }
+}
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 9440cf4..2046ea5 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -27,6 +27,7 @@
 import android.os.Parcelable
 import android.os.Parcelable.ClassLoaderCreator
 import android.util.AttributeSet
+import android.util.Log
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.MeasureSpec
@@ -202,17 +203,6 @@
 }
 
 /**
- * Pulls the string interpolation and exception throwing bytecode out of the inlined
- * [spLayoutParams] property at each call site
- */
-private fun layoutParamsError(childView: View, layoutParams: LayoutParams?): Nothing {
-    error("SlidingPaneLayout child $childView had unexpected LayoutParams $layoutParams")
-}
-
-private inline val View.spLayoutParams: SlidingPaneLayout.LayoutParams
-    get() = layoutParams as SlidingPaneLayout.LayoutParams
-
-/**
  * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level of a UI. A
  * left (or start) pane is treated as a content list or browser, subordinate to a primary detail
  * view for displaying content.
@@ -1853,7 +1843,13 @@
     }
 
     override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
-        return if (p is MarginLayoutParams) LayoutParams(p) else LayoutParams(p)
+        return if (p is MarginLayoutParams) {
+            SlidingPaneLayout.LayoutParams(p)
+        } else if (p == null) {
+            generateDefaultLayoutParams()
+        } else {
+            SlidingPaneLayout.LayoutParams(p)
+        }
     }
 
     override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
@@ -3081,4 +3077,16 @@
                 }
             }
     }
+
+    private inline val View.spLayoutParams: SlidingPaneLayout.LayoutParams
+        get() {
+            val layoutParams = this.layoutParams
+            return if (!checkLayoutParams(layoutParams)) {
+                Log.w(TAG, "Unexpected child: $this had unexpected LayoutParams: $layoutParams ")
+                generateLayoutParams(layoutParams)
+            } else {
+                layoutParams
+            }
+                as SlidingPaneLayout.LayoutParams
+        }
 }