Fix TB lose focus when navigating recyclerview

 - Issue history: b/37088814 and previous workaround: ag/11747784
 - Since the previous workaround no longer works, apply new scroll
 behavior to the recyclerview when TB navigating, make sure the focusing item's
 previous/next item totally visible, then TB won't lose focus.
 video: https://drive.google.com/file/d/1KJQT9cAmm9G5yZGDZZTNnpezDbT_Rh31/view?usp=sharing

 Bug: 157007291
 Test: manually, enable TB and test the result

Change-Id: I1c4c8416d7b1849cf705db931bcd9c6e5c354719
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index a532e40..69a45a9 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -26,12 +26,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
@@ -154,17 +156,15 @@
      * Initializes the UI for the options passed in the constructor of this class.
      */
     public void initOptions(final CustomizationManager<T> manager) {
+        mContainer.setAccessibilityDelegateCompat(
+                new OptionSelectorAccessibilityDelegate(mContainer));
+
         mAdapter = new RecyclerView.Adapter<TileViewHolder>() {
             @Override
             public int getItemViewType(int position) {
                 return mOptions.get(position).getLayoutResId();
             }
 
-            @Override
-            public long getItemId(int position) {
-                return position;
-            }
-
             @NonNull
             @Override
             public TileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -232,10 +232,6 @@
                 LinearLayoutManager.HORIZONTAL, false));
         Resources res = mContainer.getContext().getResources();
 
-        // A workaround from b/37088814, fix TalkBack will lose focus when receive notify*Changed()
-        mAdapter.setHasStableIds(true);
-        mContainer.setItemAnimator(null);
-
         mContainer.setAdapter(mAdapter);
 
         // Measure RecyclerView to get to the total amount of space used by all options.
@@ -340,4 +336,40 @@
             }
         }
     }
+
+    private class OptionSelectorAccessibilityDelegate extends RecyclerViewAccessibilityDelegate {
+
+        OptionSelectorAccessibilityDelegate(RecyclerView recyclerView) {
+            super(recyclerView);
+        }
+
+        @Override
+        public boolean onRequestSendAccessibilityEvent(
+                ViewGroup host, View child, AccessibilityEvent event) {
+
+            // Apply this workaround to horizontal recyclerview only,
+            // since the symptom is TalkBack will lose focus when navigating horizontal list items.
+            if (mContainer.getLayoutManager() != null
+                    && mContainer.getLayoutManager().canScrollHorizontally()
+                    && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+                int itemPos = mContainer.getChildLayoutPosition(child);
+                int itemWidth = mContainer.getContext().getResources()
+                        .getDimensionPixelOffset(R.dimen.option_tile_width);
+                int itemMarginHorizontal = mContainer.getContext().getResources()
+                        .getDimensionPixelOffset(R.dimen.option_tile_margin_horizontal) * 2;
+                int scrollOffset = itemWidth + itemMarginHorizontal;
+
+                // Make focusing item's previous/next item totally visible when changing focus,
+                // ensure TalkBack won't lose focus when recyclerview scrolling.
+                if (itemPos >= ((LinearLayoutManager) mContainer.getLayoutManager())
+                        .findLastCompletelyVisibleItemPosition()) {
+                    mContainer.scrollBy(scrollOffset, 0);
+                } else if (itemPos <= ((LinearLayoutManager) mContainer.getLayoutManager())
+                        .findFirstCompletelyVisibleItemPosition() && itemPos != 0) {
+                    mContainer.scrollBy(-scrollOffset, 0);
+                }
+            }
+            return super.onRequestSendAccessibilityEvent(host, child, event);
+        }
+    }
 }