Merge "copy new native libs if they are loaded in another classloader" into androidx-master-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
index 274eed9..5f28cd8 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ErrorProneConfiguration.kt
@@ -104,6 +104,7 @@
             "-Xep:FutureReturnValueIgnored:ERROR",
             "-Xep:HidingField:ERROR",
             "-Xep:AutoValueFinalMethods:ERROR",
+            "-Xep:LockNotBeforeTry:ERROR",
 
             // Nullaway
             "-XepIgnoreUnknownCheckNames", // https://github.com/uber/NullAway/issues/25
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
index f35b7f9..c7ea442 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -361,8 +361,8 @@
         public void run() {
             final Lock closeLock = mDatabase.getCloseLock();
             Set<Integer> invalidatedTableIds = null;
+            closeLock.lock();
             try {
-                closeLock.lock();
 
                 if (!ensureInitialization()) {
                     return;
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index b038c89..8595be4 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -241,8 +241,8 @@
     public void close() {
         if (isOpen()) {
             final Lock closeLock = mCloseLock.writeLock();
+            closeLock.lock();
             try {
-                closeLock.lock();
                 mInvalidationTracker.stopMultiInstanceInvalidation();
                 mOpenHelper.close();
             } finally {
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java
deleted file mode 100644
index cc83ca1..0000000
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/ContentUriKeyProvider.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2017 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 com.example.android.supportv7.widget.selection.fancy;
-
-import android.net.Uri;
-
-import androidx.annotation.Nullable;
-import androidx.recyclerview.selection.ItemKeyProvider;
-
-import java.util.HashMap;
-import java.util.Map;
-
-class ContentUriKeyProvider extends ItemKeyProvider<Uri> {
-
-    private final Uri[] mUris;
-    private final Map<Uri, Integer> mPositions;
-
-    ContentUriKeyProvider(String authority, String[] values) {
-        // Advise the world we can supply ids/position for entire copus
-        // at any time.
-        super(SCOPE_MAPPED);
-
-        mUris = new Uri[values.length];
-        mPositions = new HashMap<>();
-
-        for (int i = 0; i < values.length; i++) {
-            mUris[i] = new Uri.Builder()
-                    .scheme("content")
-                    .encodedAuthority(authority)
-                    .appendPath(values[i])
-                    .build();
-            mPositions.put(mUris[i], i);
-        }
-    }
-
-    @Override
-    public @Nullable Uri getKey(int position) {
-        return mUris[position];
-    }
-
-    @Override
-    public int getPosition(Uri key) {
-        return mPositions.get(key);
-    }
-}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java
index 92ed977..c2d57f0 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyDetailsLookup.java
@@ -41,9 +41,11 @@
         @Nullable View view = mRecView.findChildViewUnder(e.getX(), e.getY());
         if (view != null) {
             ViewHolder holder = mRecView.getChildViewHolder(view);
-            if (holder instanceof FancyHolder) {
-                return ((FancyHolder) holder).getItemDetails();
+            if (holder instanceof FancyItemHolder) {
+                return ((FancyItemHolder) holder).getItemDetails();
             }
+            // FancyHeaderHolder doesn't hold a selectable item,
+            // so it doesn't support getItemDetails.
         }
         return null;
     }
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHeaderHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHeaderHolder.java
new file mode 100644
index 0000000..4ed2d1f
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHeaderHolder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.example.android.supportv7.R;
+
+final class FancyHeaderHolder extends FancyHolder {
+
+    private static final String HEADER_TAG = "I'm a header";
+    public final TextView mLabel;
+
+    FancyHeaderHolder(LinearLayout layout) {
+        super(layout);
+        layout.setTag(HEADER_TAG);
+        mLabel = layout.findViewById(R.id.label);
+    }
+
+    void update(String label) {
+        mLabel.setText(label.toUpperCase() + label + label + "...");
+    }
+
+    @Override
+    public String toString() {
+        return "Header{name:" + mLabel.getText() + "}";
+    }
+
+    static boolean isHeader(View view) {
+        return HEADER_TAG.equals(view.getTag());
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java
index 6637d88..6fbc206 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyHolder.java
@@ -13,111 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.example.android.supportv7.widget.selection.fancy;
 
-import android.graphics.Rect;
-import android.net.Uri;
-import android.view.MotionEvent;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.example.android.supportv7.R;
-
-final class FancyHolder extends RecyclerView.ViewHolder {
-
-    private final LinearLayout mContainer;
-    public final TextView mSelector;
-    public final TextView mLabel;
-    private final ItemDetails<Uri> mDetails;
-
-    private @Nullable Uri mKey;
-
+abstract class FancyHolder extends RecyclerView.ViewHolder {
     FancyHolder(LinearLayout layout) {
         super(layout);
-        mContainer = layout.findViewById(R.id.container);
-        mSelector = layout.findViewById(R.id.selector);
-        mLabel = layout.findViewById(R.id.label);
-        mDetails = new ItemDetails<Uri>() {
-            @Override
-            public int getPosition() {
-                return FancyHolder.this.getAdapterPosition();
-            }
-
-            @Override
-            public Uri getSelectionKey() {
-                return FancyHolder.this.mKey;
-            }
-
-            @Override
-            public boolean inDragRegion(@NonNull MotionEvent e) {
-                return FancyHolder.this.inDragRegion(e);
-            }
-
-            @Override
-            public boolean inSelectionHotspot(@NonNull MotionEvent e) {
-                return FancyHolder.this.inSelectRegion(e);
-            }
-
-            @NonNull
-            @Override
-            public String toString() {
-                return FancyHolder.this.toString();
-            }
-        };
-    }
-
-    void update(Uri key, String label, boolean selected) {
-        mKey = key;
-        mLabel.setText(label);
-        setSelected(selected);
-    }
-
-    private void setSelected(boolean selected) {
-        mContainer.setActivated(selected);
-        mSelector.setActivated(selected);
-    }
-
-    boolean inDragRegion(MotionEvent event) {
-        // If itemView is activated = selected, then whole region is interactive
-        if (itemView.isActivated()) {
-            return true;
-        }
-
-        // Do everything in global coordinates - it makes things simpler.
-        int[] coords = new int[2];
-        mSelector.getLocationOnScreen(coords);
-
-        Rect textBounds = new Rect();
-        mLabel.getPaint().getTextBounds(
-                mLabel.getText().toString(), 0, mLabel.getText().length(), textBounds);
-
-        Rect rect = new Rect(
-                coords[0],
-                coords[1],
-                coords[0] + mSelector.getWidth() + textBounds.width(),
-                coords[1] + Math.max(mSelector.getHeight(), textBounds.height()));
-
-        // If the tap occurred inside icon or the text, these are interactive spots.
-        return rect.contains((int) event.getRawX(), (int) event.getRawY());
-    }
-
-    boolean inSelectRegion(MotionEvent e) {
-        Rect iconRect = new Rect();
-        mSelector.getGlobalVisibleRect(iconRect);
-        return iconRect.contains((int) e.getRawX(), (int) e.getRawY());
-    }
-
-    ItemDetails<Uri> getItemDetails() {
-        return mDetails;
-    }
-
-    @Override
-    public String toString() {
-        return "Item{name:" + mLabel.getText() + ", url:" + mKey + "}";
     }
 }
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java
new file mode 100644
index 0000000..c7761b2
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancyItemHolder.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2017 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 com.example.android.supportv7.widget.selection.fancy;
+
+import android.graphics.Rect;
+import android.net.Uri;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+import com.example.android.supportv7.R;
+
+final class FancyItemHolder extends FancyHolder {
+
+    private final LinearLayout mContainer;
+    private final TextView mSelector;
+    private final TextView mLabel;
+    private final ItemDetails<Uri> mDetails;
+
+    private @Nullable Uri mKey;
+
+    FancyItemHolder(LinearLayout layout) {
+        super(layout);
+
+        mContainer = layout.findViewById(R.id.container);
+        mSelector = layout.findViewById(R.id.selector);
+        mLabel = layout.findViewById(R.id.label);
+        mDetails = new ItemDetails<Uri>() {
+            @Override
+            public int getPosition() {
+                return FancyItemHolder.this.getAdapterPosition();
+            }
+
+            @Override
+            public Uri getSelectionKey() {
+                return FancyItemHolder.this.mKey;
+            }
+
+            @Override
+            public boolean inDragRegion(@NonNull MotionEvent e) {
+                return FancyItemHolder.this.inDragRegion(e);
+            }
+
+            @Override
+            public boolean inSelectionHotspot(@NonNull MotionEvent e) {
+                return FancyItemHolder.this.inSelectRegion(e);
+            }
+
+            @NonNull
+            @Override
+            public String toString() {
+                return FancyItemHolder.this.toString();
+            }
+        };
+    }
+
+    void update(Uri key, String label, boolean selected) {
+        mKey = key;
+        mLabel.setText(label);
+        setSelected(selected);
+    }
+
+    private void setSelected(boolean selected) {
+        mContainer.setActivated(selected);
+        mSelector.setActivated(selected);
+    }
+
+    boolean inDragRegion(MotionEvent event) {
+        // If itemView is activated = selected, then whole region is interactive
+        if (itemView.isActivated()) {
+            return true;
+        }
+
+        // Do everything in global coordinates - it makes things simpler.
+        int[] coords = new int[2];
+        mSelector.getLocationOnScreen(coords);
+
+        Rect textBounds = new Rect();
+        mLabel.getPaint().getTextBounds(
+                mLabel.getText().toString(), 0, mLabel.getText().length(), textBounds);
+
+        Rect rect = new Rect(
+                coords[0],
+                coords[1],
+                coords[0] + mSelector.getWidth() + textBounds.width(),
+                coords[1] + Math.max(mSelector.getHeight(), textBounds.height()));
+
+        // If the tap occurred inside icon or the text, these are interactive spots.
+        return rect.contains((int) event.getRawX(), (int) event.getRawY());
+    }
+
+    boolean inSelectRegion(MotionEvent e) {
+        Rect iconRect = new Rect();
+        mSelector.getGlobalVisibleRect(iconRect);
+        return iconRect.contains((int) e.getRawX(), (int) e.getRawY());
+    }
+
+    ItemDetails<Uri> getItemDetails() {
+        return mDetails;
+    }
+
+    @Override
+    public String toString() {
+        return "Item{name:" + mLabel.getText() + ", url:" + mKey + "}";
+    }
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java
index c54368b..38e0046 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoActivity.java
@@ -58,6 +58,7 @@
 
     private GridLayoutManager mLayout;
     private int mColumnCount = 1;  // This will get updated when layout changes.
+    private boolean mIterceptListenerEnabled = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,6 +67,32 @@
         setContentView(R.layout.selection_demo_layout);
         mRecView = (RecyclerView) findViewById(R.id.list);
 
+        // If you want to provided special handling of clicks on items
+        // in RecyclerView (respond to a play button, or show a menu
+        // when a three-dot menu is clicked) you can't just add an OnClickListener
+        // to the View.  This is because Selection lib installs an
+        // OnItemTouchListener w/ RecyclerView, and that listener eats
+        // up many of the touch/mouse events RecyclerView sends its way.
+        // To work around this install your own OnItemTouchListener *before*
+        // you build your SelectionTracker instance. That'll give your listener
+        // a chance to intercept events before Selection lib gobbles them up.
+        mRecView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
+            @Override
+            public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
+                return mIterceptListenerEnabled
+                        && FancyHeaderHolder.isHeader(rv.findChildViewUnder(e.getX(), e.getY()));
+            }
+
+            @Override
+            public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
+                toast(FancySelectionDemoActivity.this, "Clicked on a header!");
+            }
+
+            @Override
+            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+            }
+        });
+
         mLayout = new GridLayoutManager(this, mColumnCount);
         mRecView.setLayoutManager(mLayout);
         mAdapter = new FancySelectionDemoAdapter(this);
@@ -137,27 +164,19 @@
     @CallSuper
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
-        menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
-        menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
+        menu.findItem(R.id.option_menu_enable_listener)
+                .setEnabled(!mIterceptListenerEnabled);
+        menu.findItem(R.id.option_menu_disable_listener)
+                .setEnabled(mIterceptListenerEnabled);
         return true;
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.option_menu_add_column:
-                // TODO: Add columns
-                mLayout.setSpanCount(++mColumnCount);
-                return true;
-            case R.id.option_menu_remove_column:
-                mLayout.setSpanCount(--mColumnCount);
-                return true;
-            default:
-                return super.onOptionsItemSelected(item);
-        }
+        mIterceptListenerEnabled = !mIterceptListenerEnabled;
+        return true;
     }
 
-
     @Override
     public void onCreateContextMenu(ContextMenu menu, View v,
             ContextMenu.ContextMenuInfo menuInfo) {
@@ -302,4 +321,4 @@
             return true;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java
index 5d2f5cb..804e7ec 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/fancy/FancySelectionDemoAdapter.java
@@ -23,8 +23,9 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.recyclerview.selection.ItemKeyProvider;
 import androidx.recyclerview.selection.SelectionTracker;
 import androidx.recyclerview.widget.RecyclerView;
@@ -32,29 +33,34 @@
 import com.example.android.supportv7.Cheeses;
 import com.example.android.supportv7.R;
 
+import java.util.HashMap;
+import java.util.Map;
+
 final class FancySelectionDemoAdapter extends RecyclerView.Adapter<FancyHolder> {
 
-    private final ContentUriKeyProvider mKeyProvider;
+    public static final int TYPE_HEADER = 1;
+    public static final int TYPE_ITEM = 2;
+
+    private final KeyProvider mKeyProvider;
     private final Context mContext;
 
-    // This should be replaced at "bind" time with a real test that
-    // asks SelectionTracker.
-    private SelectionTest mSelTest;
+    // This default implementation must be replaced
+    // with a real implementation in #bindSelectionHelper.
+    private SelectionTest mSelTest = new SelectionTest() {
+        @Override
+        public boolean isSelected(Uri id) {
+            throw new IllegalStateException(
+                    "Adapter must be initialized with SelectionTracker");
+        }
+    };
 
     FancySelectionDemoAdapter(Context context) {
         mContext = context;
-        mKeyProvider = new ContentUriKeyProvider("cheeses", Cheeses.sCheeseStrings);
-        mSelTest = new SelectionTest() {
-            @Override
-            public boolean isSelected(Uri id) {
-                throw new IllegalStateException(
-                        "Adapter must be initialized with SelectionTracker");
-            }
-        };
+        mKeyProvider = new KeyProvider("cheeses", Cheeses.sCheeseStrings);
 
         // In the fancy edition of selection support we supply access to stable
         // ids using content URI. Since we can map between position and selection key
-        // at will we get fancy dependent functionality like band selection and range support.
+        // at-will we get band selection and range support.
         setHasStableIds(false);
     }
 
@@ -63,12 +69,12 @@
     }
 
     // Glue together SelectionTracker and the adapter.
-    public void bindSelectionHelper(final SelectionTracker<Uri> selectionTracker) {
-        checkArgument(selectionTracker != null);
+    public void bindSelectionHelper(final SelectionTracker<Uri> tracker) {
+        checkArgument(tracker != null);
         mSelTest = new SelectionTest() {
             @Override
             public boolean isSelected(Uri id) {
-                return selectionTracker.isSelected(id);
+                return tracker.isSelected(id);
             }
         };
     }
@@ -83,7 +89,7 @@
 
     @Override
     public int getItemCount() {
-        return Cheeses.sCheeseStrings.length;
+        return mKeyProvider.getCount();
     }
 
     @Override
@@ -92,15 +98,40 @@
     }
 
     @Override
-    public void onBindViewHolder(FancyHolder holder, int position) {
-        Uri uri = mKeyProvider.getKey(position);
-        holder.update(uri, uri.getLastPathSegment(), mSelTest.isSelected(uri));
+    public void onBindViewHolder(@NonNull FancyHolder holder, int position) {
+        if (holder instanceof FancyHeaderHolder) {
+            Uri uri = mKeyProvider.getKey(position);
+            ((FancyHeaderHolder) holder).update(uri.getPathSegments().get(0));
+        } else if (holder instanceof FancyItemHolder) {
+            Uri uri = mKeyProvider.getKey(position);
+            ((FancyItemHolder) holder).update(uri, uri.getPathSegments().get(1),
+                    mSelTest.isSelected(uri));
+        }
     }
 
     @Override
-    public FancyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        LinearLayout layout = inflateLayout(mContext, parent, R.layout.selection_demo_list_item);
-        return new FancyHolder(layout);
+    public int getItemViewType(int position) {
+        Uri key = mKeyProvider.getKey(position);
+        if (key.getPathSegments().size() == 1) {
+            return TYPE_HEADER;
+        } else if (key.getPathSegments().size() == 2) {
+            return TYPE_ITEM;
+        }
+
+        throw new RuntimeException("Unknown view type a position " + position);
+    }
+
+    @Override
+    public FancyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        switch (viewType) {
+            case TYPE_HEADER:
+                return new FancyHeaderHolder(
+                        inflateLayout(mContext, parent, R.layout.selection_demo_list_header));
+            case TYPE_ITEM:
+                return new FancyItemHolder(
+                        inflateLayout(mContext, parent, R.layout.selection_demo_list_item));
+        }
+        throw new RuntimeException("Unsupported view type" + viewType);
     }
 
     @SuppressWarnings("TypeParameterUnusedInFormals")  // Convenience to avoid clumsy cast.
@@ -113,4 +144,62 @@
     private interface SelectionTest {
         boolean isSelected(Uri id);
     }
+
+    private static final class KeyProvider extends ItemKeyProvider<Uri> {
+
+        private final Uri[] mUris;
+        private final Map<Uri, Integer> mPositions;
+
+        KeyProvider(String authority, String[] values) {
+            // Advise the world we can supply ids/position for entire copus
+            // at any time.
+            super(SCOPE_MAPPED);
+
+            // For the convenience of this demo, we simply trust, based on
+            // past understanding that Cheeses has at least one element
+            // starting with each letter of the English alphabet :)
+            mUris = new Uri[Cheeses.sCheeseStrings.length + 26];
+            mPositions = new HashMap<>();
+
+            char section = '-';  // anything value other than 'a' will do the trick here.
+            int headerOffset = 0;
+
+            for (int i = 0; i < Cheeses.sCheeseStrings.length; i++) {
+                char leadingChar = Cheeses.sCheeseStrings[i].toLowerCase().charAt(0);
+                // When we find a new leading character insert an artificial
+                // cheese header
+                if (leadingChar != section) {
+                    section = leadingChar;
+                    mUris[i + headerOffset] = new Uri.Builder()
+                            .scheme("content")
+                            .encodedAuthority(authority)
+                            .appendPath(Character.toString(section))
+                            .build();
+                    mPositions.put(mUris[i + headerOffset], i + headerOffset);
+                    headerOffset++;
+                }
+                mUris[i + headerOffset] = new Uri.Builder()
+                        .scheme("content")
+                        .encodedAuthority(authority)
+                        .appendPath(Character.toString(section))
+                        .appendPath(Cheeses.sCheeseStrings[i])
+                        .build();
+                mPositions.put(mUris[i + headerOffset], i + headerOffset);
+            }
+        }
+
+        @Override
+        public @Nullable Uri getKey(int position) {
+            return mUris[position];
+        }
+
+        @Override
+        public int getPosition(@NonNull Uri key) {
+            return mPositions.get(key);
+        }
+
+        int getCount() {
+            return mUris.length;
+        }
+    }
 }
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java
index fafddc4..63fda61 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/selection/simple/SimpleSelectionDemoActivity.java
@@ -19,12 +19,9 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.widget.Toast;
 
-import androidx.annotation.CallSuper;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
 import androidx.recyclerview.selection.ItemKeyProvider;
@@ -128,39 +125,6 @@
     }
 
     @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        boolean showMenu = super.onCreateOptionsMenu(menu);
-        getMenuInflater().inflate(R.menu.selection_demo_actions, menu);
-        return showMenu;
-    }
-
-    @Override
-    @CallSuper
-    public boolean onPrepareOptionsMenu(Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        menu.findItem(R.id.option_menu_add_column).setEnabled(mColumnCount <= 3);
-        menu.findItem(R.id.option_menu_remove_column).setEnabled(mColumnCount > 1);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.option_menu_add_column:
-                // TODO: Add columns
-                mLayout.setSpanCount(++mColumnCount);
-                return true;
-
-            case R.id.option_menu_remove_column:
-                mLayout.setSpanCount(--mColumnCount);
-                return true;
-
-            default:
-                return super.onOptionsItemSelected(item);
-        }
-    }
-
-    @Override
     public void onBackPressed() {
         if (mSelectionTracker.clearSelection()) {
             return;
diff --git a/samples/Support7Demos/src/main/res/color/selection_demo_item.xml b/samples/Support7Demos/src/main/res/color/selection_demo_item.xml
new file mode 100644
index 0000000..ea11b42
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/color/selection_demo_item.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2019 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_activated="true"
+        android:color="?android:attr/colorForegroundInverse"
+        />
+    <item
+        android:state_activated="false"
+        android:color="?android:attr/colorForegroundInverse"
+        android:alpha=".3"
+        />
+</selector>
diff --git a/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml b/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml
index bd87b4c..c800127 100644
--- a/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml
+++ b/samples/Support7Demos/src/main/res/color/selection_demo_item_selector.xml
@@ -16,12 +16,13 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item
-        android:state_activated="true"
-        android:color="?android:attr/colorForeground"
-        />
-    <item
         android:state_activated="false"
         android:color="?android:attr/colorForeground"
         android:alpha=".3"
         />
+    <item
+        android:state_activated="true"
+        android:color="#FFFF0000"
+        android:alpha=".4"
+        />
 </selector>
diff --git a/samples/Support7Demos/src/main/res/drawable/selection_demo_item.xml b/samples/Support7Demos/src/main/res/drawable/selection_demo_item.xml
new file mode 100644
index 0000000..52d11c5
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/drawable/selection_demo_item.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true">
+        <color android:color="#22FF0000"></color>
+    </item>
+</selector>
diff --git a/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml b/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml
index e4dbd5f..e15b281 100644
--- a/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml
+++ b/samples/Support7Demos/src/main/res/drawable/selection_demo_item_background.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_activated="true">
-        <color android:color="#220000FF"></color>
+        <color android:color="#2200FF00"></color>
     </item>
 </selector>
diff --git a/samples/Support7Demos/src/main/res/layout/selection_demo_list_header.xml b/samples/Support7Demos/src/main/res/layout/selection_demo_list_header.xml
new file mode 100644
index 0000000..13e127a
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/layout/selection_demo_list_header.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp"
+    android:layout_height="50dp">
+  <LinearLayout
+      android:id="@+id/container"
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_height="match_parent"
+      android:layout_width="match_parent"
+      android:background="@drawable/selection_demo_item_background">
+      <TextView
+          android:id="@+id/label"
+          android:textSize="30sp"
+          android:textStyle="bold"
+          android:gravity="center_vertical"
+          android:paddingStart="10dp"
+          android:paddingEnd="10dp"
+          android:layout_height="match_parent"
+          android:layout_width="match_parent">
+      </TextView>
+  </LinearLayout>
+</LinearLayout>
diff --git a/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml b/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml
index 0d4b718..fb5e8e9 100644
--- a/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml
+++ b/samples/Support7Demos/src/main/res/layout/selection_demo_list_item.xml
@@ -27,11 +27,10 @@
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_height="match_parent"
       android:layout_width="match_parent"
-      android:background="@drawable/selection_demo_item_background">
+      android:background="#FFFFFFFF">
       <TextView
           android:id="@+id/selector"
           android:textSize="20sp"
-          android:textStyle="bold"
           android:gravity="center"
           android:layout_height="match_parent"
           android:layout_width="40dp"
@@ -47,7 +46,8 @@
           android:paddingStart="10dp"
           android:paddingEnd="10dp"
           android:layout_height="match_parent"
-          android:layout_width="match_parent">
+          android:layout_width="wrap_content"
+          android:background="@drawable/selection_demo_item">
       </TextView>
   </LinearLayout>
 </LinearLayout>
diff --git a/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml b/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml
index 484d8b6..ce76ddc3 100644
--- a/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml
+++ b/samples/Support7Demos/src/main/res/menu/selection_demo_actions.xml
@@ -16,9 +16,9 @@
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
-       android:id="@+id/option_menu_add_column"
-       android:title="Add column" />
+       android:id="@+id/option_menu_enable_listener"
+       android:title="Enable OnItemTouchListener" />
    <item
-       android:id="@+id/option_menu_remove_column"
-       android:title="Remove column" />
+       android:id="@+id/option_menu_disable_listener"
+       android:title="Disable OnItemTouchListener" />
 </menu>
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
index 0945a5d..17c5a12 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AccessibilityTest.kt
@@ -66,7 +66,7 @@
     @SdkSuppress(minSdkVersion = 16)
     fun test_onPerformPageAction() {
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(6)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(6)))
 
             val initialPage = viewPager.currentItem
             assertBasicState(initialPage)
@@ -93,7 +93,7 @@
     @Test
     fun test_collectionInfo() {
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(6)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(6)))
 
             val initialPage = viewPager.currentItem
             assertBasicState(initialPage)
@@ -124,7 +124,7 @@
     @Test
     fun test_onOrientationChange() {
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(2)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(2)))
 
             val initialPage = viewPager.currentItem
             assertBasicState(initialPage)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
index 3815b5a..847c44c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
@@ -102,7 +102,7 @@
         test = setUpTest(config.orientation)
         activityTestRule.runOnUiThread { test.viewPager.offscreenPageLimit = 1 }
         dataSet = stringSequence(pageCount).toMutableList()
-        test.setAdapterSync(config.adapterProvider(dataSet))
+        test.setAdapterSync(config.adapterProvider.provider(dataSet))
     }
 
     @Test
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
index f2333f9..a61995a 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterTest.kt
@@ -49,7 +49,7 @@
     @Test
     fun test_setAdapter() {
         val recorder = test.viewPager.addNewRecordingCallback()
-        test.setAdapterSync(viewAdapterProvider(stringSequence(pageCount)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(pageCount)))
         test.assertBasicState(0)
         test.viewPager.setCurrentItemSync(1, false, 2, SECONDS)
         test.assertBasicState(1)
@@ -195,7 +195,7 @@
 
     private fun setUpAdapterSync(pageCount: Int, initialPage: Int? = null) {
         dataSet = stringSequence(pageCount).toMutableList()
-        test.setAdapterSync(viewAdapterProvider(dataSet))
+        test.setAdapterSync(viewAdapterProvider.provider(dataSet))
 
         if (initialPage != null) {
             test.viewPager.setCurrentItemSync(initialPage, false, 2, SECONDS)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index d724b7f..688625ca 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -599,9 +599,14 @@
 
 typealias AdapterProvider = (TestActivity) -> RecyclerView.Adapter<out RecyclerView.ViewHolder>
 
-typealias AdapterProviderForItems = (items: List<String>) -> AdapterProvider
+data class AdapterProviderForItems(
+    val name: String,
+    val provider: (items: List<String>) -> AdapterProvider
+) {
+    override fun toString(): String = name
+}
 
-val fragmentAdapterProvider: AdapterProviderForItems = { items ->
+val fragmentAdapterProvider = AdapterProviderForItems("fragmentAdapterProvider") { items ->
     { activity: TestActivity ->
         FragmentAdapter(
             activity.supportFragmentManager,
@@ -616,35 +621,38 @@
  * [FragmentStateAdapter.getItemId] and [FragmentStateAdapter.containsItem].
  * Not suitable for testing [RecyclerView.Adapter.notifyDataSetChanged].
  */
-val fragmentAdapterProviderCustomIds: AdapterProviderForItems = { items ->
-    { activity ->
-        fragmentAdapterProvider(items)(activity).also {
-            // more than position can represent, so a good test if ids are used consistently
-            val offset = 3L * Int.MAX_VALUE
-            val adapter = it as FragmentAdapter
-            adapter.positionToItemId = { position -> position + offset }
-            adapter.itemIdToContains = { itemId ->
-                val position = itemId - offset
-                position in (0 until adapter.itemCount)
+val fragmentAdapterProviderCustomIds =
+    AdapterProviderForItems("fragmentAdapterProviderCustomIds") { items ->
+        { activity ->
+            fragmentAdapterProvider.provider(items)(activity).also {
+                // more than position can represent, so a good test if ids are used consistently
+                val offset = 3L * Int.MAX_VALUE
+                val adapter = it as FragmentAdapter
+                adapter.positionToItemId = { position -> position + offset }
+                adapter.itemIdToContains = { itemId ->
+                    val position = itemId - offset
+                    position in (0 until adapter.itemCount)
+                }
             }
         }
     }
-}
 
 /**
  * Same as [fragmentAdapterProvider] but with a custom implementation of
  * [FragmentStateAdapter.getItemId] and [FragmentStateAdapter.containsItem].
  * Suitable for testing [RecyclerView.Adapter.notifyDataSetChanged].
  */
-val fragmentAdapterProviderValueId: AdapterProviderForItems = { items ->
-    { activity ->
-        fragmentAdapterProvider(items)(activity).also {
-            val adapter = it as FragmentAdapter
-            adapter.positionToItemId = { position -> items[position].getId() }
-            adapter.itemIdToContains = { itemId -> items.any { item -> item.getId() == itemId } }
+val fragmentAdapterProviderValueId =
+    AdapterProviderForItems("fragmentAdapterProviderValueId") { items ->
+        { activity ->
+            fragmentAdapterProvider.provider(items)(activity).also {
+                val adapter = it as FragmentAdapter
+                adapter.positionToItemId = { position -> items[position].getId() }
+                adapter.itemIdToContains =
+                    { itemId -> items.any { item -> item.getId() == itemId } }
+            }
         }
     }
-}
 
 /** Extracts the sole number from a [String] and converts it to a [Long] */
 private fun (String).getId(): Long {
@@ -660,9 +668,9 @@
  * [RecyclerView.Adapter.getItemId].
  * Suitable for testing [RecyclerView.Adapter.notifyDataSetChanged].mu
  */
-val viewAdapterProviderValueId: AdapterProviderForItems = { items ->
+val viewAdapterProviderValueId = AdapterProviderForItems("viewAdapterProviderValueId") { items ->
     { activity ->
-        viewAdapterProvider(items)(activity).also {
+        viewAdapterProvider.provider(items)(activity).also {
             val adapter = it as ViewAdapter
             adapter.positionToItemId = { position -> items[position].getId() }
             adapter.setHasStableIds(true)
@@ -670,7 +678,8 @@
     }
 }
 
-val viewAdapterProvider: AdapterProviderForItems = { items -> { ViewAdapter(items) } }
+val viewAdapterProvider =
+    AdapterProviderForItems("viewAdapterProvider") { items -> { ViewAdapter(items) } }
 
 fun stringSequence(pageCount: Int) = (0 until pageCount).map { it.toString() }
 
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
index e268559..045de41 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/CanScrollTest.kt
@@ -39,19 +39,19 @@
         // given
         setUpTest(ORIENTATION_HORIZONTAL).apply {
             // when no pages
-            setAdapterSync(viewAdapterProvider(stringSequence(0)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(0)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 1 page
-            setAdapterSync(viewAdapterProvider(stringSequence(1)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(1)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 2 pages
-            setAdapterSync(viewAdapterProvider(stringSequence(2)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(2)))
 
             // then can scroll right
             assertScrollRight()
@@ -84,19 +84,19 @@
         localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
         setUpTest(ORIENTATION_HORIZONTAL).apply {
             // when no pages
-            setAdapterSync(viewAdapterProvider(stringSequence(0)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(0)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 1 page
-            setAdapterSync(viewAdapterProvider(stringSequence(1)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(1)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 2 pages
-            setAdapterSync(viewAdapterProvider(stringSequence(2)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(2)))
 
             // then can scroll left
             assertScrollLeft()
@@ -126,19 +126,19 @@
         // given
         setUpTest(ORIENTATION_VERTICAL).apply {
             // when no pages
-            setAdapterSync(viewAdapterProvider(stringSequence(0)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(0)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 1 page
-            setAdapterSync(viewAdapterProvider(stringSequence(1)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(1)))
 
             // then can't scroll
             assertScrollNone()
 
             // when 2 pages
-            setAdapterSync(viewAdapterProvider(stringSequence(2)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(2)))
 
             // then can scroll down
             assertScrollDown()
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt
index 7f22960..7a30f81 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ChangeDataSetWhileScrollingTest.kt
@@ -37,7 +37,7 @@
     fun test_regression01() {
         setUpTest(orientation).apply {
             val items = listOf("49", "51").toMutableList()
-            setAdapterSync(adapterProvider(items))
+            setAdapterSync(adapterProvider.provider(items))
             assertBasicState(0, items[0])
 
             viewPager.post {
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DisableUserInputTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DisableUserInputTest.kt
index fb2abd3..97443aa 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DisableUserInputTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DisableUserInputTest.kt
@@ -61,21 +61,22 @@
     private lateinit var test: Context
     private lateinit var adapterProvider: AdapterProvider
 
-    private val touchConsumingViewAdapter: AdapterProviderForItems = { items ->
-        {
-            object : ViewAdapter(items) {
-                override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
-                    super.onBindViewHolder(holder, position)
-                    (holder.itemView as TouchConsumingTextView).consumeTouches =
+    private val touchConsumingViewAdapter =
+        AdapterProviderForItems("touchConsumingViewAdapter") { items ->
+            {
+                object : ViewAdapter(items) {
+                    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+                        super.onBindViewHolder(holder, position)
+                        (holder.itemView as TouchConsumingTextView).consumeTouches =
                             config.childViewConsumesTouches
+                    }
                 }
             }
         }
-    }
 
     override fun setUp() {
         super.setUp()
-        adapterProvider = touchConsumingViewAdapter(stringSequence(pageCount))
+        adapterProvider = touchConsumingViewAdapter.provider(stringSequence(pageCount))
         test = setUpTest(config.orientation).also {
             it.viewPager.isUserInputEnabled = false
             it.setAdapterSync(adapterProvider)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
index 0446457..5b9266c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
@@ -73,7 +73,7 @@
         assertThat(config.distanceToTargetWhenStartDrag, greaterThan(0f))
         val pageCount = max(config.startPage, config.targetPage) + 1
         test = setUpTest(config.orientation)
-        test.setAdapterSync(viewAdapterProvider(stringSequence(pageCount)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(pageCount)))
         test.viewPager.setCurrentItemSync(config.startPage, false, 2, SECONDS)
 
         var recorder = test.viewPager.addNewRecordingCallback()
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
index eb6eb42..6ee9599 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -101,7 +101,7 @@
             localeUtil.resetLocale()
             localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
         }
-        adapterProvider = viewAdapterProvider(stringSequence(pageCount))
+        adapterProvider = viewAdapterProvider.provider(stringSequence(pageCount))
         test = setUpTest(config.orientation).also {
             fakeDragger = PageSwiperFakeDrag(it.viewPager) { it.viewPager.pageSize }
             it.viewPager.isUserInputEnabled = config.enableUserInput
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
index 51f84084..71064fd 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
@@ -57,7 +57,7 @@
     fun test_swipeBetweenPages() {
         setUpTest(orientation).apply {
             val expectedValues = stringSequence(totalPages).toMutableList()
-            val adapter = adapterProvider(expectedValues.toList()) // immutable defensive copy
+            val adapter = adapterProvider.provider(expectedValues.toList()) // defensive copy
             setAdapterSync(adapter)
 
             var ix = 0
@@ -86,7 +86,7 @@
     fun test_setCurrentItem() {
         setUpTest(orientation).apply {
             val expectedValues = stringSequence(totalPages).toMutableList()
-            val adapter = adapterProvider(expectedValues.toList()) // immutable defensive copy
+            val adapter = adapterProvider.provider(expectedValues.toList()) // defensive copy
             setAdapterSync(adapter)
 
             performAssertions(expectedValues, 0)
@@ -114,7 +114,7 @@
     fun test_dataSetChange() {
         setUpTest(orientation).apply {
             val items = stringSequence(totalPages).toMutableList()
-            setAdapterSync(adapterProvider(items))
+            setAdapterSync(adapterProvider.provider(items))
             val adapter = viewPager.adapter!!
 
             performAssertions(items, 0)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ItemDecorationTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ItemDecorationTest.kt
index a46284a..ed066f6 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ItemDecorationTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/ItemDecorationTest.kt
@@ -61,7 +61,7 @@
     ) {
         // given
         setUpTest(orientation).run {
-            setAdapterSync(adapterProvider(stringSequence(3)))
+            setAdapterSync(adapterProvider.provider(stringSequence(3)))
 
             // sanity checks
             assertBasicState(0)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
index ee8c165..c0840ce 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/MutableCollectionsTest.kt
@@ -49,7 +49,7 @@
     fun test() {
         testConfig.apply {
             setUpTest(orientation).apply {
-                setAdapterSync(adapterProvider(items))
+                setAdapterSync(adapterProvider.provider(items))
 
                 verifyViewPagerContent(items) // sanity check
 
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
index de156e2..2920809 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/OffscreenPageLimitTest.kt
@@ -82,7 +82,7 @@
             test.viewPager.offscreenPageLimit = config.offscreenPageLimit
         }
         val recorder = test.viewPager.addNewRecordingCallback()
-        test.setAdapterSync(config.adapterProvider(stringSequence(pageCount)))
+        test.setAdapterSync(config.adapterProvider.provider(stringSequence(pageCount)))
         // Do not perform self check (which checks number of shown + cached fragments) in
         // this test, as that check is not valid in the presence of offscreen page limit
         test.assertBasicState(firstPage, performSelfCheck = false)
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt
index 666bbb0..82f3535 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PaddingMarginDecorationTest.kt
@@ -133,12 +133,12 @@
     }
 
     private val adapterProvider: AdapterProviderForItems get() {
-        return if (config.itemMarginPx > 0) {
-            { items -> { MarginViewAdapter(config.itemMarginPx, items) } }
-        } else {
-            { items -> { ViewAdapter(items) } }
+            return AdapterProviderForItems("adapterProvider", if (config.itemMarginPx > 0) {
+                { items -> { MarginViewAdapter(config.itemMarginPx, items) } }
+            } else {
+                { items -> { ViewAdapter(items) } }
+            })
         }
-    }
 
     class MarginViewAdapter(private val margin: Int, items: List<String>) : ViewAdapter(items) {
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@@ -186,7 +186,7 @@
 
     @Test
     fun test_pageSize() {
-        test.setAdapterSync(adapterProvider(stringSequence(1)))
+        test.setAdapterSync(adapterProvider.provider(stringSequence(1)))
 
         val f = if (viewPager.isHorizontal) fLeft + fRight else fTop + fBottom
 
@@ -232,7 +232,7 @@
      */
     @Test
     fun test_swipeBetweenPages() {
-        test.setAdapterSync(adapterProvider(stringSequence(2)))
+        test.setAdapterSync(adapterProvider.provider(stringSequence(2)))
         listOf(1, 0).forEach { targetPage ->
             // given
             val initialPage = viewPager.currentItem
@@ -292,7 +292,7 @@
         val totalPages = 2
         val edgePages = setOf(0, totalPages - 1)
 
-        test.setAdapterSync(adapterProvider(stringSequence(totalPages)))
+        test.setAdapterSync(adapterProvider.provider(stringSequence(totalPages)))
         listOf(0, 1, 1).forEach { targetPage ->
             // given
             val initialPage = viewPager.currentItem
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index cc4bb92..fe9b258 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -109,7 +109,7 @@
     @Test
     fun test_swipeBetweenPages() {
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(4)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(4)))
             listOf(1, 2, 3, 2, 1, 0).forEach { targetPage ->
                 // given
                 val initialPage = viewPager.currentItem
@@ -172,7 +172,7 @@
 
         setUpTest(config.orientation).apply {
 
-            setAdapterSync(viewAdapterProvider(stringSequence(totalPages)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(totalPages)))
             listOf(0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0).forEach { targetPage ->
                 // given
                 val initialPage = viewPager.currentItem
@@ -235,7 +235,7 @@
     fun test_peekOnAdjacentPage_next() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(3)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(3)))
             val callback = viewPager.addNewRecordingCallback()
             val latch = viewPager.addWaitForScrolledLatch(0)
 
@@ -294,7 +294,7 @@
     fun test_peekOnAdjacentPage_previous() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(3)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(3)))
 
             viewPager.setCurrentItemSync(2, false, 1, SECONDS)
 
@@ -371,7 +371,7 @@
     fun test_selectItemProgrammatically_smoothScroll() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(1000)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(1000)))
 
             // when
             listOf(6, 5, 6, 3, 10, 0, 0, 999, 999, 0).forEach { targetPage ->
@@ -412,7 +412,7 @@
     fun test_multiplePageChanges() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(10)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(10)))
             val targetPages = listOf(4, 9)
             val callback = viewPager.addNewRecordingCallback()
             val latch = viewPager.addWaitForScrolledLatch(targetPages.last(), true)
@@ -462,7 +462,7 @@
     fun test_noSmoothScroll_after_smoothScroll() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(6)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(6)))
             val targetPage = 4
             val marker = 1
             val callback = viewPager.addNewRecordingCallback()
@@ -580,7 +580,7 @@
         // given
         assertThat(targetPage, greaterThanOrEqualTo(4))
         setUpTest(config.orientation).apply {
-            val adapterProvider = viewAdapterProvider(stringSequence(5))
+            val adapterProvider = viewAdapterProvider.provider(stringSequence(5))
             setAdapterSync(adapterProvider)
             val marker = 1
             val callback = viewPager.addNewRecordingCallback()
@@ -640,7 +640,7 @@
     fun test_selectItemProgrammatically_noSmoothScroll() {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(3)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(3)))
 
             // when
             listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -672,7 +672,7 @@
     fun test_swipeReleaseSwipeBack() {
         // given
         val test = setUpTest(config.orientation)
-        test.setAdapterSync(viewAdapterProvider(stringSequence(3)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(3)))
         val currentPage = test.viewPager.currentItem
         val halfPage = test.viewPager.pageSize / 2f
         val pageSwiper = PageSwiperManual(test.viewPager)
@@ -746,7 +746,7 @@
     private fun test_selectItemProgrammatically_noCallback(smoothScroll: Boolean) {
         // given
         setUpTest(config.orientation).apply {
-            setAdapterSync(viewAdapterProvider(stringSequence(3)))
+            setAdapterSync(viewAdapterProvider.provider(stringSequence(3)))
 
             // when
             listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -788,7 +788,7 @@
     @Test
     fun test_getScrollState() {
         val test = setUpTest(config.orientation)
-        test.setAdapterSync(viewAdapterProvider(stringSequence(5)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(5)))
 
         // Test SCROLL_STATE_SETTLING
         test_getScrollState(test, SCROLL_STATE_SETTLING, 1) {
@@ -852,7 +852,7 @@
         // given
         val test = setUpTest(config.orientation)
         val recorder = test.viewPager.addNewRecordingCallback()
-        val adapterProvider = viewAdapterProvider(stringSequence(3))
+        val adapterProvider = viewAdapterProvider.provider(stringSequence(3))
         val marker = 1
 
         fun expectedEvents(page: Int): List<Event> {
@@ -901,7 +901,7 @@
     private fun test_setCurrentItem_outOfBounds(smoothScroll: Boolean) {
         val test = setUpTest(config.orientation)
         val n = 3
-        test.setAdapterSync(viewAdapterProvider(stringSequence(n)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(n)))
         val adapterCount = test.viewPager.adapter!!.itemCount
 
         listOf(-5, -1, n, n + 1, adapterCount, adapterCount + 1).forEach { targetPage ->
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt
index 8dc6458..b0504d0 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerItemAnimatorTest.kt
@@ -42,7 +42,7 @@
     @Test
     fun test() {
         setUpTest(ORIENTATION_HORIZONTAL).apply {
-            setAdapterSync(viewAdapterProviderValueId(stringSequence(5)))
+            setAdapterSync(viewAdapterProviderValueId.provider(stringSequence(5)))
             assertBasicState(0)
 
             val rv = viewPager.getChildAt(0) as RecyclerView
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
index 4d78da2..85ece6c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageTransformerTest.kt
@@ -125,7 +125,7 @@
     fun test() {
         // given
         val test = setUpTest(config.orientation)
-        test.setAdapterSync(viewAdapterProvider(stringSequence(100)))
+        test.setAdapterSync(viewAdapterProvider.provider(stringSequence(100)))
 
         // when
         config.pageList.forEach { targetPage ->
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
index 8d6159b..e45e4d9 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
@@ -138,7 +138,7 @@
         config.apply {
             // given
             setUpTest(orientation).apply {
-                setAdapterSync(viewAdapterProvider(stringSequence(totalPages)))
+                setAdapterSync(viewAdapterProvider.provider(stringSequence(totalPages)))
                 val callback = viewPager.addNewRecordingCallback()
                 var currentPage = viewPager.currentItem
 
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
index 9cacdcd..91bf776 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SwipeTest.kt
@@ -40,7 +40,7 @@
         testConfig.apply {
             setUpTest(orientation).apply {
                 val expectedValues = stringSequence(totalPages).toMutableList()
-                val adapter = adapterProvider(expectedValues.toList()) // immutable defensive copy
+                val adapter = adapterProvider.provider(expectedValues.toList()) // defensive copy
                 setAdapterSync(adapter)
                 assertBasicState(0)
 
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
index ebe5a6a..cb29e66 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/TransientStateFragmentTest.kt
@@ -45,7 +45,7 @@
     fun test_swipeBetweenPages() {
         setUpTest(orientation).apply {
             val expectedValues = stringSequence(totalPages)
-            val adapter = adapterProvider(expectedValues)
+            val adapter = adapterProvider.provider(expectedValues)
 
             val fragmentManager = activity.supportFragmentManager
 
diff --git a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index b40f192..a616e861 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -473,7 +473,6 @@
     /**
      * Default implementation works for collections that don't add, move, remove items.
      * <p>
-     * TODO(b/122670460): add lint rule
      * When overriding, also override {@link #containsItem(long)}.
      * <p>
      * If the item is not a part of the collection, return {@link RecyclerView#NO_ID}.
@@ -481,6 +480,7 @@
      * @param position Adapter position
      * @return stable item id {@link RecyclerView.Adapter#hasStableIds()}
      */
+    // TODO(b/122670460): add lint rule
     @Override
     public long getItemId(int position) {
         return position;
@@ -489,9 +489,9 @@
     /**
      * Default implementation works for collections that don't add, move, remove items.
      * <p>
-     * TODO(b/122670460): add lint rule
      * When overriding, also override {@link #getItemId(int)}
      */
+    // TODO(b/122670460): add lint rule
     public boolean containsItem(long itemId) {
         return itemId >= 0 && itemId < getItemCount();
     }
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index e69087b..32bdc64 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -582,8 +582,6 @@
      * the current item and the specified item. Silently ignored if the adapter is not set or
      * empty. Clamps item to the bounds of the adapter.
      *
-     * TODO(b/123069219): verify first layout behavior
-     *
      * @param item Item index to select
      */
     public void setCurrentItem(int item) {
diff --git a/work/workmanager-benchmark/build.gradle b/work/workmanager-benchmark/build.gradle
index 8dd3ba4..4c74f21 100644
--- a/work/workmanager-benchmark/build.gradle
+++ b/work/workmanager-benchmark/build.gradle
@@ -32,7 +32,7 @@
 dependencies {
     androidTestImplementation(project(':work:work-runtime-ktx'))
     androidTestImplementation(project(":benchmark:benchmark-junit4"))
-    androidTestImplementation("androidx.room:room-runtime:2.2.2")
+    androidTestImplementation("androidx.room:room-runtime:2.2.3")
     androidTestImplementation(JUNIT)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
     androidTestImplementation(ANDROIDX_TEST_CORE)
diff --git a/work/workmanager-gcm/build.gradle b/work/workmanager-gcm/build.gradle
index f960dec..e7f4a95 100644
--- a/work/workmanager-gcm/build.gradle
+++ b/work/workmanager-gcm/build.gradle
@@ -51,9 +51,9 @@
         implementation(project(":room:room-runtime"))
         androidTestImplementation(project(":room:room-testing"))
     } else {
-        annotationProcessor("androidx.room:room-compiler:2.2.2")
-        implementation("androidx.room:room-runtime:2.2.2")
-        androidTestImplementation("androidx.room:room-testing:2.2.2")
+        annotationProcessor("androidx.room:room-compiler:2.2.3")
+        implementation("androidx.room:room-runtime:2.2.3")
+        androidTestImplementation("androidx.room:room-testing:2.2.3")
     }
 
     androidTestImplementation(project(":work:work-runtime-ktx"))
diff --git a/work/workmanager-ktx/build.gradle b/work/workmanager-ktx/build.gradle
index e515cec..25ad0d8c 100644
--- a/work/workmanager-ktx/build.gradle
+++ b/work/workmanager-ktx/build.gradle
@@ -53,7 +53,7 @@
     androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
     androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
-    androidTestImplementation("androidx.room:room-testing:2.2.2")
+    androidTestImplementation("androidx.room:room-testing:2.2.3")
     testImplementation(JUNIT)
 }
 
diff --git a/work/workmanager-testing/build.gradle b/work/workmanager-testing/build.gradle
index 84b62ad..42f84b7 100644
--- a/work/workmanager-testing/build.gradle
+++ b/work/workmanager-testing/build.gradle
@@ -38,7 +38,7 @@
 dependencies {
     api(project(':work:work-runtime-ktx'))
     implementation("androidx.lifecycle:lifecycle-livedata-core:2.1.0")
-    implementation("androidx.room:room-runtime:2.2.2")
+    implementation("androidx.room:room-runtime:2.2.3")
 
     androidTestImplementation("androidx.arch.core:core-testing:2.1.0")
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index 1c15e53..48a698e 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -62,9 +62,11 @@
 }
 
 dependencies {
-    annotationProcessor("androidx.room:room-compiler:2.2.2")
-    implementation("androidx.room:room-runtime:2.2.2")
-    androidTestImplementation("androidx.room:room-testing:2.2.2")
+    annotationProcessor("androidx.room:room-compiler:2.2.3")
+    implementation("androidx.room:room-runtime:2.2.3")
+    androidTestImplementation("androidx.room:room-testing:2.2.3")
+    implementation("androidx.sqlite:sqlite:2.1.0-rc01")
+    implementation("androidx.sqlite:sqlite-framework:2.1.0-rc01")
     api(GUAVA_LISTENABLE_FUTURE)
     api("androidx.lifecycle:lifecycle-livedata:2.1.0")
     implementation("androidx.core:core:1.1.0")
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
index aa4c086..83a9ff9 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -36,6 +36,8 @@
 import androidx.room.RoomDatabase;
 import androidx.room.TypeConverters;
 import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
 import androidx.work.Data;
 import androidx.work.impl.model.Dependency;
 import androidx.work.impl.model.DependencyDao;
@@ -100,7 +102,7 @@
      */
     @NonNull
     public static WorkDatabase create(
-            @NonNull Context context,
+            @NonNull final Context context,
             @NonNull Executor queryExecutor,
             boolean useTestDatabase) {
         RoomDatabase.Builder<WorkDatabase> builder;
@@ -108,8 +110,23 @@
             builder = Room.inMemoryDatabaseBuilder(context, WorkDatabase.class)
                     .allowMainThreadQueries();
         } else {
-            String path = WorkDatabasePathHelper.getDatabasePath(context).getPath();
-            builder = Room.databaseBuilder(context, WorkDatabase.class, path);
+            String name = WorkDatabasePathHelper.getWorkDatabaseName();
+            builder = Room.databaseBuilder(context, WorkDatabase.class, name);
+            builder.openHelperFactory(new SupportSQLiteOpenHelper.Factory() {
+                @NonNull
+                @Override
+                public SupportSQLiteOpenHelper create(
+                        @NonNull SupportSQLiteOpenHelper.Configuration configuration) {
+                    SupportSQLiteOpenHelper.Configuration.Builder configBuilder =
+                            SupportSQLiteOpenHelper.Configuration.builder(context);
+                    configBuilder.name(configuration.name)
+                            .callback(configuration.callback)
+                            .noBackupDirectory(true);
+                    FrameworkSQLiteOpenHelperFactory factory =
+                            new FrameworkSQLiteOpenHelperFactory();
+                    return factory.create(configBuilder.build());
+                }
+            });
         }
 
         return builder.setQueryExecutor(queryExecutor)
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabasePathHelper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabasePathHelper.java
index aafcb32..0b4bf2b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabasePathHelper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabasePathHelper.java
@@ -47,26 +47,11 @@
     private static final String[] DATABASE_EXTRA_FILES = new String[]{"-journal", "-shm", "-wal"};
 
     /**
-     * @param context The application {@link Context}
-     * @return The database path before migration to the no-backup directory.
+     * @return The name of the database.
      */
     @NonNull
-    public static File getDefaultDatabasePath(@NonNull Context context) {
-        return context.getDatabasePath(WORK_DATABASE_NAME);
-    }
-
-    /**
-     * @param context The application {@link Context}
-     * @return The the migrated database path.
-     */
-    @NonNull
-    public static File getDatabasePath(@NonNull Context context) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            // No notion of a backup directory exists.
-            return getDefaultDatabasePath(context);
-        } else {
-            return getNoBackupPath(context, WORK_DATABASE_NAME);
-        }
+    public static String getWorkDatabaseName() {
+        return WORK_DATABASE_NAME;
     }
 
     /**
@@ -123,6 +108,31 @@
     }
 
     /**
+     * @param context The application {@link Context}
+     * @return The database path before migration to the no-backup directory.
+     */
+    @NonNull
+    @VisibleForTesting
+    public static File getDefaultDatabasePath(@NonNull Context context) {
+        return context.getDatabasePath(WORK_DATABASE_NAME);
+    }
+
+    /**
+     * @param context The application {@link Context}
+     * @return The the migrated database path.
+     */
+    @NonNull
+    @VisibleForTesting
+    public static File getDatabasePath(@NonNull Context context) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            // No notion of a backup directory exists.
+            return getDefaultDatabasePath(context);
+        } else {
+            return getNoBackupPath(context, WORK_DATABASE_NAME);
+        }
+    }
+
+    /**
      * Return the path for a {@link File} path in the {@link Context#getNoBackupFilesDir()}
      * identified by the {@link String} fragment.
      *