[go: nahoru, domu]

Implement setSystemBarsBehavior for pre 30 SDK

Test: WindowInsetsControllerCompatActivityTest#systemBarsBehavior_swipe
Test:
WindowInsetsControllerCompatActivityTest#systemBarsBehavior_transient
Test: WindowInsetsControllerCompatActivityTest#systemBarsBehavior_touch
Bug: 173203649

Change-Id: I062c841f0e4201fddfcf1489dbfaff605bebbdb6
(cherry picked from commit 480d10395d28bf22e9f4539f4b992a344628779b)
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
index cc0fb65..ed40508 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsAnimationCompatActivityTest.kt
@@ -24,6 +24,7 @@
 import androidx.core.test.R
 import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE
 import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP
+import androidx.core.view.WindowInsetsCompat.Type.statusBars
 import androidx.core.view.WindowInsetsCompat.Type.systemBars
 import androidx.test.core.app.ActivityScenario
 import androidx.test.espresso.Espresso.onIdle
@@ -50,6 +51,8 @@
 
     private lateinit var scenario: ActivityScenario<WindowInsetsCompatActivity>
 
+    private var barsShown = true
+
     @Before
     public fun setup() {
         scenario = ActivityScenario.launch(WindowInsetsCompatActivity::class.java)
@@ -59,28 +62,30 @@
         scenario.onActivity {
             WindowCompat.setDecorFitsSystemWindows(it.window, false)
             WindowCompat.getInsetsController(it.window, it.window.decorView)!!.show(systemBars())
+            barsShown = true
         }
         onIdle()
     }
 
     @Test
-    public fun add_remove_listener() {
+    public fun add_both_listener() {
         assumeNotCuttlefish()
 
         val container = scenario.withActivity { findViewById(R.id.container) }
         var applyInsetsCalled = false
         var insetsAnimationCallbackCalled = false
-        var latch = CountDownLatch(2)
+        val insetsLatch = CountDownLatch(1)
+        val animationLatch = CountDownLatch(1)
         val animationCallback = createCallback(
             >
                 insetsAnimationCallbackCalled = true
-                latch.countDown()
+                animationLatch.countDown()
             }
         )
         val insetListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
             { _, insetsCompat ->
                 applyInsetsCalled = true
-                latch.countDown()
+                insetsLatch.countDown()
                 insetsCompat
             }
 
@@ -88,7 +93,8 @@
         ViewCompat.setOnApplyWindowInsetsListener(container, insetListener)
         ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback)
         triggerInsetAnimation(container)
-        latch.await(4, TimeUnit.SECONDS)
+        animationLatch.await(4, TimeUnit.SECONDS)
+        insetsLatch.await(4, TimeUnit.SECONDS)
         assertTrue(
             "The WindowInsetsAnimationCallback has not been called",
             insetsAnimationCallbackCalled
@@ -97,16 +103,36 @@
             "onApplyWindowInsetsListener has not been called",
             applyInsetsCalled
         )
-        resetBars(container)
+    }
 
-        // Remove the applyWindowInsets listener and check that the animation callback is still
-        // called
-        applyInsetsCalled = false
-        insetsAnimationCallbackCalled = false
-        latch = CountDownLatch(1)
+    @Test
+    public fun remove_insets_listener() {
+        assumeNotCuttlefish()
+
+        val container = scenario.withActivity { findViewById(R.id.container) }
+        var applyInsetsCalled = false
+        var insetsAnimationCallbackCalled = false
+        val insetsLatch = CountDownLatch(1)
+        val animationLatch = CountDownLatch(1)
+        val animationCallback = createCallback(
+            >
+                insetsAnimationCallbackCalled = true
+                animationLatch.countDown()
+            }
+        )
+        val insetListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
+            { _, insetsCompat ->
+                applyInsetsCalled = true
+                insetsLatch.countDown()
+                insetsCompat
+            }
+
+        // Check that both ApplyWindowInsets and the Animation Callback are called
+        ViewCompat.setOnApplyWindowInsetsListener(container, insetListener)
+        ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback)
         ViewCompat.setOnApplyWindowInsetsListener(container, null)
         triggerInsetAnimation(container)
-        latch.await(4, TimeUnit.SECONDS)
+        animationLatch.await(4, TimeUnit.SECONDS)
         assertFalse(
             "onApplyWindowInsetsListener should NOT have been called",
             applyInsetsCalled
@@ -115,18 +141,35 @@
             "The WindowInsetsAnimationCallback has not been called",
             insetsAnimationCallbackCalled
         )
+    }
 
-        resetBars(container)
+    @Test
+    public fun remove_animation_listener() {
+        assumeNotCuttlefish()
 
-        // Add an applyWindowInsets listener and remove the animation callback and check if the
-        // listener is called
-        applyInsetsCalled = false
-        insetsAnimationCallbackCalled = false
-        latch = CountDownLatch(1)
+        val container = scenario.withActivity { findViewById(R.id.container) }
+        var applyInsetsCalled = false
+        var insetsAnimationCallbackCalled = false
+        val insetsLatch = CountDownLatch(1)
+        val animationLatch = CountDownLatch(1)
+        val animationCallback = createCallback(
+            >
+                insetsAnimationCallbackCalled = true
+                animationLatch.countDown()
+            }
+        )
+        val insetListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
+            { _, insetsCompat ->
+                applyInsetsCalled = true
+                insetsLatch.countDown()
+                insetsCompat
+            }
+
         ViewCompat.setOnApplyWindowInsetsListener(container, insetListener)
+        ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback)
         ViewCompat.setWindowInsetsAnimationCallback(container, null)
         triggerInsetAnimation(container)
-        latch.await(4, TimeUnit.SECONDS)
+        insetsLatch.await(4, TimeUnit.SECONDS)
         assertTrue("onApplyWindowInsetsListener has not been called", applyInsetsCalled)
         assertFalse(
             "The WindowInsetsAnimationCallback should NOT have been called",
@@ -182,13 +225,13 @@
 
     private fun triggerInsetAnimation(container: View) {
         scenario.onActivity {
-            ViewCompat.getWindowInsetsController(container)!!.hide(systemBars())
-        }
-    }
-
-    private fun resetBars(container: View) {
-        scenario.onActivity {
-            ViewCompat.getWindowInsetsController(container)!!.show(systemBars())
+            if (barsShown) {
+                ViewCompat.getWindowInsetsController(container)!!.hide(statusBars())
+                barsShown = false
+            } else {
+                ViewCompat.getWindowInsetsController(container)!!.show(statusBars())
+                barsShown = true
+            }
         }
     }
 
@@ -200,7 +243,8 @@
         var applyInsetsCalled3 = false
         var insetsAnimationCallbackCalled1 = false
         var insetsAnimationCallbackCalled2 = false
-        val latch = CountDownLatch(2)
+        val insetsLatch = CountDownLatch(1)
+        val animationLatch = CountDownLatch(1)
         val animationCallback1 = createCallback(
             >
                 insetsAnimationCallbackCalled1 = true
@@ -209,7 +253,7 @@
         val animationCallback2 = createCallback(
             >
                 insetsAnimationCallbackCalled2 = true
-                latch.countDown()
+                animationLatch.countDown()
             }
         )
         val insetListener1: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
@@ -227,7 +271,7 @@
         val insetListener3: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
             { _, insetsCompat ->
                 applyInsetsCalled3 = true
-                latch.countDown()
+                insetsLatch.countDown()
                 insetsCompat
             }
 
@@ -238,7 +282,8 @@
         ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback2)
         ViewCompat.setOnApplyWindowInsetsListener(container, insetListener3)
         triggerInsetAnimation(container)
-        latch.await(5, TimeUnit.SECONDS)
+        animationLatch.await(5, TimeUnit.SECONDS)
+        insetsLatch.await(5, TimeUnit.SECONDS)
         assertFalse(
             "The WindowInsetsAnimationCallback #1 should have not been called",
             insetsAnimationCallbackCalled1
@@ -268,24 +313,26 @@
         val container = scenario.withActivity { findViewById(R.id.container) }
         var applyInsetsCalled = false
         var insetsAnimationCallbackCalled = false
-        var latch = CountDownLatch(2)
+        val insetsLatch = CountDownLatch(1)
+        val animationLatch = CountDownLatch(1)
         val animationCallback = createCallback(
             >
                 insetsAnimationCallbackCalled = true
-                latch.countDown()
+                animationLatch.countDown()
             }
         )
         val insetListener: (v: View, insets: WindowInsetsCompat) -> WindowInsetsCompat =
             { _, insetsCompat ->
                 applyInsetsCalled = true
-                latch.countDown()
+                insetsLatch.countDown()
                 insetsCompat
             }
 
         ViewCompat.setWindowInsetsAnimationCallback(container, animationCallback)
         ViewCompat.setOnApplyWindowInsetsListener(container, insetListener)
         triggerInsetAnimation(container)
-        latch.await(4, TimeUnit.SECONDS)
+        animationLatch.await(4, TimeUnit.SECONDS)
+        insetsLatch.await(4, TimeUnit.SECONDS)
         assertTrue(
             "The WindowInsetsAnimationCallback has not been called",
             insetsAnimationCallbackCalled
@@ -395,10 +442,6 @@
             childListenerCalledCount
         )
 
-        // Then we do the same but without consuming the insets in the parent, so the child
-        // listener should be called.
-        resetBars(container)
-
         >
         val dispatchCallback = createCallback(
             >
diff --git a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
index 667fc08..ca37b0c 100644
--- a/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
+++ b/core/core/src/androidTest/java/androidx/core/view/WindowInsetsControllerCompatActivityTest.kt
@@ -71,11 +71,13 @@
         }
         // Close the IME if it's open, so we start from a known scenario
         onView(withId(R.id.edittext)).perform(closeSoftKeyboard())
-        ViewCompat.getWindowInsetsController(container)!!.systemBarsBehavior =
-            WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
 
-        // Needed on API 23 to report the nav bar insets
-        scenario.withActivity { this.window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) }
+        scenario.withActivity {
+            ViewCompat.getWindowInsetsController(container)!!.systemBarsBehavior =
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+            // Needed on API 23 to report the nav bar insets
+            this.window.addFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+        }
     }
 
     /**
@@ -260,6 +262,55 @@
         }
     }
 
+    @Test
+    @SdkSuppress(maxSdkVersion = 29) // Flag deprecated in 30+
+    public fun systemBarsBehavior_swipe() {
+        scenario.onActivity {
+            windowInsetsController.systemBarsBehavior =
+                WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE
+        }
+        val decorView = scenario.withActivity { window.decorView }
+        val sysUiVis = decorView.systemUiVisibility
+        assertEquals(
+            View.SYSTEM_UI_FLAG_IMMERSIVE,
+            sysUiVis and View.SYSTEM_UI_FLAG_IMMERSIVE
+        )
+        assertEquals(0, sysUiVis and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = 29) // Flag deprecated in 30+
+    public fun systemBarsBehavior_transient() {
+        scenario.onActivity {
+            windowInsetsController.systemBarsBehavior =
+                WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+        }
+        val decorView = scenario.withActivity { window.decorView }
+        val sysUiVis = decorView.systemUiVisibility
+        assertEquals(
+            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+            sysUiVis and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+        )
+        assertEquals(0, sysUiVis and View.SYSTEM_UI_FLAG_IMMERSIVE)
+    }
+
+    @Test
+    public fun systemBarsBehavior_touch() {
+        scenario.onActivity {
+            windowInsetsController.systemBarsBehavior =
+                WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH
+        }
+        val decorView = scenario.withActivity { window.decorView }
+        val sysUiVis = decorView.systemUiVisibility
+        assertEquals(
+            0,
+            sysUiVis and (
+                View.SYSTEM_UI_FLAG_IMMERSIVE
+                    or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                )
+        )
+    }
+
     private fun assumeNotCuttlefish() {
         // TODO: remove this if b/159103848 is resolved
         assumeFalse(
diff --git a/core/core/src/main/java/androidx/core/view/ViewCompat.java b/core/core/src/main/java/androidx/core/view/ViewCompat.java
index 5e30e5f..e63c616 100644
--- a/core/core/src/main/java/androidx/core/view/ViewCompat.java
+++ b/core/core/src/main/java/androidx/core/view/ViewCompat.java
@@ -2606,7 +2606,7 @@
      * Provide original {@link WindowInsetsCompat} that are dispatched to the view hierarchy.
      * The insets are only available if the view is attached.
      * <p>
-     * On devices running API 22 and below, this method always returns null.
+     * On devices running API 20 and below, this method always returns null.
      *
      * @return WindowInsetsCompat from the top of the view hierarchy or null if View is detached
      */
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsAnimationCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsAnimationCompat.java
index 80268b9..c2c1409 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsAnimationCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsAnimationCompat.java
@@ -730,7 +730,7 @@
 
                 if (DEBUG) {
                     int allTypes = WindowInsetsCompat.Type.all();
-                    Log.d(TAG, String.format("lastInsets: %s\ntargetInsets: %s",
+                    Log.d(TAG, String.format("lastInsets:   %s\ntargetInsets: %s",
                             mLastInsets.getInsets(allTypes),
                             targetInsets.getInsets(allTypes)));
                 }
@@ -746,6 +746,9 @@
                 // We only run the animation when the some insets are animating
                 final int animationMask = buildAnimationMask(targetInsets, mLastInsets);
                 if (animationMask == 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Insets applied bug no window animation to run");
+                    }
                     return forwardToViewIfNeeded(v, insets);
                 }
 
diff --git a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
index d904b05..dd5bcd3 100644
--- a/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
+++ b/core/core/src/main/java/androidx/core/view/WindowInsetsControllerCompat.java
@@ -468,7 +468,6 @@
             switch (type) {
                 case WindowInsetsCompat.Type.STATUS_BARS:
                     setSystemUiFlag(View.SYSTEM_UI_FLAG_FULLSCREEN);
-                    setWindowFlag(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                     return;
                 case WindowInsetsCompat.Type.NAVIGATION_BARS:
                     setSystemUiFlag(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
@@ -511,6 +510,20 @@
 
         @Override
         void setSystemBarsBehavior(int behavior) {
+            switch (behavior) {
+                case BEHAVIOR_SHOW_BARS_BY_SWIPE:
+                    unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+                    break;
+                case BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE:
+                    unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE);
+                    setSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    break;
+                case BEHAVIOR_SHOW_BARS_BY_TOUCH:
+                    unsetSystemUiFlag(View.SYSTEM_UI_FLAG_IMMERSIVE
+                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+                    break;
+            }
         }
 
         @Override
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/WindowInsetsControllerPlayground.kt b/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/WindowInsetsControllerPlayground.kt
index b43d49b..fb424b1 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/WindowInsetsControllerPlayground.kt
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/view/WindowInsetsControllerPlayground.kt
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:Suppress("DEPRECATION")
+
 package com.example.android.supportv4.view
 
 import android.animation.Animator
@@ -36,7 +38,6 @@
 import android.widget.ArrayAdapter
 import android.widget.Button
 import android.widget.CheckBox
-import android.widget.LinearLayout
 import android.widget.Spinner
 import android.widget.TextView
 import android.widget.ToggleButton
@@ -49,6 +50,8 @@
 import androidx.core.view.WindowInsetsAnimationControllerCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsCompat.Type.ime
+import androidx.core.view.WindowInsetsCompat.Type.navigationBars
+import androidx.core.view.WindowInsetsCompat.Type.statusBars
 import androidx.core.view.WindowInsetsCompat.Type.systemBars
 import androidx.core.view.WindowInsetsControllerCompat
 import com.example.android.supportv4.R
@@ -71,6 +74,7 @@
     private lateinit var editRow: ViewGroup
     private lateinit var visibility: TextView
     private lateinit var buttonsRow: ViewGroup
+    private lateinit var buttonsRow2: ViewGroup
     private lateinit var fitSystemWindow: CheckBox
     private lateinit var isDecorView: CheckBox
     internal lateinit var info: TextView
@@ -88,6 +92,7 @@
         editRow = findViewById(R.id.editRow)
         visibility = findViewById(R.id.visibility)
         buttonsRow = findViewById(R.id.buttonRow)
+        buttonsRow2 = findViewById(R.id.buttonRow2)
         info = findViewById(R.id.info)
         fitSystemWindow = findViewById(R.id.decorFitsSystemWindows)
         isDecorView = findViewById(R.id.isDecorView)
@@ -109,6 +114,9 @@
             isChecked = false
             setOnCheckedChangeListener { _, isChecked ->
                 WindowCompat.setDecorFitsSystemWindows(window, isChecked)
+                if (isChecked) {
+                    mRoot.setPadding(0, 0, 0, 0)
+                }
             }
         }
 
@@ -118,20 +126,8 @@
         setupTypeSpinner()
         setupHideShowButtons()
         setupAppearanceButtons()
-
-        ViewCompat.setOnApplyWindowInsetsListener(mRoot) { _: View?, insets: WindowInsetsCompat ->
-            val systemBarInsets = insets.getInsets(ime() or systemBars())
-            mRoot.setPadding(
-                systemBarInsets.left,
-                systemBarInsets.top,
-                systemBarInsets.right,
-                systemBarInsets.bottom
-            )
-            visibility.text =
-                "Inset visibility: " + currentType?.let { insets.isVisible(it) }?.toString()
-
-            WindowInsetsCompat.CONSUMED
-        }
+        setupBehaviorSpinner()
+        setupLayoutButton()
 
         setupIMEAnimation()
         setupActionButton()
@@ -167,9 +163,8 @@
         graph.minimumWidth = 300
         graph.minimumHeight = 100
         graph.setBackgroundColor(Color.GRAY)
-        val linearLayout = info.parent as LinearLayout
-        linearLayout.addView(
-            graph, linearLayout.indexOfChild(isDecorView),
+        findViewById<ViewGroup>(R.id.graph_container).addView(
+            graph,
             ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200)
         )
     }
@@ -189,18 +184,9 @@
                     text = name
                     textOn = text
                     textOff = text
-                    setOnClickListener {
-                        isChecked = true
-                        callback(true)
-
-                        it.postDelayed(
-                            {
-                                isChecked = false
-                                callback(false)
-                            },
-                            2000
-                        )
-                    }
+                    setOnCheckedChangeListener { _, isChecked -> callback(isChecked) }
+                    isChecked = true
+                    callback(true)
                 }
             )
         }
@@ -294,7 +280,12 @@
                 insets: WindowInsetsCompat,
                 runningAnimations: List<WindowInsetsAnimationCompat>
             ): WindowInsetsCompat {
-                mTransitions.forEach { it.onProgress() }
+                val systemInsets = insets.getInsets(systemBars())
+                mRoot.setPadding(
+                    systemInsets.left, systemInsets.top, systemInsets.right,
+                    systemInsets.bottom
+                )
+                mTransitions.forEach { it.onProgress(insets) }
                 return insets
             }
 
@@ -329,6 +320,35 @@
         }
     }
 
+    private fun setupLayoutButton() {
+        arrayOf(
+            "STABLE" to View.SYSTEM_UI_FLAG_LAYOUT_STABLE,
+            "STAT" to View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+            "NAV" to View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+        ).forEach { (name, flag) ->
+            buttonsRow2.addView(
+                ToggleButton(this).apply {
+                    text = name
+                    textOn = text
+                    textOff = text
+                    setOnCheckedChangeListener { _, isChecked ->
+                        val systemUiVisibility = window.decorView.systemUiVisibility
+                        window.decorView.systemUiVisibility =
+                            if (isChecked) systemUiVisibility or flag
+                            else systemUiVisibility and flag.inv()
+                    }
+                    isChecked = false
+                }
+            )
+        }
+        window.decorView.systemUiVisibility = window.decorView.systemUiVisibility and (
+            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+            )
+            .inv()
+    }
+
     private fun createOnTouchListener(): View.OnTouchListener {
         return object : View.OnTouchListener {
             private val mViewConfiguration =
@@ -433,10 +453,11 @@
 
     private fun setupTypeSpinner() {
         val types = mapOf(
-            "IME" to ime(),
-            "Navigation" to WindowInsetsCompat.Type.navigationBars(),
             "System" to systemBars(),
-            "Status" to WindowInsetsCompat.Type.statusBars()
+            "IME" to ime(),
+            "Navigation" to navigationBars(),
+            "Status" to statusBars(),
+            "All" to (systemBars() or ime())
         )
         findViewById<Spinner>(R.id.spn_insets_type).apply {
             adapter = ArrayAdapter(
@@ -461,6 +482,37 @@
         }
     }
 
+    private fun setupBehaviorSpinner() {
+        val types = mapOf(
+            "BY TOUCH" to WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH,
+            "BY SWIPE" to WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_SWIPE,
+            "TRANSIENT" to WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+        )
+        findViewById<Spinner>(R.id.spn_behavior).apply {
+            adapter = ArrayAdapter(
+                context, android.R.layout.simple_spinner_dropdown_item,
+                types.keys.toTypedArray()
+            )
+             : AdapterView.OnItemSelectedListener {
+                override fun onNothingSelected(parent: AdapterView<*>?) {
+                }
+
+                override fun onItemSelected(
+                    parent: AdapterView<*>?,
+                    view: View?,
+                    position: Int,
+                    id: Long
+                ) {
+                    if (parent != null && view != null) {
+                        WindowCompat.getInsetsController(window, view)!!
+                            .systemBarsBehavior = types[selectedItem]!!
+                    }
+                }
+            }
+            setSelection(0)
+        }
+    }
+
     inner class Transition(private val view: View) {
         private var mEndBottom = 0
         private var mStartBottom = 0
@@ -479,13 +531,8 @@
             }
         }
 
-        fun onProgress() {
-            mInsetsAnimation?.let {
-                view.y = (
-                    mStartBottom +
-                        (mEndBottom - mStartBottom) * it.interpolatedFraction - view.height
-                    )
-            }
+        fun onProgress(insets: WindowInsetsCompat) {
+            view.y = (mStartBottom + insets.getInsets(ime() or systemBars()).bottom).toFloat()
             if (debug) {
                 Log.d(TAG, view.y.toString())
                 values.add(view.y)
diff --git a/samples/Support4Demos/src/main/res/layout/activity_insets_controller.xml b/samples/Support4Demos/src/main/res/layout/activity_insets_controller.xml
index 0deaab4..fab30e8b 100644
--- a/samples/Support4Demos/src/main/res/layout/activity_insets_controller.xml
+++ b/samples/Support4Demos/src/main/res/layout/activity_insets_controller.xml
@@ -52,37 +52,70 @@
 
     </LinearLayout>
 
-    <CheckBox
-        android:id="@+id/decorFitsSystemWindows"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="setDecorFitsSystemWindows" />
+    <Spinner
+        android:id="@+id/spn_behavior"
+        android:layout_width="match_parent"
+        android:layout_height="50dp" />
 
-    <CheckBox
-        android:id="@+id/isDecorView"
+    <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:checked="true"
-        android:text="On Decor View" />
+        android:orientation="horizontal">
 
-    <TextView
-        android:id="@+id/visibility"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="TextView" />
+        <CheckBox
+            android:id="@+id/decorFitsSystemWindows"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="setDecorFitsSystemWindows" />
 
-    <TextView
-        android:id="@+id/info"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="TextView" />
+        <CheckBox
+            android:id="@+id/isDecorView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:checked="true"
+            android:text="On Decor View" />
+    </LinearLayout>
 
     <LinearLayout
         android:id="@+id/buttonRow"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        tools:layout_height="50dp"
         android:orientation="horizontal" />
 
+    <LinearLayout
+        android:id="@+id/buttonRow2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        tools:layout_height="50dp"
+        android:orientation="horizontal" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal">
+
+        <TextView
+            android:id="@+id/visibility"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+
+        <TextView
+            android:id="@+id/info"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="TextView" />
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/graph_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+    </FrameLayout>
+
     <FrameLayout
         android:id="@+id/scrollView"
         android:layout_height="0dp"