Merge "FragmentTransactionCallback: state saving callback" into androidx-main
diff --git a/libraryversions.toml b/libraryversions.toml
index 8c64828..1b6b1c7 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -118,7 +118,7 @@
 VECTORDRAWABLE_SEEKABLE = "1.0.0-beta02"
 VERSIONED_PARCELABLE = "1.2.0-alpha01"
 VIEWPAGER = "1.1.0-alpha02"
-VIEWPAGER2 = "1.1.0-beta02"
+VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.3.0-alpha03"
 WEAR_COMPOSE = "1.0.0-beta03"
 WEAR_INPUT = "1.2.0-alpha03"
diff --git a/viewpager2/viewpager2/api/current.txt b/viewpager2/viewpager2/api/current.txt
index 997ce2a..111eb2f 100644
--- a/viewpager2/viewpager2/api/current.txt
+++ b/viewpager2/viewpager2/api/current.txt
@@ -24,6 +24,7 @@
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+    method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
   }
 
   public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/api/public_plus_experimental_current.txt b/viewpager2/viewpager2/api/public_plus_experimental_current.txt
index 997ce2a..111eb2f 100644
--- a/viewpager2/viewpager2/api/public_plus_experimental_current.txt
+++ b/viewpager2/viewpager2/api/public_plus_experimental_current.txt
@@ -24,6 +24,7 @@
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+    method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
   }
 
   public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/api/restricted_current.txt b/viewpager2/viewpager2/api/restricted_current.txt
index 4aab4bd..9316cd0 100644
--- a/viewpager2/viewpager2/api/restricted_current.txt
+++ b/viewpager2/viewpager2/api/restricted_current.txt
@@ -24,6 +24,7 @@
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentMaxLifecyclePreUpdated(androidx.fragment.app.Fragment, androidx.lifecycle.Lifecycle.State);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreAdded(androidx.fragment.app.Fragment);
     method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreRemoved(androidx.fragment.app.Fragment);
+    method public androidx.viewpager2.adapter.FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener onFragmentPreSavedInstanceState(androidx.fragment.app.Fragment);
   }
 
   public static interface FragmentStateAdapter.FragmentTransactionCallback.OnPostEventListener {
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
index 7a85e17..eca228f 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
@@ -225,6 +225,82 @@
     }
 
     @Test
+    fun test_fragmentSaveSateCallback() {
+        setUpTest(ORIENTATION_HORIZONTAL).apply {
+            // given
+            val items = stringSequence(10).toMutableList()
+            val adapterProvider = fragmentAdapterProviderValueId.provider(items)
+            setAdapterSync(adapterProvider)
+            runOnUiThreadSync { viewPager.offscreenPageLimit = 1 }
+
+            val adapter = viewPager.adapter as FragmentStateAdapter
+            val fragmentManager = activity.supportFragmentManager
+
+            val log = RecordingLogger()
+            val adapterCallback = createRecordingFragmentTransactionCallback(log)
+            val lifecycleCallback = createRecordingFragmentLifecycleCallback(log)
+            adapter.registerFragmentTransactionCallback(adapterCallback)
+            fragmentManager.registerFragmentLifecycleCallbacks(lifecycleCallback, true)
+
+            // when: new item is pushed at the start of the list
+            val latch = adapter.registerFragmentRemovedLatch()
+
+            runOnUiThreadSync {
+                val newValue = "11"
+                assertThat(items.contains(newValue), equalTo(false))
+                items.add(0, newValue) // note: new value added at the start of the list
+
+                adapter.notifyDataSetChanged()
+            }
+
+            latch.awaitStrict(5)
+
+            // then
+            assertThat(
+                log.consume(), equalTo(
+                    listOf(
+                        "Adapter:onFragmentPreAdded(<no-tag>)",
+                        "Lifecycle:onFragmentPreAttached(f1)",
+                        "Lifecycle:onFragmentAttached(f1)",
+                        "Lifecycle:onFragmentPreCreated(f1)",
+                        "Lifecycle:onFragmentCreated(f1)",
+                        "Lifecycle:onFragmentViewCreated(f1)",
+                        "Lifecycle:onFragmentActivityCreated(f1)",
+                        "Lifecycle:onFragmentStarted(f1)",
+                        "Adapter:onFragmentAdded(f1)",
+                        "Adapter:onFragmentPreAdded(<no-tag>)",
+                        "Lifecycle:onFragmentPreAttached(f11)",
+                        "Lifecycle:onFragmentAttached(f11)",
+                        "Lifecycle:onFragmentPreCreated(f11)",
+                        "Lifecycle:onFragmentCreated(f11)",
+                        "Lifecycle:onFragmentViewCreated(f11)",
+                        "Lifecycle:onFragmentActivityCreated(f11)",
+                        "Lifecycle:onFragmentStarted(f11)",
+                        "Adapter:onFragmentMaxLifecyclePreUpdated(f0 at STARTED)",
+                        "Adapter:onFragmentMaxLifecyclePreUpdated(f1 at STARTED)",
+                        "Adapter:onFragmentMaxLifecyclePreUpdated(f11 at RESUMED)",
+                        "Lifecycle:onFragmentPaused(f0)",
+                        "Lifecycle:onFragmentResumed(f11)",
+                        "Adapter:onFragmentMaxLifecycleUpdated(f11 at RESUMED)",
+                        "Adapter:onFragmentMaxLifecycleUpdated(f1 at STARTED)",
+                        "Adapter:onFragmentMaxLifecycleUpdated(f0 at STARTED)",
+                        "Adapter:onFragmentAdded(f11)",
+                        "Adapter:onFragmentPreSavedInstanceState(f1)", // note: pre-saved
+                        "Lifecycle:onFragmentSaveInstanceState(f1)",
+                        "Adapter:onFragmentSavedInstanceState(f1)", // note: saved
+                        "Adapter:onFragmentPreRemoved(f1)",
+                        "Lifecycle:onFragmentStopped(f1)",
+                        "Lifecycle:onFragmentViewDestroyed(f1)",
+                        "Lifecycle:onFragmentDestroyed(f1)",
+                        "Lifecycle:onFragmentDetached(f1)",
+                        "Adapter:onFragmentRemoved(<no-tag>)",
+                    )
+                )
+            )
+        }
+    }
+
+    @Test
     fun test_unregisterInsideCallback() {
         setUpTest(ORIENTATION_HORIZONTAL).apply {
             setAdapterSync(fragmentAdapterProviderValueId.provider(stringSequence(2)))
@@ -360,6 +436,15 @@
                     }
                 }
 
+                override fun onFragmentPreSavedInstanceState(
+                    fragment: Fragment
+                ): OnPostEventListener {
+                    log.append("Adapter:onFragmentPreSavedInstanceState(${fragment.name})")
+                    return OnPostEventListener {
+                        log.append("Adapter:onFragmentSavedInstanceState(${fragment.name})")
+                    }
+                }
+
                 override fun onFragmentPreRemoved(fragment: Fragment): OnPostEventListener {
                     log.append("Adapter:onFragmentPreRemoved(${fragment.name})")
                     return OnPostEventListener {
diff --git a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index 2c77b9b..a0ec29c 100644
--- a/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -474,7 +474,12 @@
         }
 
         if (fragment.isAdded() && containsItem(itemId)) {
-            mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
+            List<OnPostEventListener> onPost =
+                    mFragmentEventDispatcher.dispatchPreSavedInstanceState(fragment);
+            Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(fragment);
+            mFragmentEventDispatcher.dispatchPostEvents(onPost);
+
+            mSavedStates.put(itemId, savedState);
         }
         List<OnPostEventListener> onPost =
                 mFragmentEventDispatcher.dispatchPreRemoved(fragment);
@@ -837,6 +842,14 @@
             return result;
         }
 
+        public List<OnPostEventListener> dispatchPreSavedInstanceState(Fragment fragment) {
+            List<OnPostEventListener> result = new ArrayList<>();
+            for (FragmentTransactionCallback callback : mCallbacks) {
+                result.add(callback.onFragmentPreSavedInstanceState(fragment));
+            }
+            return result;
+        }
+
         public List<OnPostEventListener> dispatchPreRemoved(Fragment fragment) {
             List<OnPostEventListener> result = new ArrayList<>();
             for (FragmentTransactionCallback callback : mCallbacks) {
@@ -870,6 +883,18 @@
         }
 
         /**
+         * Called right before Fragment's state is being saved through a
+         * {@link FragmentManager#saveFragmentInstanceState} call.
+         *
+         * @param fragment Fragment which state is being saved
+         * @return Listener called after the operation
+         */
+        @NonNull
+        public OnPostEventListener onFragmentPreSavedInstanceState(@NonNull Fragment fragment) {
+            return NO_OP;
+        }
+
+        /**
          * Called right before the Fragment is removed from adapter's FragmentManager.
          *
          * @param fragment Fragment changing state