Notify RecyclerView.Adapter of changes on RecyclerView managed frame.

Bug: 139320633
Test: Verified working correctly using demo app.
Change-Id: I19f9b3c3ab9e9e94a412720e10b74c900f8d4143
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/BandSelectionHelperTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/BandSelectionHelperTest.java
index bc0cbc98..3a9a886 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/BandSelectionHelperTest.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/BandSelectionHelperTest.java
@@ -24,6 +24,7 @@
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
 import androidx.recyclerview.selection.GridModel.GridHost;
 import androidx.recyclerview.selection.testing.TestAdapter;
 import androidx.recyclerview.selection.testing.TestAutoScroller;
@@ -74,7 +75,13 @@
                 SelectionPredicates.createSelectAnything(),
                 StorageStrategy.createStringStorage());
 
-        EventBridge.install(mAdapter, mTracker, mKeyProvider);
+        EventBridge.install(mAdapter, mTracker, mKeyProvider, new Consumer<Runnable>() {
+            @Override
+            public void accept(Runnable runnable) {
+                runnable.run();
+            }
+        });
+
         FocusDelegate<String> focusDelegate = FocusDelegate.dummy();
 
         mBandController = new BandSelectionHelper<String>(
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
index 8ed4db9..7470b51 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTrackerTest.java
@@ -25,6 +25,7 @@
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
 import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate;
 import androidx.recyclerview.selection.testing.Bundles;
 import androidx.recyclerview.selection.testing.SelectionProbe;
@@ -89,7 +90,13 @@
                 mSelectionPredicate,
                 StorageStrategy.createStringStorage());
 
-        EventBridge.install(mAdapter, mTracker, mKeyProvider);
+        EventBridge.install(
+                mAdapter, mTracker, mKeyProvider, new Consumer<Runnable>() {
+                    @Override
+                    public void accept(Runnable runnable) {
+                        runnable.run();
+                    }
+                });
 
         mTracker.addObserver(mListener);
 
@@ -515,7 +522,13 @@
                 mSelectionPredicate,
                 StorageStrategy.createStringStorage());
 
-        EventBridge.install(mAdapter, mTracker, mKeyProvider);
+        EventBridge.install(
+                mAdapter, mTracker, mKeyProvider, new Consumer<Runnable>() {
+                    @Override
+                    public void accept(Runnable runnable) {
+                        runnable.run();
+                    }
+                });
 
         mTracker.addObserver(mListener);
 
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTracker_SingleSelectTest.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTracker_SingleSelectTest.java
index 6dac018..f47d65e 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTracker_SingleSelectTest.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/DefaultSelectionTracker_SingleSelectTest.java
@@ -48,7 +48,6 @@
                 keyProvider,
                 SelectionPredicates.createSelectSingleAnything(),
                 StorageStrategy.createStringStorage());
-        EventBridge.install(mAdapter, mTracker, keyProvider);
 
         mTracker.addObserver(mListener);
 
diff --git a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionTrackers.java b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionTrackers.java
index 32379bf..d76ce51 100644
--- a/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionTrackers.java
+++ b/recyclerview/recyclerview-selection/src/androidTest/java/androidx/recyclerview/selection/testing/SelectionTrackers.java
@@ -16,6 +16,7 @@
 
 package androidx.recyclerview.selection.testing;
 
+import androidx.annotation.NonNull;
 import androidx.recyclerview.selection.DefaultSelectionTracker;
 import androidx.recyclerview.selection.EventBridge;
 import androidx.recyclerview.selection.ItemKeyProvider;
@@ -37,7 +38,7 @@
                 SelectionPredicates.createSelectAnything(),
                 StorageStrategy.createStringStorage());
 
-        EventBridge.install(adapter, tracker, keyProvider);
+        EventBridge.install(adapter, tracker, keyProvider, SelectionTrackers::runImmediately);
 
         return tracker;
     }
@@ -52,8 +53,13 @@
                 SelectionPredicates.createSelectAnything(),
                 StorageStrategy.createLongStorage());
 
-        EventBridge.install(adapter, tracker, keyProvider);
+        EventBridge.install(adapter, tracker, keyProvider, SelectionTrackers::runImmediately);
 
         return tracker;
     }
+
+    // Test stand-in for RecyclerView::postOnAnimation.
+    private static void runImmediately(@NonNull Runnable runnable) {
+        runnable.run();
+    }
 }
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java
index c273106..3ea5fb4 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/EventBridge.java
@@ -26,6 +26,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Consumer;
 import androidx.recyclerview.widget.RecyclerView;
 
 /**
@@ -50,37 +51,45 @@
      * @param adapter
      * @param selectionTracker
      * @param keyProvider
+     * @param runner Callback allowing operation to be run at next opportune time.
+     *                   Implementation could be {@link RecyclerView#postOnAnimation(Runnable)}.
      *
      * @param <K> Selection key type. @see {@link StorageStrategy} for supported types.
      */
     public static <K> void install(
             @NonNull RecyclerView.Adapter<?> adapter,
             @NonNull SelectionTracker<K> selectionTracker,
-            @NonNull ItemKeyProvider<K> keyProvider) {
+            @NonNull ItemKeyProvider<K> keyProvider,
+            @NonNull Consumer<Runnable> runner) {
 
         // setup bridges to relay selection and adapter events
-        new TrackerToAdapterBridge<>(selectionTracker, keyProvider, adapter);
+        new TrackerToAdapterBridge<>(selectionTracker, keyProvider, adapter, runner);
         adapter.registerAdapterDataObserver(selectionTracker.getAdapterDataObserver());
     }
 
     private static final class TrackerToAdapterBridge<K>
             extends SelectionTracker.SelectionObserver<K> {
 
+        // Non-private as necessary to avoid synthetic accessors for inner classes.
+        final RecyclerView.Adapter<?> mAdapter;
         private final ItemKeyProvider<K> mKeyProvider;
-        private final RecyclerView.Adapter<?> mAdapter;
+        private final Consumer<Runnable> mRunner;
 
         TrackerToAdapterBridge(
                 @NonNull SelectionTracker<K> selectionTracker,
                 @NonNull ItemKeyProvider<K> keyProvider,
-                @NonNull RecyclerView.Adapter<?> adapter) {
+                @NonNull RecyclerView.Adapter<?> adapter,
+                Consumer<Runnable> runner) {
 
             selectionTracker.addObserver(this);
 
             checkArgument(keyProvider != null);
             checkArgument(adapter != null);
+            checkArgument(runner != null);
 
             mKeyProvider = keyProvider;
             mAdapter = adapter;
+            mRunner = runner;
         }
 
         /**
@@ -96,7 +105,12 @@
                 return;
             }
 
-            mAdapter.notifyItemChanged(position, SelectionTracker.SELECTION_CHANGED_MARKER);
+            mRunner.accept(new Runnable() {
+                @Override
+                public void run() {
+                    mAdapter.notifyItemChanged(position, SelectionTracker.SELECTION_CHANGED_MARKER);
+                }
+            });
         }
     }
 
diff --git a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
index 50cc4ceb..f913efd 100644
--- a/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
+++ b/recyclerview/recyclerview-selection/src/main/java/androidx/recyclerview/selection/SelectionTracker.java
@@ -709,7 +709,7 @@
             // Event glue between RecyclerView and SelectionTracker keeps the classes separate
             // so that a SelectionTracker can be shared across RecyclerView instances that
             // represent the same data in different ways.
-            EventBridge.install(mAdapter, tracker, mKeyProvider);
+            EventBridge.install(mAdapter, tracker, mKeyProvider, mRecyclerView::post);
 
             // Scroller is stateful and can be reset, but we don't manage it directly.
             // GestureSelectionHelper will reset scroller when it is reset.