package com.xtremelabs.robolectric.shadows; import android.R; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.view.View; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListAdapter; import android.widget.ListView; import com.xtremelabs.robolectric.Robolectric; import com.xtremelabs.robolectric.internal.Implementation; import com.xtremelabs.robolectric.internal.Implements; import com.xtremelabs.robolectric.internal.RealObject; import java.lang.reflect.Constructor; import static com.xtremelabs.robolectric.Robolectric.getShadowApplication; import static com.xtremelabs.robolectric.Robolectric.shadowOf; @SuppressWarnings({"UnusedDeclaration"}) @Implements(AlertDialog.class) public class ShadowAlertDialog extends ShadowDialog { @RealObject private AlertDialog realAlertDialog; private CharSequence[] items; private String message; private DialogInterface.OnClickListener clickListener; private boolean isMultiItem; private boolean isSingleItem; private DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener; private boolean[] checkedItems; private int checkedItemIndex; private Button positiveButton; private Button negativeButton; private Button neutralButton; private View view; private View customTitleView; private ListAdapter adapter; private ListView listView; /** * Non-Android accessor. * * @return the most recently created {@code AlertDialog}, or null if none has been created during this test run */ public static AlertDialog getLatestAlertDialog() { ShadowAlertDialog dialog = Robolectric.getShadowApplication().getLatestAlertDialog(); return dialog == null ? null : dialog.realAlertDialog; } @Override @Implementation public View findViewById(int viewId) { if(view == null) { return super.findViewById(viewId); } return view.findViewById(viewId); } @Implementation public void setView(View view) { this.view = view; } /** * Resets the tracking of the most recently created {@code AlertDialog} */ public static void reset() { getShadowApplication().setLatestAlertDialog(null); } /** * Simulates a click on the {@code Dialog} item indicated by {@code index}. Handles both multi- and single-choice dialogs, tracks which items are currently * checked and calls listeners appropriately. * * @param index the index of the item to click on */ public void clickOnItem(int index) { shadowOf(realAlertDialog.getListView()).performItemClick(index); } @Implementation public Button getButton(int whichButton) { switch (whichButton) { case AlertDialog.BUTTON_POSITIVE: return positiveButton; case AlertDialog.BUTTON_NEGATIVE: return negativeButton; case AlertDialog.BUTTON_NEUTRAL: return neutralButton; } throw new RuntimeException("Only positive, negative, or neutral button choices are recognized"); } @Implementation public void setButton(int whichButton, CharSequence text, DialogInterface.OnClickListener listener) { switch (whichButton) { case AlertDialog.BUTTON_POSITIVE: positiveButton = createButton(context, realAlertDialog, whichButton, text, listener); return; case AlertDialog.BUTTON_NEGATIVE: negativeButton = createButton(context, realAlertDialog, whichButton, text, listener); return; case AlertDialog.BUTTON_NEUTRAL: neutralButton = createButton(context, realAlertDialog, whichButton, text, listener); return; } throw new RuntimeException("Only positive, negative, or neutral button choices are recognized"); } private static Button createButton(final Context context, final DialogInterface dialog, final int which, CharSequence text, final DialogInterface.OnClickListener listener) { if (text == null && listener == null) { return null; } Button button = new Button(context); Robolectric.shadowOf(button).setText(text); // use shadow to skip // i18n-strict checking button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (listener != null) { listener.onClick(dialog, which); } dialog.dismiss(); } }); return button; } @Implementation public ListView getListView() { if (listView == null) { listView = new ListView(context); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { if (isMultiItem) { checkedItems[position] = !checkedItems[position]; multiChoiceClickListener.onClick(realAlertDialog, position, checkedItems[position]); } else { if (isSingleItem) { checkedItemIndex = position; } clickListener.onClick(realAlertDialog, position); } } }); } return listView; } /** * Non-Android accessor. * * @return the items that are available to be clicked on */ public CharSequence[] getItems() { return items; } public Adapter getAdapter() { return adapter; } /** * Non-Android accessor. * * @return the message displayed in the dialog */ public String getMessage() { return message; } @Implementation public void setMessage(CharSequence message) { this.message = (message == null ? null : message.toString()); } /** * Non-Android accessor. * * @return an array indicating which items are and are not clicked on a multi-choice dialog */ public boolean[] getCheckedItems() { return checkedItems; } /** * Non-Android accessor. * * @return return the index of the checked item clicked on a single-choice dialog */ public int getCheckedItemIndex() { return checkedItemIndex; } @Implementation public void show() { super.show(); if (items != null) { adapter = new ArrayAdapter(context, R.layout.simple_list_item_checked, R.id.text1, items); } if (adapter != null) { getListView().setAdapter(adapter); } getShadowApplication().setLatestAlertDialog(this); } /** * Non-Android accessor. * * @return return the view set with {@link ShadowAlertDialog.ShadowBuilder#setView(View)} */ public View getView() { return view; } /** * Non-Android accessor. * * @return return the view set with {@link ShadowAlertDialog.ShadowBuilder#setCustomTitle(View)} */ public View getCustomTitleView() { return customTitleView; } /** * Shadows the {@code android.app.AlertDialog.Builder} class. */ @Implements(AlertDialog.Builder.class) public static class ShadowBuilder { @RealObject private AlertDialog.Builder realBuilder; private CharSequence[] items; private ListAdapter adapter; private DialogInterface.OnClickListener clickListener; private DialogInterface.OnCancelListener cancelListener; private String title; private String message; private Context context; private boolean isMultiItem; private DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener; private boolean[] checkedItems; private CharSequence positiveText; private DialogInterface.OnClickListener positiveListener; private CharSequence negativeText; private DialogInterface.OnClickListener negativeListener; private CharSequence neutralText; private DialogInterface.OnClickListener neutralListener; private boolean isCancelable; private boolean isSingleItem; private int checkedItem; private View view; private View customTitleView; /** * just stashes the context for later use * * @param context the context */ public void __constructor__(Context context) { this.context = context; } /** * 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 */ @Implementation public AlertDialog.Builder setItems(int itemsId, final DialogInterface.OnClickListener listener) { this.isMultiItem = false; this.items = context.getResources().getTextArray(itemsId); this.clickListener = listener; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener) { this.isMultiItem = false; this.items = items; this.clickListener = listener; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface.OnClickListener listener) { this.isSingleItem = true; this.checkedItem = checkedItem; this.items = items; this.clickListener = listener; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface.OnClickListener listener) { this.isSingleItem = true; this.checkedItem = checkedItem; this.items = null; this.adapter = adapter; this.clickListener = listener; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final DialogInterface.OnMultiChoiceClickListener listener) { this.isMultiItem = true; this.items = items; this.multiChoiceClickListener = listener; if (checkedItems == null) { checkedItems = new boolean[items.length]; } else if (checkedItems.length != items.length) { throw new IllegalArgumentException("checkedItems must be the same length as items, or pass null to specify no checked items"); } this.checkedItems = checkedItems; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setTitle(CharSequence title) { this.title = title.toString(); return realBuilder; } @Implementation public AlertDialog.Builder setCustomTitle(android.view.View customTitleView) { this.customTitleView = customTitleView; return realBuilder; } @Implementation public AlertDialog.Builder setTitle(int titleId) { return setTitle(context.getResources().getString(titleId)); } @Implementation(i18nSafe=false) public AlertDialog.Builder setMessage(CharSequence message) { this.message = message.toString(); return realBuilder; } @Implementation public AlertDialog.Builder setMessage(int messageId) { setMessage(context.getResources().getString(messageId)); return realBuilder; } @Implementation public AlertDialog.Builder setIcon(int iconId) { return realBuilder; } @Implementation public AlertDialog.Builder setView(View view) { this.view = view; return realBuilder; } @Implementation(i18nSafe=false) public AlertDialog.Builder setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener) { this.positiveText = text; this.positiveListener = listener; return realBuilder; } @Implementation public AlertDialog.Builder setPositiveButton(int positiveTextId, final DialogInterface.OnClickListener listener) { return setPositiveButton(context.getResources().getText(positiveTextId), listener); } @Implementation(i18nSafe=false) public AlertDialog.Builder setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener) { this.negativeText = text; this.negativeListener = listener; return realBuilder; } @Implementation public AlertDialog.Builder setNegativeButton(int negativeTextId, final DialogInterface.OnClickListener listener) { return setNegativeButton(context.getResources().getString(negativeTextId), listener); } @Implementation(i18nSafe=false) public AlertDialog.Builder setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener) { this.neutralText = text; this.neutralListener = listener; return realBuilder; } @Implementation public AlertDialog.Builder setNeutralButton(int neutralTextId, final DialogInterface.OnClickListener listener) { return setNeutralButton(context.getResources().getText(neutralTextId), listener); } @Implementation public AlertDialog.Builder setCancelable(boolean cancelable) { this.isCancelable = cancelable; return realBuilder; } @Implementation public AlertDialog.Builder setOnCancelListener(DialogInterface.OnCancelListener listener) { this.cancelListener = listener; return realBuilder; } @Implementation public AlertDialog create() { AlertDialog realDialog; try { Constructor c = AlertDialog.class.getDeclaredConstructor(Context.class); c.setAccessible(true); realDialog = c.newInstance((Context) null); } catch (Exception e) { throw new RuntimeException(e); } ShadowAlertDialog latestAlertDialog = shadowOf(realDialog); latestAlertDialog.context = context; latestAlertDialog.items = items; latestAlertDialog.adapter = adapter; latestAlertDialog.setTitle(title); latestAlertDialog.message = message; latestAlertDialog.clickListener = clickListener; latestAlertDialog.setOnCancelListener(cancelListener); latestAlertDialog.isMultiItem = isMultiItem; latestAlertDialog.isSingleItem = isSingleItem; latestAlertDialog.checkedItemIndex = checkedItem; latestAlertDialog.multiChoiceClickListener = multiChoiceClickListener; latestAlertDialog.checkedItems = checkedItems; latestAlertDialog.setView(view); latestAlertDialog.positiveButton = createButton(context, realDialog, AlertDialog.BUTTON_POSITIVE, positiveText, positiveListener); latestAlertDialog.negativeButton = createButton(context, realDialog, AlertDialog.BUTTON_NEGATIVE, negativeText, negativeListener); latestAlertDialog.neutralButton = createButton(context, realDialog, AlertDialog.BUTTON_NEUTRAL, neutralText, neutralListener); latestAlertDialog.setCancelable(isCancelable); latestAlertDialog.customTitleView = customTitleView; return realDialog; } @Implementation public AlertDialog show() { AlertDialog dialog = realBuilder.create(); dialog.show(); return dialog; } @Implementation public Context getContext() { return context; } } }