content: package com.afollestad.materialdialogs.internal;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.Paint;
  import android.os.Build;
  import androidx.annotation.Nullable;
  import androidx.recyclerview.widget.RecyclerView;
  import android.util.AttributeSet;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewTreeObserver;
  import android.webkit.WebView;
  import android.widget.AdapterView;
  import android.widget.ScrollView;
  import com.afollestad.materialdialogs.GravityEnum;
  import com.afollestad.materialdialogs.R;
  import com.afollestad.materialdialogs.StackingBehavior;
  import com.afollestad.materialdialogs.util.DialogUtils;
  
  /**
   * @author Kevin Barry (teslacoil) 4/02/2015 This is the top level view for all MaterialDialogs It
   *     handles the layout of: titleFrame (md_stub_titleframe) content (text, custom view, listview,
   *     etc) buttonDefault... (either stacked or horizontal)
   */
  public class MDRootLayout extends ViewGroup {
  
    private static final int INDEX_NEUTRAL = 0;
    private static final int INDEX_NEGATIVE = 1;
    private static final int INDEX_POSITIVE = 2;
    private final MDButton[] buttons = new MDButton[3];
    private int maxHeight;
    private View titleBar;
    private View content;
    private boolean drawTopDivider = false;
    private boolean drawBottomDivider = false;
    private StackingBehavior stackBehavior = StackingBehavior.ADAPTIVE;
    private boolean isStacked = false;
    private boolean useFullPadding = true;
    private boolean reducePaddingNoTitleNoButtons;
    private boolean noTitleNoPadding;
  
    private int noTitlePaddingFull;
    private int buttonPaddingFull;
    private int buttonBarHeight;
  
    private GravityEnum buttonGravity = GravityEnum.START;
  
    /* Margin from dialog frame to first button */
    private int buttonHorizontalEdgeMargin;
  
    private Paint dividerPaint;
  
    private ViewTreeObserver.OnScrollChangedListener topOnScrollChangedListener;
    private ViewTreeObserver.OnScrollChangedListener bottomOnScrollChangedListener;
    private int dividerWidth;
  
    public MDRootLayout(Context context) {
      super(context);
      init(context, null, 0);
    }
  
    public MDRootLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
      init(context, attrs, 0);
    }
  
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public MDRootLayout(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      init(context, attrs, defStyleAttr);
    }
  
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public MDRootLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      super(context, attrs, defStyleAttr, defStyleRes);
      init(context, attrs, defStyleAttr);
    }
  
    private static boolean isVisible(View v) {
      boolean visible = v != null && v.getVisibility() != View.GONE;
      if (visible && v instanceof MDButton) {
        visible = ((MDButton) v).getText().toString().trim().length() > 0;
      }
      return visible;
    }
  
    public static boolean canRecyclerViewScroll(RecyclerView view) {
      return view != null
          && view.getLayoutManager() != null
          && view.getLayoutManager().canScrollVertically();
    }
  
    private static boolean canScrollViewScroll(ScrollView sv) {
      if (sv.getChildCount() == 0) {
        return false;
      }
      final int childHeight = sv.getChildAt(0).getMeasuredHeight();
      return sv.getMeasuredHeight() - sv.getPaddingTop() - sv.getPaddingBottom() < childHeight;
    }
  
    private static boolean canWebViewScroll(WebView view) {
      //noinspection deprecation
      return view.getMeasuredHeight() < view.getContentHeight() * view.getScale();
    }
  
    private static boolean canAdapterViewScroll(AdapterView lv) {
      /* Force it to layout it's children */
      if (lv.getLastVisiblePosition() == -1) {
        return false;
      }
  
      /* We can scroll if the first or last item is not visible */
      boolean firstItemVisible = lv.getFirstVisiblePosition() == 0;
      boolean lastItemVisible = lv.getLastVisiblePosition() == lv.getCount() - 1;
  
      if (firstItemVisible && lastItemVisible && lv.getChildCount() > 0) {
        /* Or the first item's top is above or own top */
        if (lv.getChildAt(0).getTop() < lv.getPaddingTop()) {
          return true;
        }
        /* or the last item's bottom is beyond our own bottom */
        return lv.getChildAt(lv.getChildCount() - 1).getBottom()
            > lv.getHeight() - lv.getPaddingBottom();
      }
  
      return true;
    }
  
    /**
     * Find the view touching the bottom of this ViewGroup. Non visible children are ignored, however
     * getChildDrawingOrder is not taking into account for simplicity and because it behaves
     * inconsistently across platform versions.
     *
     * @return View touching the bottom of this ViewGroup or null
     */
    @Nullable
    private static View getBottomView(ViewGroup viewGroup) {
      if (viewGroup == null || viewGroup.getChildCount() == 0) {
        return null;
      }
      View bottomView = null;
      for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
        View child = viewGroup.getChildAt(i);
        if (child.getVisibility() == View.VISIBLE
            && child.getBottom() == viewGroup.getMeasuredHeight()) {
          bottomView = child;
          break;
        }
      }
      return bottomView;
    }
  
    @Nullable
    private static View getTopView(ViewGroup viewGroup) {
      if (viewGroup == null || viewGroup.getChildCount() == 0) {
        return null;
      }
      View topView = null;
      for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
        View child = viewGroup.getChildAt(i);
        if (child.getVisibility() == View.VISIBLE && child.getTop() == 0) {
          topView = child;
          break;
        }
      }
      return topView;
    }
  
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
      Resources r = context.getResources();
  
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MDRootLayout, defStyleAttr, 0);
      reducePaddingNoTitleNoButtons =
          a.getBoolean(R.styleable.MDRootLayout_md_reduce_padding_no_title_no_buttons, true);
      a.recycle();
  
      noTitlePaddingFull = r.getDimensionPixelSize(R.dimen.md_notitle_vertical_padding);
      buttonPaddingFull = r.getDimensionPixelSize(R.dimen.md_button_frame_vertical_padding);
  
      buttonHorizontalEdgeMargin = r.getDimensionPixelSize(R.dimen.md_button_padding_frame_side);
      buttonBarHeight = r.getDimensionPixelSize(R.dimen.md_button_height);
  
      dividerPaint = new Paint();
      dividerWidth = r.getDimensionPixelSize(R.dimen.md_divider_height);
      dividerPaint.setColor(DialogUtils.resolveColor(context, R.attr.md_divider_color));
      setWillNotDraw(false);
    }
  
    public void setMaxHeight(int maxHeight) {
      this.maxHeight = maxHeight;
    }
  
    public void noTitleNoPadding() {
      noTitleNoPadding = true;
    }
  
    @Override
    public void onFinishInflate() {
      super.onFinishInflate();
      for (int i = 0; i < getChildCount(); i++) {
        View v = getChildAt(i);
        if (v.getId() == R.id.md_titleFrame) {
          titleBar = v;
        } else if (v.getId() == R.id.md_buttonDefaultNeutral) {
          buttons[INDEX_NEUTRAL] = (MDButton) v;
        } else if (v.getId() == R.id.md_buttonDefaultNegative) {
          buttons[INDEX_NEGATIVE] = (MDButton) v;
        } else if (v.getId() == R.id.md_buttonDefaultPositive) {
          buttons[INDEX_POSITIVE] = (MDButton) v;
        } else {
          content = v;
        }
      }
    }
  
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      int width = MeasureSpec.getSize(widthMeasureSpec);
      int height = MeasureSpec.getSize(heightMeasureSpec);
  
      if (height > maxHeight) {
        height = maxHeight;
      }
  
      useFullPadding = true;
      boolean hasButtons = false;
  
      final boolean stacked;
      if (stackBehavior == StackingBehavior.ALWAYS) {
        stacked = true;
      } else if (stackBehavior == StackingBehavior.NEVER) {
        stacked = false;
      } else {
        int buttonsWidth = 0;
        for (MDButton button : buttons) {
          if (button != null && isVisible(button)) {
            button.setStacked(false, false);
            measureChild(button, widthMeasureSpec, heightMeasureSpec);
            buttonsWidth += button.getMeasuredWidth();
            hasButtons = true;
          }
        }
  
        int buttonBarPadding =
            getContext().getResources().getDimensionPixelSize(R.dimen.md_neutral_button_margin);
        final int buttonFrameWidth = width - 2 * buttonBarPadding;
        stacked = buttonsWidth > buttonFrameWidth;
      }
  
      int stackedHeight = 0;
      isStacked = stacked;
      if (stacked) {
        for (MDButton button : buttons) {
          if (button != null && isVisible(button)) {
            button.setStacked(true, false);
            measureChild(button, widthMeasureSpec, heightMeasureSpec);
            stackedHeight += button.getMeasuredHeight();
            hasButtons = true;
          }
        }
      }
  
      int availableHeight = height;
      int fullPadding = 0;
      int minPadding = 0;
      if (hasButtons) {
        if (isStacked) {
          availableHeight -= stackedHeight;
          fullPadding += 2 * buttonPaddingFull;
          minPadding += 2 * buttonPaddingFull;
        } else {
          availableHeight -= buttonBarHeight;
          fullPadding += 2 * buttonPaddingFull;
          /* No minPadding */
        }
      } else {
        /* Content has 8dp, we add 16dp and get 24dp, the frame margin */
        fullPadding += 2 * buttonPaddingFull;
      }
  
      if (isVisible(titleBar)) {
        titleBar.measure(
            MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);
        availableHeight -= titleBar.getMeasuredHeight();
      } else if (!noTitleNoPadding) {
        fullPadding += noTitlePaddingFull;
      }
  
      if (isVisible(content)) {
        content.measure(
            MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(availableHeight - minPadding, MeasureSpec.AT_MOST));
  
        if (content.getMeasuredHeight() <= availableHeight - fullPadding) {
          if (!reducePaddingNoTitleNoButtons || isVisible(titleBar) || hasButtons) {
            useFullPadding = true;
            availableHeight -= content.getMeasuredHeight() + fullPadding;
          } else {
            useFullPadding = false;
            availableHeight -= content.getMeasuredHeight() + minPadding;
          }
        } else {
          useFullPadding = false;
          availableHeight = 0;
        }
      }
  
      setMeasuredDimension(width, height - availableHeight);
    }
  
    @Override
    public void onDraw(Canvas canvas) {
      super.onDraw(canvas);
  
      if (content != null) {
        if (drawTopDivider) {
          int y = content.getTop();
          canvas.drawRect(0, y - dividerWidth, getMeasuredWidth(), y, dividerPaint);
        }
  
        if (drawBottomDivider) {
          int y = content.getBottom();
          canvas.drawRect(0, y, getMeasuredWidth(), y + dividerWidth, dividerPaint);
        }
      }
    }
  
    @Override
    protected void onLayout(boolean changed, final int l, int t, final int r, int b) {
      if (isVisible(titleBar)) {
        int height = titleBar.getMeasuredHeight();
        titleBar.layout(l, t, r, t + height);
        t += height;
      } else if (!noTitleNoPadding && useFullPadding) {
        t += noTitlePaddingFull;
      }
  
      if (isVisible(content)) {
        content.layout(l, t, r, t + content.getMeasuredHeight());
      }
  
      if (isStacked) {
        b -= buttonPaddingFull;
        for (MDButton mButton : buttons) {
          if (isVisible(mButton)) {
            mButton.layout(l, b - mButton.getMeasuredHeight(), r, b);
            b -= mButton.getMeasuredHeight();
          }
        }
      } else {
        int barTop;
        int barBottom = b;
        if (useFullPadding) {
          barBottom -= buttonPaddingFull;
        }
        barTop = barBottom - buttonBarHeight;
        /* START:
          Neutral   Negative  Positive
  
          CENTER:
          Negative  Neutral   Positive
  
          END:
          Positive  Negative  Neutral
  
          (With no Positive, Negative takes it's place except for CENTER)
        */
        int offset = buttonHorizontalEdgeMargin;
  
        /* Used with CENTER gravity */
        int neutralLeft = -1;
        int neutralRight = -1;
  
        if (isVisible(buttons[INDEX_POSITIVE])) {
          int bl, br;
          if (buttonGravity == GravityEnum.END) {
            bl = l + offset;
            br = bl + buttons[INDEX_POSITIVE].getMeasuredWidth();
          } else {
            /* START || CENTER */
            br = r - offset;
            bl = br - buttons[INDEX_POSITIVE].getMeasuredWidth();
            neutralRight = bl;
          }
          buttons[INDEX_POSITIVE].layout(bl, barTop, br, barBottom);
          offset += buttons[INDEX_POSITIVE].getMeasuredWidth();
        }
  
        if (isVisible(buttons[INDEX_NEGATIVE])) {
          int bl, br;
          if (buttonGravity == GravityEnum.END) {
            bl = l + offset;
            br = bl + buttons[INDEX_NEGATIVE].getMeasuredWidth();
          } else if (buttonGravity == GravityEnum.START) {
            br = r - offset;
            bl = br - buttons[INDEX_NEGATIVE].getMeasuredWidth();
          } else {
            /* CENTER */
            bl = l + buttonHorizontalEdgeMargin;
            br = bl + buttons[INDEX_NEGATIVE].getMeasuredWidth();
            neutralLeft = br;
          }
          buttons[INDEX_NEGATIVE].layout(bl, barTop, br, barBottom);
        }
  
        if (isVisible(buttons[INDEX_NEUTRAL])) {
          int bl, br;
          if (buttonGravity == GravityEnum.END) {
            br = r - buttonHorizontalEdgeMargin;
            bl = br - buttons[INDEX_NEUTRAL].getMeasuredWidth();
          } else if (buttonGravity == GravityEnum.START) {
            bl = l + buttonHorizontalEdgeMargin;
            br = bl + buttons[INDEX_NEUTRAL].getMeasuredWidth();
          } else {
            /* CENTER */
            if (neutralLeft == -1 && neutralRight != -1) {
              neutralLeft = neutralRight - buttons[INDEX_NEUTRAL].getMeasuredWidth();
            } else if (neutralRight == -1 && neutralLeft != -1) {
              neutralRight = neutralLeft + buttons[INDEX_NEUTRAL].getMeasuredWidth();
            } else if (neutralRight == -1) {
              neutralLeft = (r - l) / 2 - buttons[INDEX_NEUTRAL].getMeasuredWidth() / 2;
              neutralRight = neutralLeft + buttons[INDEX_NEUTRAL].getMeasuredWidth();
            }
            bl = neutralLeft;
            br = neutralRight;
          }
  
          buttons[INDEX_NEUTRAL].layout(bl, barTop, br, barBottom);
        }
      }
  
      setUpDividersVisibility(content, true, true);
    }
  
    public void setStackingBehavior(StackingBehavior behavior) {
      stackBehavior = behavior;
      invalidate();
    }
  
    public void setDividerColor(int color) {
      dividerPaint.setColor(color);
      invalidate();
    }
  
    public void setButtonGravity(GravityEnum gravity) {
      buttonGravity = gravity;
      invertGravityIfNecessary();
    }
  
    private void invertGravityIfNecessary() {
      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return;
      }
      Configuration config = getResources().getConfiguration();
      if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
        switch (buttonGravity) {
          case START:
            buttonGravity = GravityEnum.END;
            break;
          case END:
            buttonGravity = GravityEnum.START;
            break;
        }
      }
    }
  
    public void setButtonStackedGravity(GravityEnum gravity) {
      for (MDButton mButton : buttons) {
        if (mButton != null) {
          mButton.setStackedGravity(gravity);
        }
      }
    }
  
    private void setUpDividersVisibility(
        final View view, final boolean setForTop, final boolean setForBottom) {
      if (view == null) {
        return;
      }
      if (view instanceof ScrollView) {
        final ScrollView sv = (ScrollView) view;
        if (canScrollViewScroll(sv)) {
          addScrollListener(sv, setForTop, setForBottom);
        } else {
          if (setForTop) {
            drawTopDivider = false;
          }
          if (setForBottom) {
            drawBottomDivider = false;
          }
        }
      } else if (view instanceof AdapterView) {
        final AdapterView sv = (AdapterView) view;
        if (canAdapterViewScroll(sv)) {
          addScrollListener(sv, setForTop, setForBottom);
        } else {
          if (setForTop) {
            drawTopDivider = false;
          }
          if (setForBottom) {
            drawBottomDivider = false;
          }
        }
      } else if (view instanceof WebView) {
        view.getViewTreeObserver()
            .addOnPreDrawListener(
                new ViewTreeObserver.OnPreDrawListener() {
                  @Override
                  public boolean onPreDraw() {
                    if (view.getMeasuredHeight() != 0) {
                      if (!canWebViewScroll((WebView) view)) {
                        if (setForTop) {
                          drawTopDivider = false;
                        }
                        if (setForBottom) {
                          drawBottomDivider = false;
                        }
                      } else {
                        addScrollListener((ViewGroup) view, setForTop, setForBottom);
                      }
                      view.getViewTreeObserver().removeOnPreDrawListener(this);
                    }
                    return true;
                  }
                });
      } else if (view instanceof RecyclerView) {
        boolean canScroll = canRecyclerViewScroll((RecyclerView) view);
        if (setForTop) {
          drawTopDivider = canScroll;
        }
        if (setForBottom) {
          drawBottomDivider = canScroll;
        }
        if (canScroll) {
          addScrollListener((ViewGroup) view, setForTop, setForBottom);
        }
      } else if (view instanceof ViewGroup) {
        View topView = getTopView((ViewGroup) view);
        setUpDividersVisibility(topView, setForTop, setForBottom);
        View bottomView = getBottomView((ViewGroup) view);
        if (bottomView != topView) {
          setUpDividersVisibility(bottomView, false, true);
        }
      }
    }
  
    private void addScrollListener(
        final ViewGroup vg, final boolean setForTop, final boolean setForBottom) {
      if ((!setForBottom && topOnScrollChangedListener == null
          || (setForBottom && bottomOnScrollChangedListener == null))) {
        if (vg instanceof RecyclerView) {
          RecyclerView.OnScrollListener scrollListener =
              new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                  super.onScrolled(recyclerView, dx, dy);
                  boolean hasButtons = false;
                  for (MDButton button : buttons) {
                    if (button != null && button.getVisibility() != View.GONE) {
                      hasButtons = true;
                      break;
                    }
                  }
                  invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
                  invalidate();
                }
              };
          ((RecyclerView) vg).addOnScrollListener(scrollListener);
          scrollListener.onScrolled((RecyclerView) vg, 0, 0);
        } else {
          ViewTreeObserver.OnScrollChangedListener onScrollChangedListener =
              new ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {
                  boolean hasButtons = false;
                  for (MDButton button : buttons) {
                    if (button != null && button.getVisibility() != View.GONE) {
                      hasButtons = true;
                      break;
                    }
                  }
                  if (vg instanceof WebView) {
                    invalidateDividersForWebView((WebView) vg, setForTop, setForBottom, hasButtons);
                  } else {
                    invalidateDividersForScrollingView(vg, setForTop, setForBottom, hasButtons);
                  }
                  invalidate();
                }
              };
          if (!setForBottom) {
            topOnScrollChangedListener = onScrollChangedListener;
            vg.getViewTreeObserver().addOnScrollChangedListener(topOnScrollChangedListener);
          } else {
            bottomOnScrollChangedListener = onScrollChangedListener;
            vg.getViewTreeObserver().addOnScrollChangedListener(bottomOnScrollChangedListener);
          }
          onScrollChangedListener.onScrollChanged();
        }
      }
    }
  
    private void invalidateDividersForScrollingView(
        ViewGroup view, final boolean setForTop, boolean setForBottom, boolean hasButtons) {
      if (setForTop && view.getChildCount() > 0) {
        drawTopDivider =
            titleBar != null
                && titleBar.getVisibility() != View.GONE
                &&
                //Not scrolled to the top.
                view.getScrollY() + view.getPaddingTop() > view.getChildAt(0).getTop();
      }
      if (setForBottom && view.getChildCount() > 0) {
        drawBottomDivider =
            hasButtons
                && view.getScrollY() + view.getHeight() - view.getPaddingBottom()
                    < view.getChildAt(view.getChildCount() - 1).getBottom();
      }
    }
  
    private void invalidateDividersForWebView(
        WebView view, final boolean setForTop, boolean setForBottom, boolean hasButtons) {
      if (setForTop) {
        drawTopDivider =
            titleBar != null
                && titleBar.getVisibility() != View.GONE
                &&
                //Not scrolled to the top.
                view.getScrollY() + view.getPaddingTop() > 0;
      }
      if (setForBottom) {
        //noinspection deprecation
        drawBottomDivider =
            hasButtons
                && view.getScrollY() + view.getMeasuredHeight() - view.getPaddingBottom()
                    < view.getContentHeight() * view.getScale();
      }
    }
  }
  