jp.co.noxi.app.NXAlertDialog.java Source code

Java tutorial

Introduction

Here is the source code for jp.co.noxi.app.NXAlertDialog.java

Source

/*
 * Copyright (C) 2013 noxi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jp.co.noxi.app;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.TypedValue;
import android.view.View;
import android.widget.ListAdapter;

/**
 * {@link android.support.v4.app.DialogFragment} like {@link AlertDialog.Builder}
 * 
 * @author noxi
 */
public class NXAlertDialog extends NXDialog {

    /**
     * Delegate for list adapter bindings.
     */
    interface ListAdapterDelegate {
        ListAdapter getListAdapter(NXDialogInterface dialog);
    }

    /**
     * Delegate for custom title view.
     */
    interface TitleViewDelegate {
        View getTitleView(NXDialogInterface dialog);
    }

    /**
     * Special theme constant for {@link Builder#Builder(Context, int)}: use
     * the traditional (pre-Holo) alert dialog theme.
     */
    public static final int THEME_TRADITIONAL = AlertDialog.THEME_TRADITIONAL;

    /**
     * Special theme constant for {@link Builder#Builder(Context, int)}: use
     * the holographic alert theme with a dark background.
     */
    public static final int THEME_HOLO_DARK = AlertDialog.THEME_HOLO_DARK;

    /**
     * Special theme constant for {@link Builder#Builder(Context, int)}: use
     * the holographic alert theme with a light background.
     */
    public static final int THEME_HOLO_LIGHT = AlertDialog.THEME_HOLO_LIGHT;

    /**
     * Special theme constant for {@link Builder#Builder(Context, int)}: use
     * the device's default alert theme with a dark background.
     */
    public static final int THEME_DEVICE_DEFAULT_DARK = AlertDialog.THEME_DEVICE_DEFAULT_DARK;

    /**
     * Special theme constant for {@link Builder#Builder(Context, int)}: use
     * the device's default alert theme with a dark background.
     */
    public static final int THEME_DEVICE_DEFAULT_LIGHT = AlertDialog.THEME_DEVICE_DEFAULT_LIGHT;

    private static final String ARG_THEME = "theme";
    private static final String ARG_ICON = "icon";
    private static final String ARG_INVERSE_BACKGROUND = "inverseBackground";
    private static final String ARG_TITLE = "title";
    private static final String ARG_CUSTOM_TITLE = "customTitle";
    private static final String ARG_MESSAGE = "message";

    private static final String ARG_ITEMS = "items";
    private static final String ARG_ITEMS_LISTENER = "itemsListener";

    private static final String ARG_ADAPTER = "adapter";
    private static final String ARG_ADAPTER_LISTENER = "adapterListener";

    private static final String ARG_CHECKED_ITEMS = "checkedItems";
    private static final String ARG_MULTI_CHOICE_ITEMS = "multiChoiceItems";
    private static final String ARG_MULTI_CHOICE_LISTENER = "multiChoiceListener";

    private static final String ARG_CHECKED_ITEM = "checkedItem";
    private static final String ARG_SINGLE_CHOICE_ITEMS = "singleChoiceItems";
    private static final String ARG_SINGLE_CHOICE_ADAPTER = "singleChoiceAdapter";
    private static final String ARG_SINGLE_CHOICE_LISTENER = "singleChoiceListener";

    private static final String ARG_NEGATIVE_BUTTON = "negative";
    private static final String ARG_NEGATIVE_BUTTON_LISTENER = "negativeListener";

    private static final String ARG_NEUTRAL_BUTTON = "neutral";
    private static final String ARG_NEUTRAL_BUTTON_LISTENER = "neutralListener";

    private static final String ARG_POSITIVE_BUTTON = "positive";
    private static final String ARG_POSITIVE_BUTTON_LISTENER = "positiveListener";

    private static final String ARG_VIEW = "view";

    private static final String ARG_CANCEL_LISTENER = "cancelListener";
    private static final String ARG_DISMISS_LISTENER = "dismissListener";
    private static final String ARG_KEY_LISTENER = "keyListener";

    public static class Builder {

        private final Context mContext;
        private final Bundle mArguments = new Bundle();
        private boolean mCancelable = true;
        private Bundle mExtra;

        /**
         * Constructor using a context for this builder and the {@link NXAlertDialog} it creates.
         */
        public Builder(Context context) {
            this(context, VALUE_NULL);
        }

        /**
         * Constructor using a context and theme for this builder and
         * the {@link NXAlertDialog} it creates. The actual theme
         * that an AlertDialog uses is a private implementation, however you can
         * here supply either the name of an attribute in the theme from which
         * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme} or one of the
         * constants {@link NXAlertDialog#THEME_TRADITIONAL}, {@link NXAlertDialog#THEME_HOLO_DARK},
         * or {@link NXAlertDialog#THEME_HOLO_LIGHT}.
         */
        public Builder(Context context, int theme) {
            mContext = context.getApplicationContext();
            mArguments.putInt(ARG_THEME, theme);
        }

        /**
         * Creates a {@link NXAlertDialog} with the arguments supplied to this builder. It does not
         * {@link NXDialog#show} the dialog. This allows the user to do any extra processing
         * before displaying the dialog. Use {@link #show} if you don't have any other processing
         * to do and want this to be created and displayed.
         */
        public NXAlertDialog create() {
            final NXAlertDialog f = new NXAlertDialog();
            f.setFromBuilder(true);
            f.setArguments(mArguments);
            if (!mCancelable) {
                f.setCancelable(false);
            }
            if (mExtra != null) {
                f.setExtra(mExtra);
            }
            return f;
        }

        /**
         * Creates a {@link NXAlertDialog} with the arguments supplied to this builder and
         * {@link NXDialog#show}'s the dialog.
         */
        public void show(FragmentManager manager, String tag) {
            create().show(manager, tag);
        }

        /**
         * Creates a {@link NXAlertDialog} with the arguments supplied to this builder and
         * {@link NXDialog#show}'s the dialog.
         */
        public void show(FragmentTransaction transaction, String tag) {
            create().show(transaction, tag);
        }

        /**
         * Sets whether the dialog is cancelable or not. Default is true.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setCancelable(boolean cancelable) {
            mCancelable = cancelable;
            return this;
        }

        /**
         * Set the resource id of the {@link android.graphics.drawable.Drawable} to be used in the
         * title.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setIcon(int iconId) {
            mArguments.putInt(ARG_ICON, iconId);
            return this;
        }

        /**
         * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
         * 
         * @param attrId ID of a theme attribute that points to a drawable resource.
         */
        public Builder setIconAttribute(int attrId) {
            TypedValue out = new TypedValue();
            mContext.getTheme().resolveAttribute(attrId, out, true);
            setIcon(out.resourceId);
            return this;
        }

        /**
         * Sets the Dialog to use the inverse background, regardless of what the
         * contents is.
         * 
         * @param useInverseBackground Whether to use the inverse background
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setInverseBackgroundForced(boolean useInverseBackground) {
            mArguments.putInt(ARG_INVERSE_BACKGROUND, useInverseBackground ? VALUE_TRUE : VALUE_FALSE);
            return this;
        }

        /**
         * Sets an extra object.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         * @see NXDialog#setExtra(android.os.Bundle)
         */
        public Builder setExtra(Bundle extra) {
            mExtra = extra;
            return this;
        }

        /**
         * Set the title displayed in the {@link Dialog}.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTitle(CharSequence title) {
            mArguments.putCharSequence(ARG_TITLE, title);
            return this;
        }

        /**
         * Set the title using the given resource id.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setTitle(int resId) {
            mArguments.putCharSequence(ARG_TITLE, mContext.getText(resId));
            return this;
        }

        /**
         * Set the title using the custom view {@code customTitleView}. The
         * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
         * sufficient for most titles, but this is provided if the title needs
         * more customization. Using this will replace the title and icon set
         * via the other methods.
         * 
         * @param customTitleView The custom view to use as the title.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & TitleViewDelegate> Builder setCustomTitle(T customTitleView) {
            putArgument(ARG_CUSTOM_TITLE, customTitleView);
            return this;
        }

        /**
         * Set the title using the custom view {@code customTitleView}. The
         * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
         * sufficient for most titles, but this is provided if the title needs
         * more customization. Using this will replace the title and icon set
         * via the other methods.
         * 
         * @param customTitleView The custom view to use as the title.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & TitleViewDelegate> Builder setCustomTitle(T customTitleView) {
            putArgument(ARG_CUSTOM_TITLE, customTitleView);
            return this;
        }

        /**
         * Set the message to display.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(CharSequence message) {
            mArguments.putCharSequence(ARG_MESSAGE, message);
            return this;
        }

        /**
         * Set the message to display using the given resource id.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setMessage(int resId) {
            return setMessage(mContext.getText(resId));
        }

        /**
         * Set a custom view to be the contents of the Dialog. If the supplied view is an instance
         * of a {@link android.widget.ListView} the light background will be used.
         * 
         * @param view The {@link ViewDelegate} to use as the contents of the Dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & ViewDelegate> Builder setView(T view) {
            putArgument(ARG_VIEW, view);
            return this;
        }

        /**
         * Set a custom view to be the contents of the Dialog. If the supplied view is an instance
         * of a {@link android.widget.ListView} the light background will be used.
         * 
         * @param view The {@link ViewDelegate} to use as the contents of the Dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & ViewDelegate> Builder setView(T view) {
            putArgument(ARG_VIEW, view);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the
         * selected item via the supplied listener.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnChoiceClickListener> Builder setItems(CharSequence[] items, T listener) {
            mArguments.putCharSequenceArray(ARG_ITEMS, items);
            putArgument(ARG_ITEMS_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the
         * selected item via the supplied listener. This should be an array type i.e. R.array.foo
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnChoiceClickListener> Builder setItems(int itemsId, T listener) {
            return setItems(mContext.getResources().getTextArray(itemsId), listener);
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the
         * selected item via the supplied listener.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnChoiceClickListener> Builder setItems(CharSequence[] items, T listener) {
            mArguments.putCharSequenceArray(ARG_ITEMS, items);
            putArgument(ARG_ITEMS_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the
         * selected item via the supplied listener. This should be an array type i.e. R.array.foo
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */

        public <T extends Activity & OnChoiceClickListener> Builder setItems(int itemsId, T listener) {
            return setItems(mContext.getResources().getTextArray(itemsId), listener);
        }

        /**
         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
         * displayed in the dialog as the content, you will be notified of the
         * selected item via the supplied listener.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param listener The listener that will be called when an item is clicked.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Fragment & ListAdapterDelegate, TListener extends Fragment & OnChoiceClickListener> Builder setAdapter(
                TAdapter adapter, TListener listener) {
            putArgument(ARG_ADAPTER, adapter);
            putArgument(ARG_ADAPTER_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
         * displayed in the dialog as the content, you will be notified of the
         * selected item via the supplied listener.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param listener The listener that will be called when an item is clicked.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Activity & ListAdapterDelegate, TListener extends Activity & OnChoiceClickListener> Builder setAdapter(
                TAdapter adapter, TListener listener) {
            putArgument(ARG_ADAPTER, adapter);
            putArgument(ARG_ADAPTER_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
         * displayed in the dialog as the content, you will be notified of the
         * selected item via the supplied listener.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param listener The listener that will be called when an item is clicked.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Fragment & ListAdapterDelegate, TListener extends Activity & OnChoiceClickListener> Builder setAdapter(
                TAdapter adapter, TListener listener) {
            putArgument(ARG_ADAPTER, adapter);
            putArgument(ARG_ADAPTER_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
         * displayed in the dialog as the content, you will be notified of the
         * selected item via the supplied listener.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param listener The listener that will be called when an item is clicked.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Activity & ListAdapterDelegate, TListener extends Fragment & OnChoiceClickListener> Builder setAdapter(
                TAdapter adapter, TListener listener) {
            putArgument(ARG_ADAPTER, adapter);
            putArgument(ARG_ADAPTER_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content,
         * you will be notified of the selected item via the supplied listener.
         * The list will have a check mark displayed to the right of the text
         * for each checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param items the text of the items to be displayed in the list.
         * @param checkedItems specifies which items are checked. It should be null in which case no
         *        items are checked. If non null it must be exactly the same length as the array of
         *        items.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnMultiChoiceClickListener> Builder setMultiChoiceItems(CharSequence[] items,
                boolean[] checkedItems, T listener) {
            mArguments.putCharSequenceArray(ARG_MULTI_CHOICE_ITEMS, items);
            mArguments.putBooleanArray(ARG_CHECKED_ITEMS, checkedItems);
            putArgument(ARG_MULTI_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content,
         * you will be notified of the selected item via the supplied listener.
         * This should be an array type, e.g. R.array.foo. The list will have
         * a check mark displayed to the right of the text for each checked
         * item. Clicking on an item in the list will not dismiss the dialog.
         * Clicking on a button will dismiss the dialog.
         * 
         * @param itemsId the resource id of an array i.e. R.array.foo
         * @param checkedItems specifies which items are checked. It should be null in which case no
         *        items are checked. If non null it must be exactly the same length as the array of
         *        items.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnMultiChoiceClickListener> Builder setMultiChoiceItems(int itemsId,
                boolean[] checkedItems, T listener) {
            return setMultiChoiceItems(mContext.getResources().getTextArray(itemsId), checkedItems, listener);
        }

        /**
         * Set a list of items to be displayed in the dialog as the content,
         * you will be notified of the selected item via the supplied listener.
         * The list will have a check mark displayed to the right of the text
         * for each checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param items the text of the items to be displayed in the list.
         * @param checkedItems specifies which items are checked. It should be null in which case no
         *        items are checked. If non null it must be exactly the same length as the array of
         *        items.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnMultiChoiceClickListener> Builder setMultiChoiceItems(CharSequence[] items,
                boolean[] checkedItems, T listener) {
            mArguments.putCharSequenceArray(ARG_MULTI_CHOICE_ITEMS, items);
            mArguments.putBooleanArray(ARG_CHECKED_ITEMS, checkedItems);
            putArgument(ARG_MULTI_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content,
         * you will be notified of the selected item via the supplied listener.
         * This should be an array type, e.g. R.array.foo. The list will have
         * a check mark displayed to the right of the text for each checked
         * item. Clicking on an item in the list will not dismiss the dialog.
         * Clicking on a button will dismiss the dialog.
         * 
         * @param itemsId the resource id of an array i.e. R.array.foo
         * @param checkedItems specifies which items are checked. It should be null in which case no
         *        items are checked. If non null it must be exactly the same length as the array of
         *        items.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnMultiChoiceClickListener> Builder setMultiChoiceItems(int itemsId,
                boolean[] checkedItems, T listener) {
            return setMultiChoiceItems(mContext.getResources().getTextArray(itemsId), checkedItems, listener);
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param items the items to be displayed.
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnChoiceClickListener> Builder setSingleChoiceItems(CharSequence[] items,
                int checkedItem, T listener) {
            mArguments.putCharSequenceArray(ARG_SINGLE_CHOICE_ITEMS, items);
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. This should be an array type i.e.
         * R.array.foo The list will have a check mark displayed to the right of the text for the
         * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
         * button will dismiss the dialog.
         * 
         * @param itemsId the resource id of an array i.e. R.array.foo
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnChoiceClickListener> Builder setSingleChoiceItems(int itemsId,
                int checkedItem, T listener) {
            return setSingleChoiceItems(mContext.getResources().getTextArray(itemsId), checkedItem, listener);
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param items the items to be displayed.
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnChoiceClickListener> Builder setSingleChoiceItems(CharSequence[] items,
                int checkedItem, T listener) {
            mArguments.putCharSequenceArray(ARG_SINGLE_CHOICE_ITEMS, items);
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. This should be an array type i.e.
         * R.array.foo The list will have a check mark displayed to the right of the text for the
         * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
         * button will dismiss the dialog.
         * 
         * @param itemsId the resource id of an array i.e. R.array.foo
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnChoiceClickListener> Builder setSingleChoiceItems(int itemsId,
                int checkedItem, T listener) {
            return setSingleChoiceItems(mContext.getResources().getTextArray(itemsId), checkedItem, listener);
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Fragment & ListAdapterDelegate, TListener extends Fragment & OnChoiceClickListener> Builder setSingleChoiceItems(
                TAdapter adapter, int checkedItem, TListener listener) {
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_ADAPTER, adapter);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Activity & ListAdapterDelegate, TListener extends Activity & OnChoiceClickListener> Builder setSingleChoiceItems(
                TAdapter adapter, int checkedItem, TListener listener) {
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_ADAPTER, adapter);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Fragment & ListAdapterDelegate, TListener extends Activity & OnChoiceClickListener> Builder setSingleChoiceItems(
                TAdapter adapter, int checkedItem, TListener listener) {
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_ADAPTER, adapter);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a list of items to be displayed in the dialog as the content, you will be notified of
         * the selected item via the supplied listener. The list will have a check mark displayed to
         * the right of the text for the checked item. Clicking on an item in the list will not
         * dismiss the dialog. Clicking on a button will dismiss the dialog.
         * 
         * @param adapter The {@link ListAdapterDelegate} to supply the list of items
         * @param checkedItem specifies which item is checked. If -1 no items are checked.
         * @param listener notified when an item on the list is clicked. The dialog will not be
         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <TAdapter extends Activity & ListAdapterDelegate, TListener extends Fragment & OnChoiceClickListener> Builder setSingleChoiceItems(
                TAdapter adapter, int checkedItem, TListener listener) {
            mArguments.putInt(ARG_CHECKED_ITEM, checkedItem);
            putArgument(ARG_SINGLE_CHOICE_ADAPTER, adapter);
            putArgument(ARG_SINGLE_CHOICE_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * 
         * @param text The text to display in the negative button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setNegativeButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_NEGATIVE_BUTTON, text);
            putArgument(ARG_NEGATIVE_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the negative button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setNegativeButton(int textId, T listener) {
            return setNegativeButton(mContext.getText(textId), listener);
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * 
         * @param text The text to display in the negative button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setNegativeButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_NEGATIVE_BUTTON, text);
            putArgument(ARG_NEGATIVE_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the negative button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the negative button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setNegativeButton(int textId, T listener) {
            return setNegativeButton(mContext.getText(textId), listener);
        }

        /**
         * Set a negative button.
         * 
         * @param text The text to display in the negative button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNegativeButton(CharSequence text) {
            mArguments.putCharSequence(ARG_NEGATIVE_BUTTON, text);
            return this;
        }

        /**
         * Set a negative button.
         * 
         * @param textId The resource id of the text to display in the negative button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNegativeButton(int textId) {
            return setNegativeButton(mContext.getText(textId));
        }

        /**
         * Set a listener to be invoked when the neutral button of the dialog is pressed.
         * 
         * @param text The text to display in the neutral button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setNeutralButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_NEUTRAL_BUTTON, text);
            putArgument(ARG_NEUTRAL_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the neutral button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the neutral button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setNeutralButton(int textId, T listener) {
            return setNeutralButton(mContext.getText(textId), listener);
        }

        /**
         * Set a listener to be invoked when the neutral button of the dialog is pressed.
         * 
         * @param text The text to display in the neutral button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setNeutralButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_NEUTRAL_BUTTON, text);
            putArgument(ARG_NEUTRAL_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the neutral button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the neutral button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setNeutralButton(int textId, T listener) {
            return setNeutralButton(mContext.getText(textId), listener);
        }

        /**
         * Set a neutral button.
         * 
         * @param text The resource id of the text to display in the neutral button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNeutralButton(CharSequence text) {
            mArguments.putCharSequence(ARG_NEUTRAL_BUTTON, text);
            return this;
        }

        /**
         * Set a positive button.
         * 
         * @param textId The resource id of the text to display in the neutral button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setNeutralButton(int textId) {
            return setNeutralButton(mContext.getText(textId));
        }

        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * 
         * @param text The text to display in the positive button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setPositiveButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_POSITIVE_BUTTON, text);
            putArgument(ARG_POSITIVE_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the positive button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnClickListener> Builder setPositiveButton(int textId, T listener) {
            return setPositiveButton(mContext.getText(textId), listener);
        }

        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * 
         * @param text The text to display in the positive button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setPositiveButton(CharSequence text, T listener) {
            mArguments.putCharSequence(ARG_POSITIVE_BUTTON, text);
            putArgument(ARG_POSITIVE_BUTTON_LISTENER, listener);
            return this;
        }

        /**
         * Set a listener to be invoked when the positive button of the dialog is pressed.
         * 
         * @param textId The resource id of the text to display in the positive button
         * @param listener The {@link OnClickListener} to use.
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnClickListener> Builder setPositiveButton(int textId, T listener) {
            return setPositiveButton(mContext.getText(textId), listener);
        }

        /**
         * Set a positive button.
         * 
         * @param text The text to display in the positive button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPositiveButton(CharSequence text) {
            mArguments.putCharSequence(ARG_POSITIVE_BUTTON, text);
            return this;
        }

        /**
         * Set a positive button.
         * 
         * @param textId The resource id of the text to display in the positive button
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public Builder setPositiveButton(int textId) {
            return setPositiveButton(mContext.getText(textId));
        }

        /**
         * Sets the callback that will be called if the dialog is canceled.
         * <p/>
         * <p>
         * Even in a cancelable dialog, the dialog may be dismissed for reasons other than being
         * canceled or one of the supplied choices being selected. If you are interested in
         * listening for all cases where the dialog is dismissed and not just when it is canceled,
         * see {@link #setOnDismissListener(android.support.v4.app.Fragment)}.
         * </p>
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         * @see #setCancelable(boolean)
         * @see #setOnDismissListener(android.support.v4.app.Fragment)
         */
        public <T extends Fragment & OnCancelListener> Builder setOnCancelListener(T listener) {
            putArgument(ARG_CANCEL_LISTENER, listener);
            return this;
        }

        /**
         * Sets the callback that will be called if the dialog is canceled.
         * <p/>
         * <p>
         * Even in a cancelable dialog, the dialog may be dismissed for reasons other than being
         * canceled or one of the supplied choices being selected. If you are interested in
         * listening for all cases where the dialog is dismissed and not just when it is canceled,
         * see {@link #setOnDismissListener(android.app.Activity)}.
         * </p>
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         * @see #setCancelable(boolean)
         * @see #setOnDismissListener(android.app.Activity)
         */
        public <T extends Activity & OnCancelListener> Builder setOnCancelListener(T listener) {
            putArgument(ARG_CANCEL_LISTENER, listener);
            return this;
        }

        /**
         * Sets the callback that will be called when the dialog is dismissed for any reason.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnDismissListener> Builder setOnDismissListener(T listener) {
            putArgument(ARG_DISMISS_LISTENER, listener);
            return this;
        }

        /**
         * Sets the callback that will be called when the dialog is dismissed for any reason.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnDismissListener> Builder setOnDismissListener(T listener) {
            putArgument(ARG_DISMISS_LISTENER, listener);
            return this;
        }

        /**
         * Sets the callback that will be called if a key is dispatched to the dialog.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Fragment & OnKeyListener> Builder setOnKeyListener(T listener) {
            putArgument(ARG_KEY_LISTENER, listener);
            return this;
        }

        /**
         * Sets the callback that will be called if a key is dispatched to the dialog.
         * 
         * @return This Builder object to allow for chaining of calls to set methods
         */
        public <T extends Activity & OnKeyListener> Builder setOnKeyListener(T listener) {
            putArgument(ARG_KEY_LISTENER, listener);
            return this;
        }

        //
        // Helper
        //

        private void putArgument(String key, Fragment fragment) {
            if ((fragment != null) && (fragment.getTag() != null)) {
                mArguments.putString(key, TAG_FRAGMENT + fragment.getTag());
            }
        }

        private void putArgument(String key, Activity activity) {
            if (activity != null) {
                mArguments.putString(key, TAG_ACTIVITY);
            }
        }
    }

    private static final String SAVED_BUILDER_FLAG = "nx:alertDialog.builder";

    boolean mFromBuilder;

    public NXAlertDialog() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            return;
        }

        mFromBuilder = savedInstanceState.getBoolean(SAVED_BUILDER_FLAG);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (!mFromBuilder) {
            throw new RuntimeException("Use NXAlertDialog$Builder");
        }

        final Bundle args = getArguments();
        final int theme = args.getInt(ARG_THEME);

        AlertDialog.Builder builder;
        if ((theme == VALUE_NULL) || (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)) {
            builder = new AlertDialog.Builder(getActivity());
        } else {
            builder = newDialogBuilder(theme);
        }

        final CharSequence title = args.getCharSequence(ARG_TITLE);
        if (title == null) {
            setCustomTitle(builder);
        } else {
            builder.setTitle(title);
        }

        final CharSequence message = args.getCharSequence(ARG_MESSAGE);
        if (message != null) {
            builder.setMessage(message);
        }

        final int iconId = args.getInt(ARG_ICON, VALUE_NULL);
        if (iconId != VALUE_NULL) {
            builder.setIcon(iconId);
        }

        final int useInverseBackground = args.getInt(ARG_INVERSE_BACKGROUND);
        if (useInverseBackground != VALUE_NULL) {
            builder.setInverseBackgroundForced(useInverseBackground == VALUE_TRUE);
        }

        // View
        setView(builder);

        // List
        setItems(builder);
        setAdapter(builder);
        setMultiChoiceItems(builder);
        setSingleChoiceItems(builder);

        // Buttons
        setPositiveButton(builder);
        setNegativeButton(builder);
        setNeutralButton(builder);

        return builder.create();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_BUILDER_FLAG, mFromBuilder);
    }

    protected void setFromBuilder(boolean fromBuilder) {
        mFromBuilder = fromBuilder;
    }

    private void setCustomTitle(AlertDialog.Builder builder) {
        final TitleViewDelegate delegate = findListenerByTag(TitleViewDelegate.class, ARG_CUSTOM_TITLE);
        if (delegate == null) {
            return;
        }
        builder.setCustomTitle(delegate.getTitleView(this));
    }

    private void setView(AlertDialog.Builder builder) {
        final ViewDelegate delegate = findListenerByTag(ViewDelegate.class, ARG_VIEW);
        if (delegate == null) {
            return;
        }
        builder.setView(delegate.getView(this));
    }

    private void setAdapter(AlertDialog.Builder builder) {
        final ListAdapterDelegate delegate = findListenerByTag(ListAdapterDelegate.class, ARG_ADAPTER);
        if (delegate == null) {
            return;
        }
        builder.setAdapter(delegate.getListAdapter(this), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final OnChoiceClickListener listener = findListenerByTag(OnChoiceClickListener.class,
                        ARG_ADAPTER_LISTENER);
                listener.onClick(NXAlertDialog.this, which);
            }
        });
    }

    private void setSingleChoiceItems(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence[] items = args.getCharSequenceArray(ARG_SINGLE_CHOICE_ITEMS);
        final int checkedItem = args.getInt(ARG_CHECKED_ITEM);
        if (items != null) {
            builder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    final OnChoiceClickListener listener = findListenerByTag(OnChoiceClickListener.class,
                            ARG_SINGLE_CHOICE_LISTENER);
                    if (listener != null) {
                        listener.onClick(NXAlertDialog.this, which);
                    }
                }
            });
            return;
        }

        final ListAdapterDelegate delegate = findListenerByTag(ListAdapterDelegate.class,
                ARG_SINGLE_CHOICE_ADAPTER);
        if (delegate == null) {
            return;
        }
        builder.setSingleChoiceItems(delegate.getListAdapter(this), checkedItem,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        final OnChoiceClickListener listener = findListenerByTag(OnChoiceClickListener.class,
                                ARG_SINGLE_CHOICE_LISTENER);
                        if (listener != null) {
                            listener.onClick(NXAlertDialog.this, which);
                        }
                    }
                });
    }

    private void setPositiveButton(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence positiveButtonText = args.getCharSequence(ARG_POSITIVE_BUTTON);
        if (positiveButtonText == null) {
            return;
        }
        builder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final OnClickListener listener = findListenerByTag(OnClickListener.class,
                        ARG_POSITIVE_BUTTON_LISTENER);
                if (listener != null) {
                    listener.onClickPositive(NXAlertDialog.this);
                }
            }
        });
    }

    private void setNeutralButton(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence naturalButtonText = args.getCharSequence(ARG_NEUTRAL_BUTTON);
        if (naturalButtonText == null) {
            return;
        }
        builder.setNeutralButton(naturalButtonText, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final OnClickListener listener = findListenerByTag(OnClickListener.class,
                        ARG_NEUTRAL_BUTTON_LISTENER);
                if (listener != null) {
                    listener.onClickNeutral(NXAlertDialog.this);
                }
            }
        });
    }

    private void setNegativeButton(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence negativeButtonText = args.getCharSequence(ARG_NEGATIVE_BUTTON);
        if (negativeButtonText == null) {
            return;
        }
        builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final OnClickListener listener = findListenerByTag(OnClickListener.class,
                        ARG_NEGATIVE_BUTTON_LISTENER);
                if (listener != null) {
                    listener.onClickNegative(NXAlertDialog.this);
                }
            }
        });
    }

    private void setItems(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence[] items = args.getCharSequenceArray(ARG_ITEMS);
        if (items == null) {
            return;
        }
        builder.setItems(items, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                final OnChoiceClickListener listener = findListenerByTag(OnChoiceClickListener.class,
                        ARG_ITEMS_LISTENER);
                if (listener != null) {
                    listener.onClick(NXAlertDialog.this, which);
                }
            }
        });
    }

    private void setMultiChoiceItems(AlertDialog.Builder builder) {
        final Bundle args = getArguments();
        final CharSequence[] items = args.getCharSequenceArray(ARG_MULTI_CHOICE_ITEMS);
        final boolean[] checked = args.getBooleanArray(ARG_CHECKED_ITEMS);
        if ((items == null) || (checked == null) || (items.length != checked.length)) {
            return;
        }
        builder.setMultiChoiceItems(items, checked, new DialogInterface.OnMultiChoiceClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                final OnMultiChoiceClickListener listener = findListenerByTag(OnMultiChoiceClickListener.class,
                        ARG_MULTI_CHOICE_LISTENER);
                if (listener != null) {
                    listener.onClick(NXAlertDialog.this, which, isChecked);
                }
            }
        });
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private AlertDialog.Builder newDialogBuilder(int theme) {
        return new AlertDialog.Builder(getActivity(), theme);
    }

    /**
     * Find listener/delegate from {@link #getArguments()}.
     */
    private <T> T findListenerByTag(Class<T> clss, String argName) {
        return findListenerByTag(clss, getArguments(), argName);
    }
}