Add a custom Tab selection effect for Viewpager swiping

Mock: https://docs.google.com/presentation/d/1o1CGLwwpega6NlFVvkgIEnxiYsn2qSnTX4axCEXBkVg/edit?ts=60c12e95&resourcekey=0-gwPJERFbm4jFJnCk8ZG0Vg#slide=id.ge040080db2_0_54

Before: https://drive.google.com/file/d/1L-7g_8PiSEKvquuJvntCVoSZR7-CYxI9/view?usp=sharing
After: https://drive.google.com/file/d/1_iFygqxfVc_tO4upX0RF0solwl_3-IYG/view?usp=sharing

Bug: 191388121
Test: Manually
Change-Id: Ieb75b69196283cbe45879230d895ebf02ec5cc71
diff --git a/Android.bp b/Android.bp
index 871e193..7e55969 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,11 +27,12 @@
     static_libs: [
         "androidx.appcompat_appcompat",
         "androidx.cardview_cardview",
+        "androidx-constraintlayout_constraintlayout",
         "androidx.exifinterface_exifinterface",
         "androidx.lifecycle_lifecycle-runtime-ktx",
         "androidx.recyclerview_recyclerview",
         "androidx.slice_slice-view",
-        "androidx-constraintlayout_constraintlayout",
+        "androidx.viewpager2_viewpager2",
         "com.google.android.material_material",
         "glide-prebuilt",
         "kotlinx-coroutines-android",
diff --git a/src/com/android/wallpaper/widget/SeparatedTabLayout.java b/src/com/android/wallpaper/widget/SeparatedTabLayout.java
index 793403f..8642485 100644
--- a/src/com/android/wallpaper/widget/SeparatedTabLayout.java
+++ b/src/com/android/wallpaper/widget/SeparatedTabLayout.java
@@ -1,17 +1,31 @@
 package com.android.wallpaper.widget;
 
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING;
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE;
+import static androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING;
+
 import android.content.Context;
 import android.util.AttributeSet;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.viewpager2.widget.ViewPager2;
+import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback;
 
 import com.android.wallpaper.R;
 
 import com.google.android.material.tabs.TabLayout;
 
-/** Custom {@link TabLayout} for separated tabs. */
-public class SeparatedTabLayout extends TabLayout {
+import java.lang.ref.WeakReference;
+
+/**
+ * Custom {@link TabLayout} for separated tabs.
+ *
+ * <p>Don't use {@code TabLayoutMediator} for the tab layout, which binds the tab scrolling
+ * animation that is unwanted for the separated tab design. Uses {@link
+ * SeparatedTabLayout#setViewPager} to bind a {@link ViewPager2} to use the proper tab effect.
+ */
+public final class SeparatedTabLayout extends TabLayout {
 
     public SeparatedTabLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -24,4 +38,81 @@
         tab.view.setBackgroundResource(R.drawable.separated_tabs_ripple_mask);
         return tab;
     }
+
+    /** Binds the given {@code viewPager} to the {@link SeparatedTabLayout}. */
+    public void setViewPager(ViewPager2 viewPager) {
+        viewPager.registerOnPageChangeCallback(new SeparatedTabLayoutOnPageChangeCallback(this));
+        addOnTabSelectedListener(new SeparatedTabLayoutOnTabSelectedListener(viewPager));
+    }
+
+    private static class SeparatedTabLayoutOnTabSelectedListener implements
+            OnTabSelectedListener {
+        private final WeakReference<ViewPager2> mViewPagerRef;
+
+        private SeparatedTabLayoutOnTabSelectedListener(ViewPager2 viewPager) {
+            mViewPagerRef = new WeakReference<>(viewPager);
+        }
+
+        @Override
+        public void onTabSelected(Tab tab) {
+            ViewPager2 viewPager = mViewPagerRef.get();
+            if (viewPager != null && viewPager.getCurrentItem() != tab.getPosition()) {
+                viewPager.setCurrentItem(tab.getPosition());
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab) {}
+
+        @Override
+        public void onTabReselected(Tab tab) {}
+    }
+
+    private static class SeparatedTabLayoutOnPageChangeCallback extends OnPageChangeCallback {
+        private final WeakReference<TabLayout> mTabLayoutRef;
+        private int mPreviousScrollState = SCROLL_STATE_IDLE;
+        private int mScrollState = SCROLL_STATE_IDLE;
+
+        private SeparatedTabLayoutOnPageChangeCallback(TabLayout tabLayout) {
+            mTabLayoutRef = new WeakReference<>(tabLayout);
+        }
+
+        @Override
+        public void onPageSelected(final int position) {
+            if (isUserDragging()) {
+                // Don't update tab position here, wait for page scrolling done to update the tabs.
+                return;
+            }
+            // ViewPager2#setCurrentItem would run into here.
+            updateTabPositionIfNeeded(position);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(final int state) {
+            mPreviousScrollState = mScrollState;
+            mScrollState = state;
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+            // Update the tab when the scrolling page is full displayed and is user dragging case.
+            if (positionOffset == 0f && isUserDragging()) {
+                updateTabPositionIfNeeded(position);
+            }
+        }
+
+        private boolean isUserDragging() {
+            return mPreviousScrollState == SCROLL_STATE_DRAGGING
+                    && mScrollState == SCROLL_STATE_SETTLING;
+        }
+
+        private void updateTabPositionIfNeeded(int position) {
+            TabLayout tabLayout = mTabLayoutRef.get();
+            if (tabLayout != null
+                    && tabLayout.getSelectedTabPosition() != position
+                    && position < tabLayout.getTabCount()) {
+                tabLayout.selectTab(tabLayout.getTabAt(position), /* updateIndicator= */ true);
+            }
+        }
+    }
 }