ShadowAlertDialog.java revision 99fafb79bf98b7aa1946bbda1f0a225cefa2d35d
1package com.xtremelabs.robolectric.shadows; 2 3import android.R; 4import android.app.AlertDialog; 5import android.content.Context; 6import android.content.DialogInterface; 7import android.view.View; 8import android.widget.Adapter; 9import android.widget.AdapterView; 10import android.widget.ArrayAdapter; 11import android.widget.Button; 12import android.widget.ListAdapter; 13import android.widget.ListView; 14import com.xtremelabs.robolectric.Robolectric; 15import com.xtremelabs.robolectric.internal.Implementation; 16import com.xtremelabs.robolectric.internal.Implements; 17import com.xtremelabs.robolectric.internal.RealObject; 18 19import java.lang.reflect.Constructor; 20 21import static com.xtremelabs.robolectric.Robolectric.getShadowApplication; 22import static com.xtremelabs.robolectric.Robolectric.shadowOf; 23 24@SuppressWarnings({"UnusedDeclaration"}) 25@Implements(AlertDialog.class) 26public class ShadowAlertDialog extends ShadowDialog { 27 @RealObject 28 private AlertDialog realAlertDialog; 29 30 private CharSequence[] items; 31 private String message; 32 private DialogInterface.OnClickListener clickListener; 33 private boolean isMultiItem; 34 private boolean isSingleItem; 35 private DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener; 36 private boolean[] checkedItems; 37 private int checkedItemIndex; 38 private Button positiveButton; 39 private Button negativeButton; 40 private Button neutralButton; 41 private View view; 42 private View customTitleView; 43 private ListAdapter adapter; 44 private ListView listView; 45 46 /** 47 * Non-Android accessor. 48 * 49 * @return the most recently created {@code AlertDialog}, or null if none has been created during this test run 50 */ 51 public static AlertDialog getLatestAlertDialog() { 52 ShadowAlertDialog dialog = Robolectric.getShadowApplication().getLatestAlertDialog(); 53 return dialog == null ? null : dialog.realAlertDialog; 54 } 55 56 @Override 57 @Implementation 58 public View findViewById(int viewId) { 59 if(view == null) { 60 return super.findViewById(viewId); 61 } 62 63 return view.findViewById(viewId); 64 } 65 66 @Implementation 67 public void setView(View view) { 68 this.view = view; 69 } 70 71 /** 72 * Resets the tracking of the most recently created {@code AlertDialog} 73 */ 74 public static void reset() { 75 getShadowApplication().setLatestAlertDialog(null); 76 } 77 78 /** 79 * Simulates a click on the {@code Dialog} item indicated by {@code index}. Handles both multi- and single-choice dialogs, tracks which items are currently 80 * checked and calls listeners appropriately. 81 * 82 * @param index the index of the item to click on 83 */ 84 public void clickOnItem(int index) { 85 shadowOf(realAlertDialog.getListView()).performItemClick(index); 86 } 87 88 @Implementation 89 public Button getButton(int whichButton) { 90 switch (whichButton) { 91 case AlertDialog.BUTTON_POSITIVE: 92 return positiveButton; 93 case AlertDialog.BUTTON_NEGATIVE: 94 return negativeButton; 95 case AlertDialog.BUTTON_NEUTRAL: 96 return neutralButton; 97 } 98 throw new RuntimeException("Only positive, negative, or neutral button choices are recognized"); 99 } 100 101 @Implementation 102 public void setButton(int whichButton, CharSequence text, DialogInterface.OnClickListener listener) { 103 switch (whichButton) { 104 case AlertDialog.BUTTON_POSITIVE: 105 positiveButton = createButton(context, realAlertDialog, whichButton, text, listener); 106 return; 107 case AlertDialog.BUTTON_NEGATIVE: 108 negativeButton = createButton(context, realAlertDialog, whichButton, text, listener); 109 return; 110 case AlertDialog.BUTTON_NEUTRAL: 111 neutralButton = createButton(context, realAlertDialog, whichButton, text, listener); 112 return; 113 } 114 throw new RuntimeException("Only positive, negative, or neutral button choices are recognized"); 115 } 116 117 private static Button createButton(final Context context, final DialogInterface dialog, final int which, CharSequence text, final DialogInterface.OnClickListener listener) { 118 if (text == null && listener == null) { 119 return null; 120 } 121 Button button = new Button(context); 122 Robolectric.shadowOf(button).setText(text); // use shadow to skip 123 // i18n-strict checking 124 button.setOnClickListener(new View.OnClickListener() { 125 @Override 126 public void onClick(View v) { 127 if (listener != null) { 128 listener.onClick(dialog, which); 129 } 130 dialog.dismiss(); 131 } 132 }); 133 return button; 134 } 135 136 @Implementation 137 public ListView getListView() { 138 if (listView == null) { 139 listView = new ListView(context); 140 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 141 @Override 142 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 143 if (isMultiItem) { 144 checkedItems[position] = !checkedItems[position]; 145 multiChoiceClickListener.onClick(realAlertDialog, position, checkedItems[position]); 146 } else { 147 if (isSingleItem) { 148 checkedItemIndex = position; 149 } 150 clickListener.onClick(realAlertDialog, position); 151 } 152 } 153 }); 154 } 155 return listView; 156 } 157 158 /** 159 * Non-Android accessor. 160 * 161 * @return the items that are available to be clicked on 162 */ 163 public CharSequence[] getItems() { 164 return items; 165 } 166 167 public Adapter getAdapter() { 168 return adapter; 169 } 170 171 /** 172 * Non-Android accessor. 173 * 174 * @return the message displayed in the dialog 175 */ 176 public String getMessage() { 177 return message; 178 } 179 180 @Implementation 181 public void setMessage(CharSequence message) { 182 this.message = (message == null ? null : message.toString()); 183 } 184 185 /** 186 * Non-Android accessor. 187 * 188 * @return an array indicating which items are and are not clicked on a multi-choice dialog 189 */ 190 public boolean[] getCheckedItems() { 191 return checkedItems; 192 } 193 194 /** 195 * Non-Android accessor. 196 * 197 * @return return the index of the checked item clicked on a single-choice dialog 198 */ 199 public int getCheckedItemIndex() { 200 return checkedItemIndex; 201 } 202 203 @Implementation 204 public void show() { 205 super.show(); 206 if (items != null) { 207 adapter = new ArrayAdapter<CharSequence>(context, R.layout.simple_list_item_checked, R.id.text1, items); 208 } 209 210 if (adapter != null) { 211 getListView().setAdapter(adapter); 212 } 213 214 215 getShadowApplication().setLatestAlertDialog(this); 216 } 217 218 /** 219 * Non-Android accessor. 220 * 221 * @return return the view set with {@link ShadowAlertDialog.ShadowBuilder#setView(View)} 222 */ 223 public View getView() { 224 return view; 225 } 226 227 /** 228 * Non-Android accessor. 229 * 230 * @return return the view set with {@link ShadowAlertDialog.ShadowBuilder#setCustomTitle(View)} 231 */ 232 public View getCustomTitleView() { 233 return customTitleView; 234 } 235 236 /** 237 * Shadows the {@code android.app.AlertDialog.Builder} class. 238 */ 239 @Implements(AlertDialog.Builder.class) 240 public static class ShadowBuilder { 241 @RealObject 242 private AlertDialog.Builder realBuilder; 243 244 private CharSequence[] items; 245 private ListAdapter adapter; 246 private DialogInterface.OnClickListener clickListener; 247 private DialogInterface.OnCancelListener cancelListener; 248 private String title; 249 private String message; 250 private Context context; 251 private boolean isMultiItem; 252 private DialogInterface.OnMultiChoiceClickListener multiChoiceClickListener; 253 private boolean[] checkedItems; 254 private CharSequence positiveText; 255 private DialogInterface.OnClickListener positiveListener; 256 private CharSequence negativeText; 257 private DialogInterface.OnClickListener negativeListener; 258 private CharSequence neutralText; 259 private DialogInterface.OnClickListener neutralListener; 260 private boolean isCancelable; 261 private boolean isSingleItem; 262 private int checkedItem; 263 private View view; 264 private View customTitleView; 265 266 /** 267 * just stashes the context for later use 268 * 269 * @param context the context 270 */ 271 public void __constructor__(Context context) { 272 this.context = context; 273 } 274 275 /** 276 * 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 277 * an array type i.e. R.array.foo 278 * 279 * @return This Builder object to allow for chaining of calls to set methods 280 */ 281 @Implementation 282 public AlertDialog.Builder setItems(int itemsId, final DialogInterface.OnClickListener listener) { 283 this.isMultiItem = false; 284 285 this.items = context.getResources().getTextArray(itemsId); 286 this.clickListener = listener; 287 return realBuilder; 288 } 289 290 @Implementation(i18nSafe=false) 291 public AlertDialog.Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener) { 292 this.isMultiItem = false; 293 294 this.items = items; 295 this.clickListener = listener; 296 return realBuilder; 297 } 298 299 @Implementation(i18nSafe=false) 300 public AlertDialog.Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface.OnClickListener listener) { 301 this.isSingleItem = true; 302 this.checkedItem = checkedItem; 303 this.items = items; 304 this.clickListener = listener; 305 return realBuilder; 306 } 307 308 @Implementation(i18nSafe=false) 309 public AlertDialog.Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface.OnClickListener listener) { 310 this.isSingleItem = true; 311 this.checkedItem = checkedItem; 312 this.items = null; 313 this.adapter = adapter; 314 this.clickListener = listener; 315 return realBuilder; 316 } 317 318 @Implementation(i18nSafe=false) 319 public AlertDialog.Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final DialogInterface.OnMultiChoiceClickListener listener) { 320 this.isMultiItem = true; 321 322 this.items = items; 323 this.multiChoiceClickListener = listener; 324 325 if (checkedItems == null) { 326 checkedItems = new boolean[items.length]; 327 } else if (checkedItems.length != items.length) { 328 throw new IllegalArgumentException("checkedItems must be the same length as items, or pass null to specify no checked items"); 329 } 330 this.checkedItems = checkedItems; 331 332 return realBuilder; 333 } 334 335 @Implementation(i18nSafe=false) 336 public AlertDialog.Builder setTitle(CharSequence title) { 337 this.title = title.toString(); 338 return realBuilder; 339 } 340 341 342 @Implementation 343 public AlertDialog.Builder setCustomTitle(android.view.View customTitleView) { 344 this.customTitleView = customTitleView; 345 return realBuilder; 346 } 347 348 @Implementation 349 public AlertDialog.Builder setTitle(int titleId) { 350 return setTitle(context.getResources().getString(titleId)); 351 } 352 353 @Implementation(i18nSafe=false) 354 public AlertDialog.Builder setMessage(CharSequence message) { 355 this.message = message.toString(); 356 return realBuilder; 357 } 358 359 @Implementation 360 public AlertDialog.Builder setMessage(int messageId) { 361 setMessage(context.getResources().getString(messageId)); 362 return realBuilder; 363 } 364 365 @Implementation 366 public AlertDialog.Builder setIcon(int iconId) { 367 return realBuilder; 368 } 369 370 @Implementation 371 public AlertDialog.Builder setView(View view) { 372 this.view = view; 373 return realBuilder; 374 } 375 376 @Implementation(i18nSafe=false) 377 public AlertDialog.Builder setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener) { 378 this.positiveText = text; 379 this.positiveListener = listener; 380 return realBuilder; 381 } 382 383 @Implementation 384 public AlertDialog.Builder setPositiveButton(int positiveTextId, final DialogInterface.OnClickListener listener) { 385 return setPositiveButton(context.getResources().getText(positiveTextId), listener); 386 } 387 388 @Implementation(i18nSafe=false) 389 public AlertDialog.Builder setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener) { 390 this.negativeText = text; 391 this.negativeListener = listener; 392 return realBuilder; 393 } 394 395 @Implementation 396 public AlertDialog.Builder setNegativeButton(int negativeTextId, final DialogInterface.OnClickListener listener) { 397 return setNegativeButton(context.getResources().getString(negativeTextId), listener); 398 } 399 400 @Implementation(i18nSafe=false) 401 public AlertDialog.Builder setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener) { 402 this.neutralText = text; 403 this.neutralListener = listener; 404 return realBuilder; 405 } 406 407 @Implementation 408 public AlertDialog.Builder setNeutralButton(int neutralTextId, final DialogInterface.OnClickListener listener) { 409 return setNeutralButton(context.getResources().getText(neutralTextId), listener); 410 } 411 412 413 @Implementation 414 public AlertDialog.Builder setCancelable(boolean cancelable) { 415 this.isCancelable = cancelable; 416 return realBuilder; 417 } 418 419 @Implementation 420 public AlertDialog.Builder setOnCancelListener(DialogInterface.OnCancelListener listener) { 421 this.cancelListener = listener; 422 return realBuilder; 423 } 424 425 @Implementation 426 public AlertDialog create() { 427 AlertDialog realDialog; 428 try { 429 Constructor<AlertDialog> c = AlertDialog.class.getDeclaredConstructor(Context.class); 430 c.setAccessible(true); 431 realDialog = c.newInstance((Context) null); 432 } catch (Exception e) { 433 throw new RuntimeException(e); 434 } 435 436 ShadowAlertDialog latestAlertDialog = shadowOf(realDialog); 437 latestAlertDialog.context = context; 438 latestAlertDialog.items = items; 439 latestAlertDialog.adapter = adapter; 440 latestAlertDialog.setTitle(title); 441 latestAlertDialog.message = message; 442 latestAlertDialog.clickListener = clickListener; 443 latestAlertDialog.setOnCancelListener(cancelListener); 444 latestAlertDialog.isMultiItem = isMultiItem; 445 latestAlertDialog.isSingleItem = isSingleItem; 446 latestAlertDialog.checkedItemIndex = checkedItem; 447 latestAlertDialog.multiChoiceClickListener = multiChoiceClickListener; 448 latestAlertDialog.checkedItems = checkedItems; 449 latestAlertDialog.setView(view); 450 latestAlertDialog.positiveButton = createButton(context, realDialog, AlertDialog.BUTTON_POSITIVE, positiveText, positiveListener); 451 latestAlertDialog.negativeButton = createButton(context, realDialog, AlertDialog.BUTTON_NEGATIVE, negativeText, negativeListener); 452 latestAlertDialog.neutralButton = createButton(context, realDialog, AlertDialog.BUTTON_NEUTRAL, neutralText, neutralListener); 453 latestAlertDialog.setCancelable(isCancelable); 454 latestAlertDialog.customTitleView = customTitleView; 455 return realDialog; 456 } 457 458 @Implementation 459 public AlertDialog show() { 460 AlertDialog dialog = realBuilder.create(); 461 dialog.show(); 462 return dialog; 463 } 464 465 @Implementation 466 public Context getContext() { 467 return context; 468 } 469 } 470} 471