BottomSheetDialog handles setCancelable

BottomSheetDialog now properly reacts to the standard Dialog API
setCancelable and setCanceledOnTouchOutside.

Bug: 29628657
Change-Id: I55dee42c4552630c678075150d36bf5671c0eea5
diff --git a/design/src/android/support/design/widget/BottomSheetDialog.java b/design/src/android/support/design/widget/BottomSheetDialog.java
index 55b5cbe..0473e54 100644
--- a/design/src/android/support/design/widget/BottomSheetDialog.java
+++ b/design/src/android/support/design/widget/BottomSheetDialog.java
@@ -17,6 +17,7 @@
 package android.support.design.widget;
 
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.LayoutRes;
@@ -35,6 +36,12 @@
  */
 public class BottomSheetDialog extends AppCompatDialog {
 
+    private BottomSheetBehavior<FrameLayout> mBehavior;
+
+    private boolean mCancelable = true;
+    private boolean mCanceledOnTouchOutside = true;
+    private boolean mCanceledOnTouchOutsideSet;
+
     public BottomSheetDialog(@NonNull Context context) {
         this(context, 0);
     }
@@ -50,6 +57,7 @@
             OnCancelListener cancelListener) {
         super(context, cancelable, cancelListener);
         supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
+        mCancelable = cancelable;
     }
 
     @Override
@@ -74,6 +82,27 @@
         super.setContentView(wrapInBottomSheet(0, view, params));
     }
 
+    @Override
+    public void setCancelable(boolean cancelable) {
+        super.setCancelable(cancelable);
+        if (mCancelable != cancelable) {
+            mCancelable = cancelable;
+            if (mBehavior != null) {
+                mBehavior.setHideable(cancelable);
+            }
+        }
+    }
+
+    @Override
+    public void setCanceledOnTouchOutside(boolean cancel) {
+        super.setCanceledOnTouchOutside(cancel);
+        if (cancel && !mCancelable) {
+            mCancelable = true;
+        }
+        mCanceledOnTouchOutside = cancel;
+        mCanceledOnTouchOutsideSet = true;
+    }
+
     private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
         final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
                 R.layout.design_bottom_sheet_dialog, null);
@@ -81,38 +110,39 @@
             view = getLayoutInflater().inflate(layoutResId, coordinator, false);
         }
         FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
-        BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
+        mBehavior = BottomSheetBehavior.from(bottomSheet);
+        mBehavior.setBottomSheetCallback(mBottomSheetCallback);
+        mBehavior.setHideable(mCancelable);
         if (params == null) {
             bottomSheet.addView(view);
         } else {
             bottomSheet.addView(view, params);
         }
         // We treat the CoordinatorLayout as outside the dialog though it is technically inside
-        if (shouldWindowCloseOnTouchOutside()) {
-            coordinator.findViewById(R.id.touch_outside).setOnClickListener(
-                    new View.OnClickListener() {
-                        @Override
-                        public void onClick(View view) {
-                            if (isShowing()) {
-                                cancel();
-                            }
-                        }
-                    });
-        }
+        coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
+                    cancel();
+                }
+            }
+        });
         return coordinator;
     }
 
     private boolean shouldWindowCloseOnTouchOutside() {
-        if (Build.VERSION.SDK_INT < 11) {
-            return true;
+        if (!mCanceledOnTouchOutsideSet) {
+            if (Build.VERSION.SDK_INT < 11) {
+                mCanceledOnTouchOutside = true;
+            } else {
+                TypedArray a = getContext().obtainStyledAttributes(
+                        new int[]{android.R.attr.windowCloseOnTouchOutside});
+                mCanceledOnTouchOutside = a.getBoolean(0, true);
+                a.recycle();
+            }
+            mCanceledOnTouchOutsideSet = true;
         }
-        TypedValue value = new TypedValue();
-        //noinspection SimplifiableIfStatement
-        if (getContext().getTheme()
-                .resolveAttribute(android.R.attr.windowCloseOnTouchOutside, value, true)) {
-            return value.data != 0;
-        }
-        return false;
+        return mCanceledOnTouchOutside;
     }
 
     private static int getThemeResId(Context context, int themeId) {
diff --git a/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java b/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java
index 54a71f0..4223b44 100644
--- a/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java
+++ b/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java
@@ -21,8 +21,6 @@
 
 import android.content.Context;
 import android.support.design.test.R;
-import android.support.design.widget.BottomSheetBehavior;
-import android.support.design.widget.BottomSheetDialog;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.espresso.Espresso;
 import android.support.test.espresso.UiController;
@@ -54,20 +52,14 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                Context context = mActivityTestRule.getActivity();
-                mDialog = new BottomSheetDialog(context);
-                AppCompatTextView text = new AppCompatTextView(context);
-                StringBuilder builder = new StringBuilder();
-                builder.append("It is fine today. ");
-                text.setText(builder);
-                mDialog.setContentView(text);
-                mDialog.show();
+                showDialog();
                 // Confirms that the dialog is shown
                 assertThat(mDialog.isShowing(), is(true));
                 FrameLayout bottomSheet = (FrameLayout) mDialog
                         .findViewById(R.id.design_bottom_sheet);
                 assertThat(bottomSheet, is(notNullValue()));
                 BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+                assertThat(behavior.isHideable(), is(true));
                 assertThat(behavior, is(notNullValue()));
                 // Modal bottom sheets have auto peek height by default.
                 assertThat(behavior.getPeekHeight(), is(BottomSheetBehavior.PEEK_HEIGHT_AUTO));
@@ -91,14 +83,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                Context context = mActivityTestRule.getActivity();
-                mDialog = new BottomSheetDialog(context);
-                AppCompatTextView text = new AppCompatTextView(context);
-                StringBuilder builder = new StringBuilder();
-                builder.append("It is fine today. ");
-                text.setText(builder);
-                mDialog.setContentView(text);
-                mDialog.show();
+                showDialog();
             }
         });
         // This ensures that the views are laid out before assertions below
@@ -124,6 +109,43 @@
         });
     }
 
+    @Test
+    public void testNonCancelableDialog() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                showDialog();
+                mDialog.setCancelable(false);
+            }
+        });
+        // Click outside the bottom sheet
+        Espresso.onView(ViewMatchers.withId(R.id.touch_outside))
+                .perform(ViewActions.click());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                FrameLayout bottomSheet = (FrameLayout) mDialog
+                        .findViewById(R.id.design_bottom_sheet);
+                BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+                assertThat(behavior.isHideable(), is(false));
+                assertThat(mDialog.isShowing(), is(true));
+                mDialog.cancel();
+                assertThat(mDialog.isShowing(), is(false));
+            }
+        });
+    }
+
+    private void showDialog() {
+        Context context = mActivityTestRule.getActivity();
+        mDialog = new BottomSheetDialog(context);
+        AppCompatTextView text = new AppCompatTextView(context);
+        StringBuilder builder = new StringBuilder();
+        builder.append("It is fine today. ");
+        text.setText(builder);
+        mDialog.setContentView(text);
+        mDialog.show();
+    }
+
     private static ViewAction setTallPeekHeight() {
         return new ViewAction() {
             @Override