ShadowActivity.java revision 6c243b00fee6298c384d10f2cf97508d24237633
1package com.xtremelabs.robolectric.shadows; 2 3import android.app.Activity; 4import android.app.Application; 5import android.app.Dialog; 6import android.content.Context; 7import android.content.Intent; 8import android.os.Bundle; 9import android.view.KeyEvent; 10import android.view.LayoutInflater; 11import android.view.MenuInflater; 12import android.view.View; 13import android.view.Window; 14import android.view.WindowManager; 15import android.widget.FrameLayout; 16import com.xtremelabs.robolectric.Robolectric; 17import com.xtremelabs.robolectric.internal.Implementation; 18import com.xtremelabs.robolectric.internal.Implements; 19import com.xtremelabs.robolectric.internal.RealObject; 20import com.xtremelabs.robolectric.tester.android.view.TestWindow; 21 22import java.lang.reflect.InvocationTargetException; 23import java.lang.reflect.Method; 24import java.util.ArrayList; 25import java.util.HashMap; 26import java.util.List; 27import java.util.Map; 28 29import static com.xtremelabs.robolectric.Robolectric.shadowOf; 30 31 32@SuppressWarnings({"UnusedDeclaration"}) 33@Implements(Activity.class) 34public class ShadowActivity extends ShadowContextWrapper { 35 @RealObject 36 private Activity realActivity; 37 38 private Intent intent; 39 View contentView; 40 private int orientation; 41 private int resultCode; 42 private Intent resultIntent; 43 private Activity parent; 44 private boolean finishWasCalled; 45 private TestWindow window; 46 47 private List<IntentForResult> startedActivitiesForResults = new ArrayList<IntentForResult>(); 48 49 private Map<Intent, Integer> intentRequestCodeMap = new HashMap<Intent, Integer>(); 50 private int requestedOrientation = -1; 51 private View currentFocus; 52 private Integer lastShownDialogId = null; 53 private int pendingTransitionEnterAnimResId = -1; 54 private int pendingTransitionExitAnimResId = -1; 55 private Object lastNonConfigurationInstance; 56 private Map<Integer, Dialog> dialogForId = new HashMap<Integer, Dialog>(); 57 private CharSequence title; 58 private boolean onKeyUpWasCalled; 59 60 @Implementation 61 public final Application getApplication() { 62 return Robolectric.application; 63 } 64 65 @Override 66 @Implementation 67 public final Application getApplicationContext() { 68 return getApplication(); 69 } 70 71 @Implementation 72 public void setIntent(Intent intent) { 73 this.intent = intent; 74 } 75 76 @Implementation 77 public Intent getIntent() { 78 return intent; 79 } 80 81 @Implementation(i18nSafe = false) 82 public void setTitle(CharSequence title) { 83 this.title = title; 84 } 85 86 @Implementation 87 public void setTitle(int titleId) { 88 this.title = this.getResources().getString(titleId); 89 } 90 91 @Implementation 92 public CharSequence getTitle() { 93 return title; 94 } 95 96 /** 97 * Sets the {@code contentView} for this {@code Activity} by invoking the 98 * {@link android.view.LayoutInflater} 99 * 100 * @param layoutResID ID of the layout to inflate 101 * @see #getContentView() 102 */ 103 @Implementation 104 public void setContentView(int layoutResID) { 105 contentView = getLayoutInflater().inflate(layoutResID, new FrameLayout(realActivity)); 106 realActivity.onContentChanged(); 107 } 108 109 @Implementation 110 public void setContentView(View view) { 111 contentView = view; 112 realActivity.onContentChanged(); 113 } 114 115 @Implementation 116 public final void setResult(int resultCode) { 117 this.resultCode = resultCode; 118 } 119 120 @Implementation 121 public final void setResult(int resultCode, Intent data) { 122 this.resultCode = resultCode; 123 this.resultIntent = data; 124 } 125 126 @Implementation 127 public LayoutInflater getLayoutInflater() { 128 return LayoutInflater.from(realActivity); 129 } 130 131 @Implementation 132 public MenuInflater getMenuInflater() { 133 return new MenuInflater(realActivity); 134 } 135 136 /** 137 * Checks to ensure that the{@code contentView} has been set 138 * 139 * @param id ID of the view to find 140 * @return the view 141 * @throws RuntimeException if the {@code contentView} has not been called first 142 */ 143 @Implementation 144 public View findViewById(int id) { 145 if (contentView != null) { 146 return contentView.findViewById(id); 147 } else { 148 System.out.println("WARNING: you probably should have called setContentView() first"); 149 Thread.dumpStack(); 150 return null; 151 } 152 } 153 154 @Implementation 155 public final Activity getParent() { 156 return parent; 157 } 158 159 @Implementation 160 public void onBackPressed() { 161 finish(); 162 } 163 164 @Implementation 165 public void finish() { 166 finishWasCalled = true; 167 } 168 169 /** 170 * @return whether {@link #finish()} was called 171 */ 172 @Implementation 173 public boolean isFinishing() { 174 return finishWasCalled; 175 } 176 177 /** 178 * Constructs a new Window (a {@link com.xtremelabs.robolectric.tester.android.view.TestWindow}) if no window has previously been 179 * set. 180 * 181 * @return the window associated with this Activity 182 */ 183 @Implementation 184 public Window getWindow() { 185 if (window == null) { 186 window = new TestWindow(realActivity); 187 } 188 return window; 189 } 190 191 @Implementation 192 public void runOnUiThread(Runnable action) { 193 Robolectric.getUiThreadScheduler().post(action); 194 } 195 196 /** 197 * Checks to see if {@code BroadcastListener}s are still registered. 198 * 199 * @throws RuntimeException if any listeners are still registered 200 * @see #assertNoBroadcastListenersRegistered() 201 */ 202 @Implementation 203 public void onDestroy() { 204 assertNoBroadcastListenersRegistered(); 205 } 206 207 @Implementation 208 public WindowManager getWindowManager() { 209 return (WindowManager) Robolectric.application.getSystemService(Context.WINDOW_SERVICE); 210 } 211 212 @Implementation 213 public void setRequestedOrientation(int requestedOrientation) { 214 this.requestedOrientation = requestedOrientation; 215 } 216 217 @Implementation 218 public int getRequestedOrientation() { 219 return requestedOrientation; 220 } 221 222 /** 223 * Checks the {@code ApplicationContext} to see if {@code BroadcastListener}s are still registered. 224 * 225 * @throws RuntimeException if any listeners are still registered 226 * @see ShadowApplication#assertNoBroadcastListenersRegistered(android.content.Context, String) 227 */ 228 public void assertNoBroadcastListenersRegistered() { 229 shadowOf(getApplicationContext()).assertNoBroadcastListenersRegistered(realActivity, "Activity"); 230 } 231 232 /** 233 * Non-Android accessor. 234 * 235 * @return the {@code contentView} set by one of the {@code setContentView()} methods 236 */ 237 public View getContentView() { 238 return contentView; 239 } 240 241 /** 242 * Non-Android accessor. 243 * 244 * @return the {@code resultCode} set by one of the {@code setResult()} methods 245 */ 246 public int getResultCode() { 247 return resultCode; 248 } 249 250 /** 251 * Non-Android accessor. 252 * 253 * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)} 254 */ 255 public Intent getResultIntent() { 256 return resultIntent; 257 } 258 259 /** 260 * Non-Android accessor consumes and returns the next {@code Intent} on the 261 * started activities for results stack. 262 * 263 * @return the next started {@code Intent} for an activity, wrapped in 264 * an {@link ShadowActivity.IntentForResult} object 265 */ 266 public IntentForResult getNextStartedActivityForResult() { 267 if (startedActivitiesForResults.isEmpty()) { 268 return null; 269 } else { 270 return startedActivitiesForResults.remove(0); 271 } 272 } 273 274 /** 275 * Non-Android accessor returns the most recent {@code Intent} started by 276 * {@link #startActivityForResult(android.content.Intent, int)} without 277 * consuming it. 278 * 279 * @return the most recently started {@code Intent}, wrapped in 280 * an {@link ShadowActivity.IntentForResult} object 281 */ 282 public IntentForResult peekNextStartedActivityForResult() { 283 if (startedActivitiesForResults.isEmpty()) { 284 return null; 285 } else { 286 return startedActivitiesForResults.get(0); 287 } 288 } 289 290 @Implementation 291 public Object getLastNonConfigurationInstance() { 292 return lastNonConfigurationInstance; 293 } 294 295 public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) { 296 this.lastNonConfigurationInstance = lastNonConfigurationInstance; 297 } 298 299 /** 300 * Non-Android accessor Sets the {@code View} for this {@code Activity} 301 * 302 * @param view 303 */ 304 public void setCurrentFocus(View view) { 305 currentFocus = view; 306 } 307 308 @Implementation 309 public View getCurrentFocus() { 310 return currentFocus; 311 } 312 313 @Implementation 314 public boolean onKeyUp(int keyCode, KeyEvent event) { 315 onKeyUpWasCalled = true; 316 if (keyCode == KeyEvent.KEYCODE_BACK) { 317 onBackPressed(); 318 return true; 319 } 320 return false; 321 } 322 323 public boolean onKeyUpWasCalled() { 324 return onKeyUpWasCalled; 325 } 326 327 public void resetKeyUpWasCalled() { 328 onKeyUpWasCalled = false; 329 } 330 331 /** 332 * Container object to hold an Intent, together with the requestCode used 333 * in a call to {@code Activity#startActivityForResult(Intent, int)} 334 */ 335 public class IntentForResult { 336 public Intent intent; 337 public int requestCode; 338 339 public IntentForResult(Intent intent, int requestCode) { 340 this.intent = intent; 341 this.requestCode = requestCode; 342 } 343 } 344 345 @Implementation 346 public void startActivityForResult(Intent intent, int requestCode) { 347 intentRequestCodeMap.put(intent, requestCode); 348 startedActivitiesForResults.add(new IntentForResult(intent, requestCode)); 349 getApplicationContext().startActivity(intent); 350 } 351 352 public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) { 353 Integer requestCode = intentRequestCodeMap.get(requestIntent); 354 if (requestCode == null) { 355 throw new RuntimeException("No intent matches " + requestIntent + " among " + intentRequestCodeMap.keySet()); 356 } 357 try { 358 Method method = Activity.class.getDeclaredMethod("onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class); 359 method.setAccessible(true); 360 method.invoke(realActivity, requestCode, resultCode, resultIntent); 361 } catch (IllegalAccessException e) { 362 throw new RuntimeException(e); 363 } catch (InvocationTargetException e) { 364 throw new RuntimeException(e); 365 } catch (NoSuchMethodException e) { 366 throw new RuntimeException(e); 367 } 368 } 369 370 @Implementation 371 public final void showDialog(int id) { 372 showDialog(id, null); 373 } 374 375 @Implementation 376 public final void removeDialog(int id) { 377 dialogForId.remove(id); 378 } 379 380 @Implementation 381 public final boolean showDialog(int id, Bundle bundle) { 382 Dialog dialog = null; 383 this.lastShownDialogId = id; 384 385 dialog = dialogForId.get(id); 386 387 if (dialog == null) { 388 try { 389 Method method = Activity.class.getDeclaredMethod("onCreateDialog", Integer.TYPE); 390 method.setAccessible(true); 391 dialog = (Dialog) method.invoke(realActivity, id); 392 393 if (bundle == null) { 394 method = Activity.class.getDeclaredMethod("onPrepareDialog", Integer.TYPE, Dialog.class); 395 method.setAccessible(true); 396 method.invoke(realActivity, id, dialog); 397 } else { 398 method = Activity.class.getDeclaredMethod("onPrepareDialog", Integer.TYPE, Dialog.class, Bundle.class); 399 method.setAccessible(true); 400 method.invoke(realActivity, id, dialog, bundle); 401 } 402 } catch (IllegalAccessException e) { 403 throw new RuntimeException(e); 404 } catch (InvocationTargetException e) { 405 throw new RuntimeException(e); 406 } catch (NoSuchMethodException e) { 407 throw new RuntimeException(e); 408 } 409 410 dialogForId.put(id, dialog); 411 } 412 413 dialog.show(); 414 415 return true; 416 } 417 418 /** 419 * Non-Android accessor 420 * 421 * @return the dialog resource id passed into 422 * {@code Activity#showDialog(int, Bundle)} or {@code Activity#showDialog(int)} 423 */ 424 public Integer getLastShownDialogId() { 425 return lastShownDialogId; 426 } 427 428 public boolean hasCancelledPendingTransitions() { 429 return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0; 430 } 431 432 @Implementation 433 public void overridePendingTransition(int enterAnim, int exitAnim) { 434 pendingTransitionEnterAnimResId = enterAnim; 435 pendingTransitionExitAnimResId = exitAnim; 436 } 437 438 public Dialog getDialogById(int dialogId) { 439 return dialogForId.get(dialogId); 440 } 441} 442