public class DialogPlus {

  private static final int INVALID = -1;

  /**
   * DialogPlus base layout root view
   */
  private final ViewGroup rootView;

  /**
   * DialogPlus content container which is a different layout rather than base layout
   */
  private final ViewGroup contentContainer;

  /**
   * Determines whether dialog should be dismissed by back button or touch in the black overlay
   */
  private final boolean isCancelable;

  /**
   * Determines whether dialog is showing dismissing animation and avoid to repeat it
   */
  private boolean isDismissing;

  /**
   * Listener for the user to take action by clicking any item
   */
  private final OnItemClickListener onItemClickListener;

  /**
   * Listener for the user to take action by clicking views in header or footer
   */
  private final OnClickListener onClickListener;

  /**
   * Listener to notify the user that dialog has been dismissed
   */
  private final OnDismissListener onDismissListener;

  /**
   * Listener to notify the user that dialog has been canceled
   */
  private final OnCancelListener onCancelListener;

  /**
   * Listener to notify back press
   */
  private final OnBackPressListener onBackPressListener;

  /**
   * Listener to notify on show
   */
  private final OnShowListener onShowListener;

  /**
   * Content
   */
  private final Holder holder;

  /**
   * basically activity root view
   */
  private final ViewGroup decorView;

  private final Animation outAnim;
  private final Animation inAnim;

  DialogPlus(DialogPlusBuilder builder) {
    LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());

    Activity activity = (Activity) builder.getContext();

    holder = builder.getHolder();

    onItemClickListener = builder.getOnItemClickListener();
    onClickListener = builder.getOnClickListener();
    onDismissListener = builder.getOnDismissListener();
    onCancelListener = builder.getOnCancelListener();
    onBackPressListener = builder.getOnBackPressListener();
    onShowListener = builder.getOnShowListener();
    isCancelable = builder.isCancelable();

    /**
     * Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on
     * nexus device. I think it should be tested on different devices but in my opinion is the way to go.
     * @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity
     */
    if (builder.isAddToRoot()) {
        decorView = (ViewGroup) ((ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
    } else {
        decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
    }
    rootView = (ViewGroup) layoutInflater.inflate(R.layout.base_container, decorView, false);
    rootView.setLayoutParams(builder.getOutmostLayoutParams());

    View outmostView = rootView.findViewById(R.id.dialogplus_outmost_container);
    outmostView.setBackgroundResource(builder.getOverlayBackgroundResource());

    contentContainer = (ViewGroup) rootView.findViewById(R.id.dialogplus_content_container);
    contentContainer.setLayoutParams(builder.getContentParams());

    outAnim = builder.getOutAnimation();
    inAnim = builder.getInAnimation();

    initContentView(
        layoutInflater,
        builder.getHeaderView(),
        builder.getFooterView(),
        builder.getAdapter(),
        builder.getContentPadding(),
        builder.getContentMargin()
    );

    initCancelable();
    if (builder.isExpanded()) {
      initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.getContentParams().gravity);
    }
  }

  public static DialogPlusBuilder newDialog(Context context) {
    return new DialogPlusBuilder(context);
  }

  /**
   * It adds the dialog view into rootView which is decorView of activity
   */
  public void show() {
    if (isShowing()) {
      return;
    }
    onAttached(rootView);
  }

  /**
   * It basically check if the rootView contains the dialog plus view.
   *
   * @return true if it contains
   */
  public boolean isShowing() {
    View view = decorView.findViewById(R.id.dialogplus_outmost_container);
    return view != null;
  }

  /**
   * It is called when to dismiss the dialog, either by calling dismiss() method or with cancellable
   */
  public void dismiss() {
    if (isDismissing) {
      return;
    }

    outAnim.setAnimationListener(new Animation.AnimationListener() {
      @Override public void onAnimationStart(Animation animation) {
        isDismissing = true;
      }

      @Override public void onAnimationEnd(Animation animation) {
        decorView.post(new Runnable() {
          @Override public void run() {
            decorView.removeView(rootView);
            isDismissing = false;
            if (onDismissListener != null) {
              onDismissListener.onDismiss(DialogPlus.this);
            }
          }
        });
      }

      @Override public void onAnimationRepeat(Animation animation) {

      }
    });
    contentContainer.startAnimation(outAnim);
  }

  @SuppressWarnings("unused")
  public View findViewById(int resourceId) {
    return contentContainer.findViewById(resourceId);
  }

  public View getHeaderView() {
    return holder.getHeader();
  }

  public View getFooterView() {
    return holder.getFooter();
  }

  public View getHolderView() {
    return holder.getInflatedView();
  }

  private void initExpandAnimator(Activity activity, int defaultHeight, int gravity) {
    Display display = activity.getWindowManager().getDefaultDisplay();
    int displayHeight = display.getHeight() - Utils.getStatusBarHeight(activity);
    if (defaultHeight == 0) {
      defaultHeight = (displayHeight * 2) / 5;
    }

    final View view = holder.getInflatedView();
    if (!(view instanceof AbsListView)) {
      return;
    }
    final AbsListView absListView = (AbsListView) view;

    view.setOnTouchListener(ExpandTouchListener.newListener(
        activity, absListView, contentContainer, gravity, displayHeight, defaultHeight
    ));
  }

  /**
   * It is called in order to create content
   */
  private void initContentView(LayoutInflater inflater, View header, View footer, BaseAdapter adapter,
                               int[] padding, int[] margin) {
    View contentView = createView(inflater, header, footer, adapter);
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
    );
    params.setMargins(margin[0], margin[1], margin[2], margin[3]);
    contentView.setLayoutParams(params);
    getHolderView().setPadding(padding[0], padding[1], padding[2], padding[3]);
    contentContainer.addView(contentView);
  }

  /**
   * It is called to set whether the dialog is cancellable by pressing back button or
   * touching the black overlay
   */
  private void initCancelable() {
    if (!isCancelable) {
      return;
    }
    View view = rootView.findViewById(R.id.dialogplus_outmost_container);
    view.setOnTouchListener(onCancelableTouchListener);
  }

  /**
   * it is called when the content view is created
   *
   * @param inflater used to inflate the content of the dialog
   * @return any view which is passed
   */
  private View createView(LayoutInflater inflater, View headerView, View footerView, BaseAdapter adapter) {
    View view = holder.getView(inflater, rootView);

    if (holder instanceof ViewHolder) {
      assignClickListenerRecursively(view);
    }

    assignClickListenerRecursively(headerView);
    holder.addHeader(headerView);

    assignClickListenerRecursively(footerView);
    holder.addFooter(footerView);

    if (adapter != null && holder instanceof HolderAdapter) {
      HolderAdapter holderAdapter = (HolderAdapter) holder;
      holderAdapter.setAdapter(adapter);
      holderAdapter.setOnItemClickListener(new OnHolderListener() {
        @Override public void onItemClick(Object item, View view, int position) {
          if (onItemClickListener == null) {
            return;
          }
          onItemClickListener.onItemClick(DialogPlus.this, item, view, position);
        }
      });
    }
    return view;
  }

  /**
   * Loop among the views in the hierarchy and assign listener to them
   */
  private void assignClickListenerRecursively(View parent) {
    if (parent == null) {
      return;
    }

    if (parent instanceof ViewGroup) {
      ViewGroup viewGroup = (ViewGroup) parent;
      int childCount = viewGroup.getChildCount();
      for (int i = childCount - 1; i >= 0; i--) {
        View child = viewGroup.getChildAt(i);
        assignClickListenerRecursively(child);
      }
    }
    setClickListener(parent);
  }

  /**
   * It is used to setListener on view that have a valid id associated
   */
  private void setClickListener(View view) {
    if (view.getId() == INVALID) {
      return;
    }
    //adapterview does not support click listener
    if (view instanceof AdapterView) {
      return;
    }

    view.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        if (onClickListener == null) {
          return;
        }
        onClickListener.onClick(DialogPlus.this, v);
      }
    });
  }

  /**
   * It is called when the show() method is called
   *
   * @param view is the dialog plus view
   */
  private void onAttached(View view) {
    decorView.addView(view);
    inAnim.setAnimationListener(new Animation.AnimationListener() {
      @Override
      public void onAnimationStart(Animation animation) {

      }

      @Override
      public void onAnimationEnd(Animation animation) {
        decorView.post(new Runnable() {
          @Override public void run() {
            if (onShowListener != null) {
              onShowListener.onShow(DialogPlus.this);
            }
          }
        });
      }

      @Override
      public void onAnimationRepeat(Animation animation) {

      }
    });
    contentContainer.startAnimation(inAnim);

    contentContainer.requestFocus();
    holder.setOnKeyListener(new View.OnKeyListener() {
      @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        switch (event.getAction()) {
          case KeyEvent.ACTION_UP:
            if (keyCode == KeyEvent.KEYCODE_BACK) {
              if (onBackPressListener != null) {
                onBackPressListener.onBackPressed(DialogPlus.this);
              }
              if (isCancelable) {
                onBackPressed(DialogPlus.this);
              }
              return true;
            }
            break;
          default:
            break;
        }
        return false;
      }
    });
  }

  /**
   * Dismiss the dialog when the user press the back button
   *
   * @param dialogPlus is the current dialog
   */
  public void onBackPressed(DialogPlus dialogPlus) {
    if (onCancelListener != null) {
      onCancelListener.onCancel(DialogPlus.this);
    }
    dismiss();
  }

  /**
   * Called when the user touch on black overlay in order to dismiss the dialog
   */
  private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() {
    @Override public boolean onTouch(View v, MotionEvent event) {
      if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (onCancelListener != null) {
          onCancelListener.onCancel(DialogPlus.this);
        }
        dismiss();
      }
      return false;
    }
  };

    public void onSizeChanged(int width, int height) {
//        ViewGroup.LayoutParams params = rootView.getLayoutParams();
//        params.width = width;
//        params.height = height;
//        rootView.setLayoutParams(params);
    }
} public class DialogPlusBuilder {
  private static final int INVALID = -1;

  private final int[] margin = new int[4];
  private final int[] padding = new int[4];
  private final int[] outMostMargin = new int[4];
  private final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
      ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM
  );

  private BaseAdapter adapter;
  private Context context;
  private View footerView;
  private View headerView;
  private Holder holder;
  private int gravity = Gravity.BOTTOM;
  private OnItemClickListener onItemClickListener;
  private OnClickListener onClickListener;
  private OnDismissListener onDismissListener;
  private OnCancelListener onCancelListener;
  private OnBackPressListener onBackPressListener;
  private OnShowListener onShowListener;

  private boolean isCancelable = true;
  private int contentBackgroundResource = android.R.color.white;
  private int headerViewResourceId = INVALID;
  private int footerViewResourceId = INVALID;
  private int inAnimation = INVALID;
  private int outAnimation = INVALID;
  private boolean expanded;
  private boolean addToRoot;
  private int defaultContentHeight;
  private int overlayBackgroundResource = R.color.dialogplus_black_overlay;

  private DialogPlusBuilder() {
  }

  /**
   * Initialize the builder with a valid context in order to inflate the dialog
   */
  DialogPlusBuilder(Context context) {
    if (context == null) {
      throw new NullPointerException("Context may not be null");
    }
    this.context = context;
    Arrays.fill(margin, INVALID);
  }

  /**
   * Set the adapter that will be used when ListHolder or GridHolder are passed
   */
  public DialogPlusBuilder setAdapter(BaseAdapter adapter) {
    if (adapter == null) {
      throw new NullPointerException("Adapter may not be null");
    }
    this.adapter = adapter;
    return this;
  }

  /**
   * Set the footer view using the id of the layout resource
   */
  public DialogPlusBuilder setFooter(int resourceId) {
    this.footerViewResourceId = resourceId;
    return this;
  }

  /**
   * Set the footer view using a view
   */
  public DialogPlusBuilder setFooter(View view) {
    this.footerView = view;
    return this;
  }

  /**
   * Set the header view using the id of the layout resource
   */
  public DialogPlusBuilder setHeader(int resourceId) {
    this.headerViewResourceId = resourceId;
    return this;
  }

  /**
   * Set the header view using a view
   */
  public DialogPlusBuilder setHeader(View view) {
    this.headerView = view;
    return this;
  }

  /**
   * Define if the dialog is cancelable and should be closed when back pressed or click outside is pressed
   */
  public DialogPlusBuilder setCancelable(boolean isCancelable) {
    this.isCancelable = isCancelable;
    return this;
  }

  /**
   * Set the content of the dialog by passing one of the provided Holders
   */
  public DialogPlusBuilder setContentHolder(Holder holder) {
    this.holder = holder;
    return this;
  }

  /**
   * Use setBackgroundResource
   */
  @Deprecated public DialogPlusBuilder setBackgroundColorResId(int resourceId) {
    return setContentBackgroundResource(resourceId);
  }

    public DialogPlusBuilder addToRoot(boolean addToRoot) {
        this.addToRoot = addToRoot;
        return this;
    }

    public boolean isAddToRoot() {
        return this.addToRoot;
    }

  /**
   * Set background color for your dialog. If no resource is passed 'white' will be used
   */
  public DialogPlusBuilder setContentBackgroundResource(int resourceId) {
    this.contentBackgroundResource = resourceId;
    return this;
  }

  public DialogPlusBuilder setOverlayBackgroundResource(int resourceId) {
    this.overlayBackgroundResource = resourceId;
    return this;
  }

  /**
   * Set the gravity you want the dialog to have among the ones that are provided
   */
  public DialogPlusBuilder setGravity(int gravity) {
    this.gravity = gravity;
    params.gravity = gravity;
    return this;
  }

  /**
   * Customize the in animation by passing an animation resource
   */
  public DialogPlusBuilder setInAnimation(int inAnimResource) {
    this.inAnimation = inAnimResource;
    return this;
  }

  /**
   * Customize the out animation by passing an animation resource
   */
  public DialogPlusBuilder setOutAnimation(int outAnimResource) {
    this.outAnimation = outAnimResource;
    return this;
  }

  /**
   * Add margins to your outmost view which contains everything. As default they are 0
   * are applied
   */
  public DialogPlusBuilder setOutMostMargin(int left, int top, int right, int bottom) {
    this.outMostMargin[0] = left;
    this.outMostMargin[1] = top;
    this.outMostMargin[2] = right;
    this.outMostMargin[3] = bottom;
    return this;
  }

  /**
   * Add margins to your dialog. They are set to 0 except when gravity is center. In that case basic margins
   * are applied
   */
  public DialogPlusBuilder setMargin(int left, int top, int right, int bottom) {
    this.margin[0] = left;
    this.margin[1] = top;
    this.margin[2] = right;
    this.margin[3] = bottom;
    return this;
  }

  /**
   * Set paddings for the dialog content
   */
  public DialogPlusBuilder setPadding(int left, int top, int right, int bottom) {
    this.padding[0] = left;
    this.padding[1] = top;
    this.padding[2] = right;
    this.padding[3] = bottom;
    return this;
  }

  /**
   * Set an item click listener when list or grid holder is chosen. In that way you can have callbacks when one
   * of your items is clicked
   */
  public DialogPlusBuilder setOnItemClickListener(OnItemClickListener listener) {
    this.onItemClickListener = listener;
    return this;
  }

  /**
   * Set a global click listener to you dialog in order to handle all the possible click events. You can then
   * identify the view by using its id and handle the correct behaviour
   */
  public DialogPlusBuilder setOnClickListener(OnClickListener listener) {
    this.onClickListener = listener;
    return this;
  }

  public DialogPlusBuilder setOnShowListener(OnShowListener listener) {
      this.onShowListener = listener;
      return this;
  }

  public DialogPlusBuilder setOnDismissListener(OnDismissListener listener) {
    this.onDismissListener = listener;
    return this;
  }

  public DialogPlusBuilder setOnCancelListener(OnCancelListener listener) {
    this.onCancelListener = listener;
    return this;
  }

  public DialogPlusBuilder setOnBackPressListener(OnBackPressListener listener) {
    this.onBackPressListener = listener;
    return this;
  }

  public DialogPlusBuilder setExpanded(boolean expanded) {
    this.expanded = expanded;
    return this;
  }

  public DialogPlusBuilder setExpanded(boolean expanded, int defaultContentHeight) {
    this.expanded = expanded;
    this.defaultContentHeight = defaultContentHeight;
    return this;
  }

  public DialogPlusBuilder setContentHeight(int height) {
    params.height = height;
    return this;
  }

  public DialogPlusBuilder setContentWidth(int width) {
    params.width = width;
    return this;
  }

  /**
   * Create the dialog using this builder
   */
  public DialogPlus create() {
    getHolder().setBackgroundResource(getContentBackgroundResource());
    return new DialogPlus(this);
  }

  public View getFooterView() {
    return Utils.getView(context, footerViewResourceId, footerView);
  }

  public View getHeaderView() {
    return Utils.getView(context, headerViewResourceId, headerView);
  }

  public Holder getHolder() {
    if (holder == null) {
      holder = new ListHolder();
    }
    return holder;
  }

  public Context getContext() {
    return context;
  }

  public BaseAdapter getAdapter() {
    return adapter;
  }

  public Animation getInAnimation() {
    int res = (inAnimation == INVALID) ? Utils.getAnimationResource(this.gravity, true) : inAnimation;
    return AnimationUtils.loadAnimation(context, res);
  }

  public Animation getOutAnimation() {
    int res = (outAnimation == INVALID) ? Utils.getAnimationResource(this.gravity, false) : outAnimation;
    return AnimationUtils.loadAnimation(context, res);
  }

  public FrameLayout.LayoutParams getContentParams() {
    if (expanded) {
      params.height = getDefaultContentHeight();
    }
    return params;
  }

  public boolean isExpanded() {
    return expanded;
  }

  public FrameLayout.LayoutParams getOutmostLayoutParams() {
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
    );
    params.setMargins(outMostMargin[0], outMostMargin[1], outMostMargin[2], outMostMargin[3]);
    return params;
  }

  public boolean isCancelable() {
    return isCancelable;
  }

  public OnItemClickListener getOnItemClickListener() {
    return onItemClickListener;
  }

  public OnClickListener getOnClickListener() {
    return onClickListener;
  }

  public OnDismissListener getOnDismissListener() {
    return onDismissListener;
  }

  public OnShowListener getOnShowListener() {
      return onShowListener;
  }

  public OnCancelListener getOnCancelListener() {
    return onCancelListener;
  }

  public OnBackPressListener getOnBackPressListener() {
    return onBackPressListener;
  }

  public int[] getContentMargin() {
    int minimumMargin = context.getResources().getDimensionPixelSize(R.dimen.dialogplus_default_center_margin);
    for (int i = 0; i < margin.length; i++) {
      margin[i] = getMargin(this.gravity, margin[i], minimumMargin);
    }
    return margin;
  }

  public int[] getContentPadding() {
    return padding;
  }

  public int getDefaultContentHeight() {
    Activity activity = (Activity) context;
    Display display = activity.getWindowManager().getDefaultDisplay();
    int displayHeight = display.getHeight() - Utils.getStatusBarHeight(activity);
    if (defaultContentHeight == 0) {
      defaultContentHeight = (displayHeight * 2) / 5;
    }
    return defaultContentHeight;
  }

  public int getOverlayBackgroundResource() {
    return overlayBackgroundResource;
  }

  public int getContentBackgroundResource() {
    return contentBackgroundResource;
  }

  /**
   * Get margins if provided or assign default values based on gravity
   *
   * @param gravity       the gravity of the dialog
   * @param margin        the value defined in the builder
   * @param minimumMargin the minimum margin when gravity center is selected
   * @return the value of the margin
   */
  private int getMargin(int gravity, int margin, int minimumMargin) {
    switch (gravity) {
      case Gravity.CENTER:
        return (margin == INVALID) ? minimumMargin : margin;
      default:
        return (margin == INVALID) ? 0 : margin;
    }
  }

} public class GridHolder implements HolderAdapter, AdapterView.OnItemClickListener {

  private final int columnNumber;

  private int backgroundResource;

  private GridView gridView;
  private ViewGroup headerContainer;
  private ViewGroup footerContainer;
  private OnHolderListener listener;
  private View.OnKeyListener keyListener;
  private View headerView;
  private View footerView;

  public GridHolder(int columnNumber) {
    this.columnNumber = columnNumber;
  }

  @Override public void addHeader(View view) {
    if (view == null) {
      return;
    }
    headerContainer.addView(view);
    headerView = view;
  }

  @Override public void addFooter(View view) {
    if (view == null) {
      return;
    }
    footerContainer.addView(view);
    footerView = view;
  }

  @Override public void setAdapter(BaseAdapter adapter) {
    gridView.setAdapter(adapter);
  }

  @Override public void setBackgroundResource(int colorResource) {
    this.backgroundResource = colorResource;
  }

  @Override public View getView(LayoutInflater inflater, ViewGroup parent) {
    View view = inflater.inflate(R.layout.dialog_grid, parent, false);
    View outMostView = view.findViewById(R.id.dialogplus_outmost_container);
    outMostView.setBackgroundResource(backgroundResource);
    gridView = (GridView) view.findViewById(R.id.dialogplus_list);
    gridView.setNumColumns(columnNumber);
    gridView.setOnItemClickListener(this);
    gridView.setOnKeyListener(new View.OnKeyListener() {
      @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyListener == null) {
          throw new NullPointerException("keyListener should not be null");
        }
        return keyListener.onKey(v, keyCode, event);
      }
    });
    headerContainer = (ViewGroup) view.findViewById(R.id.dialogplus_header_container);
    footerContainer = (ViewGroup) view.findViewById(R.id.dialogplus_footer_container);
    return view;
  }

  @Override public void setOnItemClickListener(OnHolderListener listener) {
    this.listener = listener;
  }

  @Override public void setOnKeyListener(View.OnKeyListener keyListener) {
    this.keyListener = keyListener;
  }

  @Override public View getInflatedView() {
    return gridView;
  }

  @Override public View getHeader() {
    return headerView;
  }

  @Override public View getFooter() {
    return footerView;
  }

  @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    if (listener == null) {
      return;
    }
    listener.onItemClick(parent.getItemAtPosition(position), view, position);
  }
} public class ListHolder implements HolderAdapter, AdapterView.OnItemClickListener {

  private int backgroundResource;

  private ListView listView;
  private OnHolderListener listener;
  private View.OnKeyListener keyListener;
  private View headerView;
  private View footerView;

  @Override public void addHeader(View view) {
    if (view == null) {
      return;
    }
    listView.addHeaderView(view);
    headerView = view;
  }

  @Override public void addFooter(View view) {
    if (view == null) {
      return;
    }
    listView.addFooterView(view);
    footerView = view;
  }

  @Override public void setAdapter(BaseAdapter adapter) {
    listView.setAdapter(adapter);
  }

  @Override public void setBackgroundResource(int colorResource) {
    this.backgroundResource = colorResource;
  }

  @Override public View getView(LayoutInflater inflater, ViewGroup parent) {
    View view = inflater.inflate(R.layout.dialog_list, parent, false);
    View outMostView = view.findViewById(R.id.dialogplus_outmost_container);
    outMostView.setBackgroundResource(backgroundResource);
    listView = (ListView) view.findViewById(R.id.dialogplus_list);
    listView.setOnItemClickListener(this);
    listView.setOnKeyListener(new View.OnKeyListener() {
      @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyListener == null) {
          throw new NullPointerException("keyListener should not be null");
        }
        return keyListener.onKey(v, keyCode, event);
      }
    });
    return view;
  }

  @Override public void setOnItemClickListener(OnHolderListener listener) {
    this.listener = listener;
  }

  @Override public void setOnKeyListener(View.OnKeyListener keyListener) {
    this.keyListener = keyListener;
  }

  @Override public View getInflatedView() {
    return listView;
  }

  @Override public View getHeader() {
    return headerView;
  }

  @Override public View getFooter() {
    return footerView;
  }

  @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    if (listener == null) {
      return;
    }
    //ListView counts header as position as well. For consistency we don't
    listener.onItemClick(
        parent.getItemAtPosition(position),
        view,
        headerView != null ? --position : position
    );
  }
} public interface OnBackPressListener {

  void onBackPressed(DialogPlus dialogPlus);

} public interface OnCancelListener {
  void onCancel(DialogPlus dialog);
} public interface OnClickListener {

  void onClick(DialogPlus dialog, View view);

} public interface OnDismissListener {
   void onDismiss(DialogPlus dialog);
} public interface OnItemClickListener {

  void onItemClick(DialogPlus dialog, Object item, View view, int position);

} public class ViewHolder implements Holder {

  private static final int INVALID = -1;

  private int backgroundResource;

  private ViewGroup headerContainer;
  private View headerView;

  private ViewGroup footerContainer;
  private View footerView;

  private View.OnKeyListener keyListener;

  private View contentView;
  private int viewResourceId = INVALID;

  public ViewHolder(int viewResourceId) {
    this.viewResourceId = viewResourceId;
  }

  public ViewHolder(View contentView) {
    this.contentView = contentView;
  }

  @Override public void addHeader(View view) {
    if (view == null) {
      return;
    }
    headerContainer.addView(view);
    headerView = view;
  }

  @Override public void addFooter(View view) {
    if (view == null) {
      return;
    }
    footerContainer.addView(view);
    footerView = view;
  }

  @Override public void setBackgroundResource(int colorResource) {
    this.backgroundResource = colorResource;
  }

  @Override public View getView(LayoutInflater inflater, ViewGroup parent) {
    View view = inflater.inflate(R.layout.dialog_view, parent, false);
    View outMostView = view.findViewById(R.id.dialogplus_outmost_container);
    outMostView.setBackgroundResource(backgroundResource);
    ViewGroup contentContainer = (ViewGroup) view.findViewById(R.id.dialogplus_view_container);
    contentContainer.setOnKeyListener(new View.OnKeyListener() {
      @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (keyListener == null) {
          throw new NullPointerException("keyListener should not be null");
        }
        return keyListener.onKey(v, keyCode, event);
      }
    });
    addContent(inflater, parent, contentContainer);
    headerContainer = (ViewGroup) view.findViewById(R.id.dialogplus_header_container);
    footerContainer = (ViewGroup) view.findViewById(R.id.dialogplus_footer_container);
    return view;
  }

  private void addContent(LayoutInflater inflater, ViewGroup parent, ViewGroup container) {
    if (viewResourceId != INVALID) {
      contentView = inflater.inflate(viewResourceId, parent, false);
    } else {
      ViewGroup parentView = (ViewGroup) contentView.getParent();
      if (parentView != null) {
        parentView.removeView(contentView);
      }
    }

    container.addView(contentView);
  }

  @Override public void setOnKeyListener(View.OnKeyListener keyListener) {
    this.keyListener = keyListener;
  }

  @Override public View getInflatedView() {
    return contentView;
  }

  @Override public View getHeader() {
    return headerView;
  }

  @Override public View getFooter() {
    return footerView;
  }
}
import android.app.Activity;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.orhanobut.dialogplus.BuildConfig;
import com.orhanobut.dialogplus.DialogPlus;
import com.orhanobut.dialogplus.DialogPlusBuilder;
import com.orhanobut.dialogplus.GridHolder;
import com.orhanobut.dialogplus.ListHolder;
import com.orhanobut.dialogplus.listener.OnBackPressListener;
import com.orhanobut.dialogplus.listener.OnCancelListener;
import com.orhanobut.dialogplus.listener.OnClickListener;
import com.orhanobut.dialogplus.listener.OnDismissListener;
import com.orhanobut.dialogplus.listener.OnItemClickListener;
import com.orhanobut.dialogplus.R;
import com.orhanobut.dialogplus.ViewHolder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import static junit.framework.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
    assertThat(params.height).isEqualTo(100);
    assertThat(params.gravity).isEqualTo(Gravity.BOTTOM);
  }

  @Test public void testExpanded() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);

    //default should be false
    assertThat(builder.isExpanded()).isFalse();

    builder.setExpanded(true);
    assertThat(builder.isExpanded()).isTrue();
  }

  @Test public void testOutMostParams() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    FrameLayout.LayoutParams params = builder.getOutmostLayoutParams();

    assertThat(params.width).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT);
    assertThat(params.height).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT);

    // default should be 0 for all
    assertThat(params.leftMargin).isEqualTo(0);
    assertThat(params.rightMargin).isEqualTo(0);
    assertThat(params.topMargin).isEqualTo(0);
    assertThat(params.bottomMargin).isEqualTo(0);

    //set new margin
    builder.setOutMostMargin(1, 2, 3, 4);
    params = builder.getOutmostLayoutParams();
    assertThat(params.leftMargin).isEqualTo(1);
    assertThat(params.topMargin).isEqualTo(2);
    assertThat(params.rightMargin).isEqualTo(3);
    assertThat(params.bottomMargin).isEqualTo(4);
  }

  @Test public void testDefaultContentMarginWhenGravityCenter() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    builder.setGravity(Gravity.CENTER);

    int minimumMargin = context.getResources().getDimensionPixelSize(R.dimen.dialogplus_default_center_margin);
    int[] margin = builder.getContentMargin();
    assertThat(margin[0]).isEqualTo(minimumMargin);
    assertThat(margin[1]).isEqualTo(minimumMargin);
    assertThat(margin[2]).isEqualTo(minimumMargin);
    assertThat(margin[3]).isEqualTo(minimumMargin);
  }

  @Test public void testDefaultContentMarginWhenGravityIsNotCenter() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    int[] margin = builder.getContentMargin();
    assertThat(margin[0]).isEqualTo(0);
    assertThat(margin[1]).isEqualTo(0);
    assertThat(margin[2]).isEqualTo(0);
    assertThat(margin[3]).isEqualTo(0);
  }

  @Test public void testContentMarginWhenSet() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    builder.setMargin(1, 2, 3, 4);
    int[] margin = builder.getContentMargin();
    assertThat(margin[0]).isEqualTo(1);
    assertThat(margin[1]).isEqualTo(2);
    assertThat(margin[2]).isEqualTo(3);
    assertThat(margin[3]).isEqualTo(4);
  }

  @Test public void testPadding() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);

    //default 0
    int[] padding = builder.getContentPadding();
    assertThat(padding[0]).isEqualTo(0);
    assertThat(padding[1]).isEqualTo(0);
    assertThat(padding[2]).isEqualTo(0);
    assertThat(padding[3]).isEqualTo(0);


    builder.setPadding(1, 2, 3, 4);
    padding = builder.getContentPadding();
    assertThat(padding[0]).isEqualTo(1);
    assertThat(padding[1]).isEqualTo(2);
    assertThat(padding[2]).isEqualTo(3);
    assertThat(padding[3]).isEqualTo(4);
  }

  @Test public void testSetContentWidth() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    builder.setContentWidth(100);

    FrameLayout.LayoutParams params = builder.getContentParams();
    assertThat(params.width).isEqualTo(100);
  }

  @Test public void testSetContentHeight() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    builder.setContentHeight(100);

    FrameLayout.LayoutParams params = builder.getContentParams();
    assertThat(params.height).isEqualTo(100);
  }

  @Test public void testSetOnClickListener() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    assertThat(builder.getOnClickListener()).isNull();

    OnClickListener clickListener = new OnClickListener() {
      @Override public void onClick(DialogPlus dialog, View view) {

      }
    };
    builder.setOnClickListener(clickListener);
    assertThat(builder.getOnClickListener()).isNotNull();
    assertThat(builder.getOnClickListener()).isEqualTo(clickListener);
  }

  @Test public void testSetOnItemClickListener() {
    DialogPlusBuilder builder = DialogPlus.newDialog(context);
    assertThat(builder.getOnItemClickListener()).isNull();

    OnItemClickListener listener = new OnItemClickListener() {