Home | History | Annotate | Download | only in shadows
      1 package com.xtremelabs.robolectric.shadows;
      2 
      3 import android.app.Activity;
      4 import android.app.Application;
      5 import android.app.Dialog;
      6 import android.content.Context;
      7 import android.content.Intent;
      8 import android.content.SharedPreferences;
      9 import android.database.Cursor;
     10 import android.os.Bundle;
     11 import android.view.*;
     12 import android.widget.FrameLayout;
     13 import com.xtremelabs.robolectric.Robolectric;
     14 import com.xtremelabs.robolectric.internal.Implementation;
     15 import com.xtremelabs.robolectric.internal.Implements;
     16 import com.xtremelabs.robolectric.internal.RealObject;
     17 import com.xtremelabs.robolectric.tester.android.view.TestWindow;
     18 
     19 import java.lang.reflect.InvocationTargetException;
     20 import java.lang.reflect.Method;
     21 import java.util.ArrayList;
     22 import java.util.HashMap;
     23 import java.util.List;
     24 import java.util.Map;
     25 
     26 import javassist.bytecode.Mnemonic;
     27 
     28 import static com.xtremelabs.robolectric.Robolectric.shadowOf;
     29 
     30 
     31 @SuppressWarnings({"UnusedDeclaration"})
     32 @Implements(Activity.class)
     33 public class ShadowActivity extends ShadowContextWrapper {
     34     @RealObject
     35     protected Activity realActivity;
     36 
     37     private Intent intent;
     38     private FrameLayout contentViewContainer;
     39     private 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     private ArrayList<Cursor> managedCusors = new ArrayList<Cursor>();
     60 
     61     @Implementation
     62     public final Application getApplication() {
     63         return Robolectric.application;
     64     }
     65 
     66     @Override
     67     @Implementation
     68     public final Application getApplicationContext() {
     69         return getApplication();
     70     }
     71 
     72     @Implementation
     73     public void setIntent(Intent intent) {
     74         this.intent = intent;
     75     }
     76 
     77     @Implementation
     78     public Intent getIntent() {
     79         return intent;
     80     }
     81 
     82     @Implementation(i18nSafe = false)
     83     public void setTitle(CharSequence title) {
     84         this.title = title;
     85     }
     86 
     87     @Implementation
     88     public void setTitle(int titleId) {
     89         this.title = this.getResources().getString(titleId);
     90     }
     91 
     92     @Implementation
     93     public CharSequence getTitle() {
     94         return title;
     95     }
     96 
     97     /**
     98      * Sets the {@code contentView} for this {@code Activity} by invoking the
     99      * {@link android.view.LayoutInflater}
    100      *
    101      * @param layoutResID ID of the layout to inflate
    102      * @see #getContentView()
    103      */
    104     @Implementation
    105     public void setContentView(int layoutResID) {
    106         contentView = getLayoutInflater().inflate(layoutResID, new FrameLayout(realActivity));
    107         realActivity.onContentChanged();
    108     }
    109 
    110     @Implementation
    111     public void setContentView(View view) {
    112         contentView = view;
    113         realActivity.onContentChanged();
    114     }
    115 
    116     @Implementation
    117     public final void setResult(int resultCode) {
    118         this.resultCode = resultCode;
    119     }
    120 
    121     @Implementation
    122     public final void setResult(int resultCode, Intent data) {
    123         this.resultCode = resultCode;
    124         this.resultIntent = data;
    125     }
    126 
    127     @Implementation
    128     public LayoutInflater getLayoutInflater() {
    129         return LayoutInflater.from(realActivity);
    130     }
    131 
    132     @Implementation
    133     public MenuInflater getMenuInflater() {
    134         return new MenuInflater(realActivity);
    135     }
    136 
    137     /**
    138      * Checks to ensure that the{@code contentView} has been set
    139      *
    140      * @param id ID of the view to find
    141      * @return the view
    142      * @throws RuntimeException if the {@code contentView} has not been called first
    143      */
    144     @Implementation
    145     public View findViewById(int id) {
    146         if (id == android.R.id.content) {
    147             return getContentViewContainer();
    148         }
    149         if (contentView != null) {
    150             return contentView.findViewById(id);
    151         } else {
    152             System.out.println("WARNING: you probably should have called setContentView() first");
    153             Thread.dumpStack();
    154             return null;
    155         }
    156     }
    157 
    158     private View getContentViewContainer() {
    159         if (contentViewContainer == null) {
    160             contentViewContainer = new FrameLayout(realActivity);
    161         }
    162         contentViewContainer.addView(contentView, 0);
    163         return contentViewContainer;
    164     }
    165 
    166     @Implementation
    167     public final Activity getParent() {
    168         return parent;
    169     }
    170 
    171     @Implementation
    172     public void onBackPressed() {
    173         finish();
    174     }
    175 
    176     @Implementation
    177     public void finish() {
    178         finishWasCalled = true;
    179     }
    180 
    181     public void resetIsFinishing() {
    182         finishWasCalled = false;
    183     }
    184 
    185     /**
    186      * @return whether {@link #finish()} was called
    187      */
    188     @Implementation
    189     public boolean isFinishing() {
    190         return finishWasCalled;
    191     }
    192 
    193     /**
    194      * Constructs a new Window (a {@link com.xtremelabs.robolectric.tester.android.view.TestWindow}) if no window has previously been
    195      * set.
    196      *
    197      * @return the window associated with this Activity
    198      */
    199     @Implementation
    200     public Window getWindow() {
    201         if (window == null) {
    202             window = new TestWindow(realActivity);
    203         }
    204         return window;
    205     }
    206 
    207     public void setWindow(TestWindow wind){
    208     	window = wind;
    209     }
    210 
    211     @Implementation
    212     public void runOnUiThread(Runnable action) {
    213         Robolectric.getUiThreadScheduler().post(action);
    214     }
    215 
    216     @Implementation
    217     public void onCreate(Bundle bundle) {
    218 
    219     }
    220 
    221     /**
    222      * Checks to see if {@code BroadcastListener}s are still registered.
    223      *
    224      * @throws RuntimeException if any listeners are still registered
    225      * @see #assertNoBroadcastListenersRegistered()
    226      */
    227     @Implementation
    228     public void onDestroy() {
    229         assertNoBroadcastListenersRegistered();
    230     }
    231 
    232     @Implementation
    233     public WindowManager getWindowManager() {
    234         return (WindowManager) Robolectric.application.getSystemService(Context.WINDOW_SERVICE);
    235     }
    236 
    237     @Implementation
    238     public void setRequestedOrientation(int requestedOrientation) {
    239         this.requestedOrientation = requestedOrientation;
    240     }
    241 
    242     @Implementation
    243     public int getRequestedOrientation() {
    244         return requestedOrientation;
    245     }
    246 
    247     @Implementation
    248     public SharedPreferences getPreferences(int mode) {
    249     	return ShadowPreferenceManager.getDefaultSharedPreferences(getApplicationContext());
    250     }
    251 
    252     /**
    253      * Checks the {@code ApplicationContext} to see if {@code BroadcastListener}s are still registered.
    254      *
    255      * @throws RuntimeException if any listeners are still registered
    256      * @see ShadowApplication#assertNoBroadcastListenersRegistered(android.content.Context, String)
    257      */
    258     public void assertNoBroadcastListenersRegistered() {
    259         shadowOf(getApplicationContext()).assertNoBroadcastListenersRegistered(realActivity, "Activity");
    260     }
    261 
    262     /**
    263      * Non-Android accessor.
    264      *
    265      * @return the {@code contentView} set by one of the {@code setContentView()} methods
    266      */
    267     public View getContentView() {
    268         return contentView;
    269     }
    270 
    271     /**
    272      * Non-Android accessor.
    273      *
    274      * @return the {@code resultCode} set by one of the {@code setResult()} methods
    275      */
    276     public int getResultCode() {
    277         return resultCode;
    278     }
    279 
    280     /**
    281      * Non-Android accessor.
    282      *
    283      * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
    284      */
    285     public Intent getResultIntent() {
    286         return resultIntent;
    287     }
    288 
    289     /**
    290      * Non-Android accessor consumes and returns the next {@code Intent} on the
    291      * started activities for results stack.
    292      *
    293      * @return the next started {@code Intent} for an activity, wrapped in
    294      *         an {@link ShadowActivity.IntentForResult} object
    295      */
    296     public IntentForResult getNextStartedActivityForResult() {
    297         if (startedActivitiesForResults.isEmpty()) {
    298             return null;
    299         } else {
    300             return startedActivitiesForResults.remove(0);
    301         }
    302     }
    303 
    304     /**
    305      * Non-Android accessor returns the most recent {@code Intent} started by
    306      * {@link #startActivityForResult(android.content.Intent, int)} without
    307      * consuming it.
    308      *
    309      * @return the most recently started {@code Intent}, wrapped in
    310      *         an {@link ShadowActivity.IntentForResult} object
    311      */
    312     public IntentForResult peekNextStartedActivityForResult() {
    313         if (startedActivitiesForResults.isEmpty()) {
    314             return null;
    315         } else {
    316             return startedActivitiesForResults.get(0);
    317         }
    318     }
    319 
    320     @Implementation
    321     public Object getLastNonConfigurationInstance() {
    322         return lastNonConfigurationInstance;
    323     }
    324 
    325     public void setLastNonConfigurationInstance(Object lastNonConfigurationInstance) {
    326         this.lastNonConfigurationInstance = lastNonConfigurationInstance;
    327     }
    328 
    329     /**
    330      * Non-Android accessor Sets the {@code View} for this {@code Activity}
    331      *
    332      * @param view
    333      */
    334     public void setCurrentFocus(View view) {
    335         currentFocus = view;
    336     }
    337 
    338     @Implementation
    339     public View getCurrentFocus() {
    340         if (currentFocus != null) {
    341             return currentFocus;
    342         } else if (contentView != null) {
    343             return contentView.findFocus();
    344         } else {
    345             return null;
    346         }
    347     }
    348 
    349     public void clearFocus() {
    350         currentFocus = null;
    351         if (contentView != null) {
    352             contentView.clearFocus();
    353         }
    354     }
    355 
    356     @Implementation
    357     public boolean onKeyUp(int keyCode, KeyEvent event) {
    358         onKeyUpWasCalled = true;
    359         if (keyCode == KeyEvent.KEYCODE_BACK) {
    360             onBackPressed();
    361             return true;
    362         }
    363         return false;
    364     }
    365 
    366     public boolean onKeyUpWasCalled() {
    367         return onKeyUpWasCalled;
    368     }
    369 
    370     public void resetKeyUpWasCalled() {
    371         onKeyUpWasCalled = false;
    372     }
    373 
    374     /**
    375      * Container object to hold an Intent, together with the requestCode used
    376      * in a call to {@code Activity#startActivityForResult(Intent, int)}
    377      */
    378     public class IntentForResult {
    379         public Intent intent;
    380         public int requestCode;
    381 
    382         public IntentForResult(Intent intent, int requestCode) {
    383             this.intent = intent;
    384             this.requestCode = requestCode;
    385         }
    386     }
    387 
    388     @Implementation
    389     public void startActivityForResult(Intent intent, int requestCode) {
    390         intentRequestCodeMap.put(intent, requestCode);
    391         startedActivitiesForResults.add(new IntentForResult(intent, requestCode));
    392         getApplicationContext().startActivity(intent);
    393     }
    394 
    395     public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) {
    396         Integer requestCode = intentRequestCodeMap.get(requestIntent);
    397         if (requestCode == null) {
    398             throw new RuntimeException("No intent matches " + requestIntent + " among " + intentRequestCodeMap.keySet());
    399         }
    400 
    401         final ActivityInvoker invoker = new ActivityInvoker();
    402         invoker.call("onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class)
    403             .with(requestCode, resultCode, resultIntent);
    404     }
    405 
    406     @Implementation
    407     public final void showDialog(int id) {
    408         showDialog(id, null);
    409     }
    410 
    411     @Implementation
    412     public final void dismissDialog(int id) {
    413         final Dialog dialog = dialogForId.get(id);
    414         if (dialog == null) {
    415             throw new IllegalArgumentException();
    416         }
    417 
    418         dialog.dismiss();
    419     }
    420 
    421     @Implementation
    422     public final void removeDialog(int id) {
    423         dialogForId.remove(id);
    424     }
    425 
    426     @Implementation
    427     public final boolean showDialog(int id, Bundle bundle) {
    428         Dialog dialog = null;
    429         this.lastShownDialogId = id;
    430 
    431         dialog = dialogForId.get(id);
    432 
    433         if (dialog == null) {
    434             final ActivityInvoker invoker = new ActivityInvoker();
    435             dialog = (Dialog) invoker.call("onCreateDialog", Integer.TYPE).with(id);
    436 
    437             if (bundle == null) {
    438                 invoker.call("onPrepareDialog", Integer.TYPE, Dialog.class)
    439                     .with(id, dialog);
    440             } else {
    441                 invoker.call("onPrepareDialog", Integer.TYPE, Dialog.class, Bundle.class)
    442                     .with(id, dialog, bundle);
    443             }
    444 
    445             dialogForId.put(id, dialog);
    446         }
    447 
    448         dialog.show();
    449 
    450         return true;
    451     }
    452 
    453     /**
    454      * Non-Android accessor
    455      *
    456      * @return the dialog resource id passed into
    457      *         {@code Activity#showDialog(int, Bundle)} or {@code Activity#showDialog(int)}
    458      */
    459     public Integer getLastShownDialogId() {
    460         return lastShownDialogId;
    461     }
    462 
    463     public boolean hasCancelledPendingTransitions() {
    464         return pendingTransitionEnterAnimResId == 0 && pendingTransitionExitAnimResId == 0;
    465     }
    466 
    467     @Implementation
    468     public void overridePendingTransition(int enterAnim, int exitAnim) {
    469         pendingTransitionEnterAnimResId = enterAnim;
    470         pendingTransitionExitAnimResId = exitAnim;
    471     }
    472 
    473     public Dialog getDialogById(int dialogId) {
    474         return dialogForId.get(dialogId);
    475     }
    476 
    477     public void create() {
    478         final ActivityInvoker invoker = new ActivityInvoker();
    479 
    480         final Bundle noInstanceState = null;
    481         invoker.call("onCreate", Bundle.class).with(noInstanceState);
    482         invoker.call("onStart").withNothing();
    483         invoker.call("onPostCreate", Bundle.class).with(noInstanceState);
    484         invoker.call("onResume").withNothing();
    485     }
    486 
    487     @Implementation
    488     public void recreate() {
    489         Bundle outState = new Bundle();
    490         final ActivityInvoker invoker = new ActivityInvoker();
    491 
    492         invoker.call("onSaveInstanceState", Bundle.class).with(outState);
    493         invoker.call("onPause").withNothing();
    494         invoker.call("onStop").withNothing();
    495 
    496         Object nonConfigInstance = invoker.call("onRetainNonConfigurationInstance").withNothing();
    497         setLastNonConfigurationInstance(nonConfigInstance);
    498 
    499         invoker.call("onDestroy").withNothing();
    500         invoker.call("onCreate", Bundle.class).with(outState);
    501         invoker.call("onStart").withNothing();
    502         invoker.call("onRestoreInstanceState", Bundle.class).with(outState);
    503         invoker.call("onResume").withNothing();
    504     }
    505 
    506     @Implementation
    507     public void startManagingCursor(Cursor c) {
    508     	managedCusors.add(c);
    509     }
    510 
    511     @Implementation
    512     public void stopManagingCursor(Cursor c) {
    513     	managedCusors.remove(c);
    514     }
    515 
    516     public List<Cursor> getManagedCursors() {
    517     	return managedCusors;
    518     }
    519 
    520     private final class ActivityInvoker {
    521         private Method method;
    522 
    523         public ActivityInvoker call(final String methodName, final Class ...argumentClasses) {
    524             try {
    525                 method = Activity.class.getDeclaredMethod(methodName, argumentClasses);
    526                 method.setAccessible(true);
    527                 return this;
    528             } catch(NoSuchMethodException e) {
    529                 throw new RuntimeException(e);
    530             }
    531         }
    532 
    533         public Object withNothing() {
    534             return with();
    535         }
    536 
    537         public Object with(final Object ...parameters) {
    538             try {
    539                 return method.invoke(realActivity, parameters);
    540             } catch(IllegalAccessException e) {
    541                 throw new RuntimeException(e);
    542             } catch(IllegalArgumentException e) {
    543                 throw new RuntimeException(e);
    544             } catch(InvocationTargetException e) {
    545                 throw new RuntimeException(e);
    546             }
    547         }
    548     }
    549 }
    550