ShadowActivity.java revision c6526af59ebcc818c95680ac6f8efe83a0b65644
1package com.xtremelabs.robolectric.shadows;
2
3import android.app.Activity;
4import android.app.Application;
5import android.content.ComponentName;
6import android.content.Intent;
7import android.content.IntentSender;
8import android.view.LayoutInflater;
9import android.view.MenuInflater;
10import android.view.View;
11import android.view.Window;
12import com.xtremelabs.robolectric.Robolectric;
13import com.xtremelabs.robolectric.internal.Implementation;
14import com.xtremelabs.robolectric.internal.Implements;
15import com.xtremelabs.robolectric.internal.RealObject;
16import com.xtremelabs.robolectric.tester.android.view.TestWindow;
17
18import java.lang.reflect.InvocationTargetException;
19import java.lang.reflect.Method;
20import java.util.ArrayList;
21import java.util.HashMap;
22import java.util.List;
23import java.util.Map;
24
25import static com.xtremelabs.robolectric.Robolectric.shadowOf;
26
27
28@SuppressWarnings({"UnusedDeclaration"})
29@Implements(Activity.class)
30public class ShadowActivity extends ShadowContextWrapper {
31    @RealObject private Activity realActivity;
32
33    private Intent intent;
34    View contentView;
35    private int orientation;
36    private int resultCode;
37    private Intent resultIntent;
38    private Intent startServiceIntent;
39    private Activity parent;
40    private boolean finishWasCalled;
41    private TestWindow window;
42
43    private IntentSender startIntentSenderIntent;
44    private Intent startIntentSenderFillInIntent;
45    private int startIntentSenderFlagsMask;
46    private int startIntentSenderFlagsValues;
47    private int startIntentSenderExtraFlags;
48    private boolean startIntentSenderShouldThrowException = false;
49
50    private List<IntentForResult> startedActivitiesForResults = new ArrayList<IntentForResult>();
51
52    private Map<Intent, Integer> intentRequestCodeMap = new HashMap<Intent, Integer>();
53
54    @Implementation
55    public final Application getApplication() {
56        return Robolectric.application;
57    }
58
59    @Override @Implementation
60    public final Application getApplicationContext() {
61        return getApplication();
62    }
63
64    @Implementation
65    public void setIntent(Intent intent) {
66        this.intent = intent;
67    }
68
69    @Implementation
70    public Intent getIntent() {
71        return intent;
72    }
73
74    @Implementation
75    public int getRequestedOrientation() {
76    	return orientation;
77    }
78
79    @Implementation
80    public void setRequestedOrientation( int requestedOrientation ) {
81    	orientation = requestedOrientation;
82    }
83
84
85    /**
86     * Sets the {@code contentView} for this {@code Activity} by invoking the
87     * {@link android.view.LayoutInflater}
88     *
89     * @param layoutResID ID of the layout to inflate
90     * @see #getContentView()
91     */
92    @Implementation
93    public void setContentView(int layoutResID) {
94        contentView = getLayoutInflater().inflate(layoutResID, null);
95        realActivity.onContentChanged();
96    }
97
98    @Implementation
99    public void setContentView(View view) {
100        contentView = view;
101        realActivity.onContentChanged();
102    }
103
104    @Implementation
105    public final void setResult(int resultCode) {
106        this.resultCode = resultCode;
107    }
108
109    @Implementation
110    public final void setResult(int resultCode, Intent data) {
111        this.resultCode = resultCode;
112        this.resultIntent = data;
113    }
114
115    @Implementation
116    public LayoutInflater getLayoutInflater() {
117        return LayoutInflater.from(realActivity);
118    }
119
120    @Implementation
121    public MenuInflater getMenuInflater() {
122        return new MenuInflater(realActivity);
123    }
124
125    /**
126     * Checks to ensure that the{@code contentView} has been set
127     *
128     * @param id ID of the view to find
129     * @return the view
130     * @throws RuntimeException if the {@code contentView} has not been called first
131     */
132    @Implementation
133    public View findViewById(int id) {
134        if (contentView != null) {
135            return contentView.findViewById(id);
136        } else {
137            System.out.println("WARNING: you probably should have called setContentView() first");
138            Thread.dumpStack();
139            return null;
140        }
141    }
142
143    @Implementation
144    public final Activity getParent() {
145        return parent;
146    }
147
148    @Implementation
149    public void finish() {
150        finishWasCalled = true;
151    }
152
153    /**
154     * @return whether {@link #finish()} was called
155     */
156    @Implementation
157    public boolean isFinishing() {
158        return finishWasCalled;
159    }
160
161    /**
162     * Constructs a new Window (a {@link com.xtremelabs.robolectric.tester.android.view.TestWindow}) if no window has previously been
163     * set.
164     *
165     * @return the window associated with this Activity
166     */
167    @Implementation
168    public Window getWindow() {
169        if (window == null) {
170            window = new TestWindow(realActivity);
171        }
172        return window;
173    }
174
175    @Implementation
176    public void runOnUiThread(Runnable action) {
177        Robolectric.getUiThreadScheduler().post(action);
178    }
179
180    /**
181     * Checks to see if {@code BroadcastListener}s are still registered.
182     *
183     * @throws RuntimeException if any listeners are still registered
184     * @see #assertNoBroadcastListenersRegistered()
185     */
186    @Implementation
187    public void onDestroy() {
188        assertNoBroadcastListenersRegistered();
189    }
190
191    /**
192     * Checks the {@code ApplicationContext} to see if {@code BroadcastListener}s are still registered.
193     *
194     * @throws RuntimeException if any listeners are still registered
195     * @see ShadowApplication#assertNoBroadcastListenersRegistered(android.content.Context, String)
196     */
197    public void assertNoBroadcastListenersRegistered() {
198        shadowOf(getApplicationContext()).assertNoBroadcastListenersRegistered(realActivity, "Activity");
199    }
200
201    /**
202     * Non-Android accessor.
203     *
204     * @return the {@code contentView} set by one of the {@code setContentView()} methods
205     */
206    public View getContentView() {
207        return contentView;
208    }
209
210    /**
211     * Non-Android accessor.
212     *
213     * @return the {@code resultCode} set by one of the {@code setResult()} methods
214     */
215    public int getResultCode() {
216        return resultCode;
217    }
218
219    /**
220     * Non-Android accessor.
221     *
222     * @return the {@code Intent} set by {@link #setResult(int, android.content.Intent)}
223     */
224    public Intent getResultIntent() {
225        return resultIntent;
226    }
227
228    /**
229     * Non-Android accessor consumes and returns the next {@code Intent} on the
230     * started activities for results stack.
231     *
232     * @return the next started {@code Intent} for an activity, wrapped in
233     *         an {@link ShadowActivity.IntentForResult} object
234     */
235    public IntentForResult getNextStartedActivityForResult() {
236        if (startedActivitiesForResults.isEmpty()) {
237            return null;
238        } else {
239            return startedActivitiesForResults.remove(0);
240        }
241    }
242
243    /**
244     * Non-Android accessor returns the most recent {@code Intent} started by
245     * {@link #startActivityForResult(android.content.Intent, int)} without
246     * consuming it.
247     *
248     * @return the most recently started {@code Intent}, wrapped in
249     *         an {@link ShadowActivity.IntentForResult} object
250     */
251    public IntentForResult peekNextStartedActivityForResult() {
252        if (startedActivitiesForResults.isEmpty()) {
253            return null;
254        } else {
255            return startedActivitiesForResults.get(0);
256        }
257    }
258
259    /**
260     * Container object to hold an Intent, together with the requestCode used
261     * in a call to {@code Activity#startActivityForResult(Intent, int)}
262     */
263    public class IntentForResult {
264        public Intent intent;
265        public int requestCode;
266
267        public IntentForResult(Intent intent, int requestCode) {
268            this.intent = intent;
269            this.requestCode = requestCode;
270        }
271    }
272
273    @Implementation
274    public void startActivityForResult(Intent intent, int requestCode) {
275        intentRequestCodeMap.put(intent, requestCode);
276        startedActivitiesForResults.add(new IntentForResult(intent, requestCode));
277        getApplicationContext().startActivity(intent);
278    }
279
280    public void receiveResult(Intent requestIntent, int resultCode, Intent resultIntent) {
281        Integer requestCode = intentRequestCodeMap.get(requestIntent);
282        if (requestCode == null) {
283            throw new RuntimeException("No intent matches " + requestIntent + " among " + intentRequestCodeMap.keySet());
284        }
285        try {
286            Method method = Activity.class.getDeclaredMethod("onActivityResult", Integer.TYPE, Integer.TYPE, Intent.class);
287            method.setAccessible(true);
288            method.invoke(realActivity, requestCode, resultCode, resultIntent);
289        } catch (IllegalAccessException e) {
290            throw new RuntimeException(e);
291        } catch (InvocationTargetException e) {
292            throw new RuntimeException(e);
293        } catch (NoSuchMethodException e) {
294            throw new RuntimeException(e);
295        }
296    }
297
298    @Implementation
299    public ComponentName startService(Intent service) {
300    	startServiceIntent = service;
301    	return null;
302    }
303
304    /**
305     * Non-Android accessor.
306     *
307     * @return the {@code Intent} set by {@link #startService(android.content.Intent)}
308     */
309    public Intent getStartServiceIntent() {
310    	return startServiceIntent;
311    }
312
313    @Implementation
314    public void startIntentSender (IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
315    	throws IntentSender.SendIntentException {
316        startIntentSenderIntent = intent;
317        startIntentSenderFillInIntent = fillInIntent;
318        startIntentSenderFlagsMask = flagsMask;
319        startIntentSenderFlagsValues = flagsValues;
320        startIntentSenderExtraFlags = extraFlags;
321
322        if (startIntentSenderShouldThrowException) {
323        	throw new IntentSender.SendIntentException();
324        }
325    }
326
327    public void setStartIntentSenderShouldThrowException(boolean flag) {
328    	startIntentSenderShouldThrowException = flag;
329    }
330
331    public IntentSender getStartIntentSenderIntent() {
332    	return startIntentSenderIntent;
333    }
334
335    public Intent getStartIntentSenderFillInIntent() {
336    	return startIntentSenderFillInIntent;
337    }
338
339    public int getStartIntentSenderFlagsMask() {
340    	return startIntentSenderFlagsMask;
341    }
342
343    public int getStartIntentSenderFlagsValues() {
344    	return startIntentSenderFlagsValues;
345    }
346
347    public int getStartIntentSenderExtraFlags() {
348    	return startIntentSenderExtraFlags;
349    }
350}
351