Fully expand the bottom sheet when touch exploration is enabled.

Bug: 80546794
Test: HistoryItemActionBottomSheetTest + Manual testing
PiperOrigin-RevId: 200304666
Change-Id: Ie9d6b38b96e35c051469710ec2bae8fa2a1a17ba
diff --git a/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java b/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
index f5cdc4d..9653f55 100644
--- a/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
+++ b/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
@@ -28,13 +28,16 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.dialer.common.Assert;
 import com.android.dialer.theme.base.ThemeComponent;
 import com.android.dialer.widget.ContactPhotoView;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 
 /**
@@ -49,6 +52,42 @@
   private final List<HistoryItemActionModule> modules;
   private final HistoryItemBottomSheetHeaderInfo historyItemBottomSheetHeaderInfo;
 
+  /**
+   * An {@link OnPreDrawListener} that sets the contact layout's elevation if
+   *
+   * <ul>
+   *   <li>the bottom sheet can expand to full screen, and
+   *   <li>the bottom sheet is fully expanded.
+   * </ul>
+   *
+   * <p>The reason an {@link OnPreDrawListener} instead of a {@link BottomSheetCallback} is used to
+   * handle this is that the initial state of the bottom sheet will be STATE_EXPANDED when the touch
+   * exploration (e.g., TalkBack) is enabled and {@link BottomSheetCallback} won't be triggered in
+   * this case. See {@link #setupBottomSheetBehavior()} for details.
+   */
+  private final OnPreDrawListener onPreDrawListenerForContactLayout =
+      () -> {
+        View contactLayout = findViewById(R.id.contact_layout_root);
+        View background = findViewById(android.support.design.R.id.touch_outside);
+        View bottomSheet = findViewById(android.support.design.R.id.design_bottom_sheet);
+
+        BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheet);
+
+        // If the height of the background is equal to that of the bottom sheet, the bottom sheet
+        // *can* be expanded to full screen.
+        contactLayout.setElevation(
+            background.getHeight() == bottomSheet.getHeight()
+                    && behavior.getState() == BottomSheetBehavior.STATE_EXPANDED
+                ? getContext()
+                    .getResources()
+                    .getDimensionPixelSize(R.dimen.contact_actions_header_elevation)
+                : 0);
+
+        return true; // Return true to proceed with the current drawing pass.
+      };
+
+  private LinearLayout contactLayout;
+
   private HistoryItemActionBottomSheet(
       Context context,
       HistoryItemBottomSheetHeaderInfo historyItemBottomSheetHeaderInfo,
@@ -71,6 +110,10 @@
 
   @Override
   protected void onCreate(Bundle bundle) {
+    contactLayout = Assert.isNotNull(findViewById(R.id.contact_layout_root));
+
+    initBottomSheetState();
+    setupBottomSheetBehavior();
     setupWindow();
     setupContactLayout();
 
@@ -84,7 +127,68 @@
     }
   }
 
-  // Overrwrites the window size since Dialog's don't match parent.
+  @Override
+  public void onAttachedToWindow() {
+    super.onAttachedToWindow();
+    contactLayout.getViewTreeObserver().addOnPreDrawListener(onPreDrawListenerForContactLayout);
+  }
+
+  @Override
+  public void onDetachedFromWindow() {
+    super.onDetachedFromWindow();
+    contactLayout.getViewTreeObserver().removeOnPreDrawListener(onPreDrawListenerForContactLayout);
+  }
+
+  private void initBottomSheetState() {
+    // If the touch exploration in the system (e.g., TalkBack) is enabled, the bottom sheet should
+    // be fully expanded because sometimes services like TalkBack won't read all items when the
+    // bottom sheet is not fully expanded.
+    if (isTouchExplorationEnabled()) {
+      BottomSheetBehavior<View> behavior =
+          BottomSheetBehavior.from(findViewById(android.support.design.R.id.design_bottom_sheet));
+      behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+    }
+  }
+
+  /**
+   * Configures the bottom sheet behavior when its state changes.
+   *
+   * <p>If the touch exploration in the system (e.g., TalkBack) is enabled, the bottom sheet will be
+   * canceled if it is in a final state other than {@link BottomSheetBehavior#STATE_EXPANDED}. This
+   * is because sometimes services like TalkBack won't read all items when the bottom sheet is not
+   * fully expanded.
+   *
+   * <p>If the touch exploration is disabled, cancel the bottom sheet when it is in {@link
+   * BottomSheetBehavior#STATE_HIDDEN}.
+   */
+  private void setupBottomSheetBehavior() {
+    BottomSheetBehavior<View> behavior =
+        BottomSheetBehavior.from(findViewById(android.support.design.R.id.design_bottom_sheet));
+    behavior.setBottomSheetCallback(
+        new BottomSheetCallback() {
+          @Override
+          public void onStateChanged(@NonNull View bottomSheet, int newState) {
+            ImmutableSet<Integer> statesToCancelBottomSheet =
+                isTouchExplorationEnabled()
+                    ? ImmutableSet.of(
+                        BottomSheetBehavior.STATE_COLLAPSED,
+                        BottomSheetBehavior.STATE_HIDDEN,
+                        BottomSheetBehavior.STATE_HALF_EXPANDED)
+                    : ImmutableSet.of(BottomSheetBehavior.STATE_HIDDEN);
+
+            if (statesToCancelBottomSheet.contains(newState)) {
+              cancel();
+            }
+
+            // TODO(calderwoodra): set the status bar color when expanded, else translucent
+          }
+
+          @Override
+          public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
+        });
+  }
+
+  // Overwrites the window size since it doesn't match the parent.
   private void setupWindow() {
     Window window = getWindow();
     if (window == null) {
@@ -97,13 +201,11 @@
   }
 
   private void setupContactLayout() {
-    View contactView = Assert.isNotNull(findViewById(R.id.contact_layout_root));
-
-    ContactPhotoView contactPhotoView = contactView.findViewById(R.id.contact_photo_view);
+    ContactPhotoView contactPhotoView = contactLayout.findViewById(R.id.contact_photo_view);
     contactPhotoView.setPhoto(historyItemBottomSheetHeaderInfo.getPhotoInfo());
 
-    TextView primaryTextView = contactView.findViewById(R.id.primary_text);
-    TextView secondaryTextView = contactView.findViewById(R.id.secondary_text);
+    TextView primaryTextView = contactLayout.findViewById(R.id.primary_text);
+    TextView secondaryTextView = contactLayout.findViewById(R.id.secondary_text);
 
     primaryTextView.setText(historyItemBottomSheetHeaderInfo.getPrimaryText());
     if (!TextUtils.isEmpty(historyItemBottomSheetHeaderInfo.getSecondaryText())) {
@@ -112,35 +214,6 @@
       secondaryTextView.setVisibility(View.GONE);
       secondaryTextView.setText(null);
     }
-
-    View background = findViewById(android.support.design.R.id.touch_outside);
-    BottomSheetBehavior behavior =
-        BottomSheetBehavior.from(findViewById(android.support.design.R.id.design_bottom_sheet));
-    behavior.setBottomSheetCallback(
-        new BottomSheetCallback() {
-          @Override
-          public void onStateChanged(@NonNull View bottomSheet, int newState) {
-            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
-              cancel();
-              return;
-            }
-
-            // If the bottomsheet can expand to full screen, set the header's elevation when it's
-            // fully expanded.
-            if (background.getHeight() == bottomSheet.getHeight()) {
-              contactView.setElevation(
-                  newState == BottomSheetBehavior.STATE_EXPANDED
-                      ? getContext()
-                          .getResources()
-                          .getDimensionPixelSize(R.dimen.contact_actions_header_elevation)
-                      : 0);
-              // TODO(calderwoodra): set the status bar color when expanded, else translucent
-            }
-          }
-
-          @Override
-          public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
-        });
   }
 
   private View getDividerView(ViewGroup container) {
@@ -170,4 +243,11 @@
       dismiss();
     }
   }
+
+  private boolean isTouchExplorationEnabled() {
+    AccessibilityManager accessibilityManager =
+        getContext().getSystemService(AccessibilityManager.class);
+
+    return accessibilityManager.isTouchExplorationEnabled();
+  }
 }