1package com.xtremelabs.robolectric.shadows;
2
3import android.app.Activity;
4import android.app.Dialog;
5import android.appwidget.AppWidgetProvider;
6import android.content.*;
7import android.content.pm.ActivityInfo;
8import android.database.CharArrayBuffer;
9import android.database.ContentObserver;
10import android.database.Cursor;
11import android.database.DataSetObserver;
12import android.database.sqlite.SQLiteCursor;
13import android.net.Uri;
14import android.os.Bundle;
15import android.view.KeyEvent;
16import android.view.View;
17import android.widget.FrameLayout;
18import com.xtremelabs.robolectric.ApplicationResolver;
19import com.xtremelabs.robolectric.R;
20import com.xtremelabs.robolectric.Robolectric;
21import com.xtremelabs.robolectric.WithTestDefaultsRunner;
22import com.xtremelabs.robolectric.util.TestRunnable;
23import com.xtremelabs.robolectric.util.Transcript;
24import org.hamcrest.CoreMatchers;
25import org.junit.Test;
26import org.junit.runner.RunWith;
27
28import java.util.concurrent.atomic.AtomicBoolean;
29
30import static com.xtremelabs.robolectric.Robolectric.shadowOf;
31import static com.xtremelabs.robolectric.util.TestUtil.assertInstanceOf;
32import static com.xtremelabs.robolectric.util.TestUtil.newConfig;
33import static org.hamcrest.CoreMatchers.*;
34import static org.hamcrest.core.StringStartsWith.startsWith;
35import static org.junit.Assert.*;
36
37@RunWith(WithTestDefaultsRunner.class)
38public class ActivityTest {
39    @Test(expected = IllegalStateException.class)
40    public void shouldComplainIfActivityIsDestroyedWithRegisteredBroadcastReceivers() throws Exception {
41        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
42        activity.registerReceiver(new AppWidgetProvider(), new IntentFilter());
43        activity.onDestroy();
44    }
45
46    @Test
47    public void shouldNotComplainIfActivityIsDestroyedWhileAnotherActivityHasRegisteredBroadcastReceivers() throws Exception {
48        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
49
50        DialogLifeCycleActivity activity2 = new DialogLifeCycleActivity();
51        activity2.registerReceiver(new AppWidgetProvider(), new IntentFilter());
52
53        activity.onDestroy(); // should not throw exception
54    }
55
56    @Test
57    public void shouldNotRegisterNullBroadcastReceiver() {
58        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
59        activity.registerReceiver(null, new IntentFilter());
60
61        activity.onDestroy();
62    }
63
64    @Test
65    public void startActivityForResultAndReceiveResult_shouldSendResponsesBackToActivity() throws Exception {
66        final Transcript transcript = new Transcript();
67        Activity activity = new Activity() {
68            @Override
69            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
70                transcript.add("onActivityResult called with requestCode " + requestCode + ", resultCode " + resultCode + ", intent data " + data.getData());
71            }
72        };
73        activity.startActivityForResult(new Intent().setType("audio/*"), 123);
74        activity.startActivityForResult(new Intent().setType("image/*"), 456);
75
76        shadowOf(activity).receiveResult(new Intent().setType("image/*"), Activity.RESULT_OK,
77                new Intent().setData(Uri.parse("content:foo")));
78        transcript.assertEventsSoFar("onActivityResult called with requestCode 456, resultCode -1, intent data content:foo");
79    }
80
81    @Test
82    public void startActivityForResultAndReceiveResult_whenNoIntentMatches_shouldThrowException() throws Exception {
83        Activity activity = new Activity() {
84            @Override
85            protected void onActivityResult(int requestCode, int resultCode, Intent data) {
86                throw new IllegalStateException("should not be called");
87            }
88        };
89        activity.startActivityForResult(new Intent().setType("audio/*"), 123);
90        activity.startActivityForResult(new Intent().setType("image/*"), 456);
91
92        Intent requestIntent = new Intent().setType("video/*");
93        try {
94            shadowOf(activity).receiveResult(requestIntent, Activity.RESULT_OK,
95                    new Intent().setData(Uri.parse("content:foo")));
96            fail();
97        } catch (Exception e) {
98            assertThat(e.getMessage(), startsWith("No intent matches " + requestIntent));
99        }
100    }
101
102    @Test
103    public void shouldSupportStartActivityForResult() throws Exception {
104        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
105        ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
106        Intent intent = new Intent().setClass(activity, DialogLifeCycleActivity.class);
107        assertThat(shadowActivity.getNextStartedActivity(), nullValue());
108
109        activity.startActivityForResult(intent, 142);
110
111        Intent startedIntent = shadowActivity.getNextStartedActivity();
112        assertThat(startedIntent, notNullValue());
113        assertThat(startedIntent, sameInstance(intent));
114    }
115
116    @Test
117    public void shouldSupportGetStartedActitivitesForResult() throws Exception {
118        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
119        ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
120        Intent intent = new Intent().setClass(activity, DialogLifeCycleActivity.class);
121
122        activity.startActivityForResult(intent, 142);
123
124        ShadowActivity.IntentForResult intentForResult = shadowActivity.getNextStartedActivityForResult();
125        assertThat(intentForResult, notNullValue());
126        assertThat(shadowActivity.getNextStartedActivityForResult(), nullValue());
127        assertThat(intentForResult.intent, notNullValue());
128        assertThat(intentForResult.intent, sameInstance(intent));
129        assertThat(intentForResult.requestCode, equalTo(142));
130    }
131
132    @Test
133    public void shouldSupportPeekStartedActitivitesForResult() throws Exception {
134        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
135        ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
136        Intent intent = new Intent().setClass(activity, DialogLifeCycleActivity.class);
137
138        activity.startActivityForResult(intent, 142);
139
140        ShadowActivity.IntentForResult intentForResult = shadowActivity.peekNextStartedActivityForResult();
141        assertThat(intentForResult, notNullValue());
142        assertThat(shadowActivity.peekNextStartedActivityForResult(), sameInstance(intentForResult));
143        assertThat(intentForResult.intent, notNullValue());
144        assertThat(intentForResult.intent, sameInstance(intent));
145        assertThat(intentForResult.requestCode, equalTo(142));
146    }
147
148    @Test
149    public void onContentChangedShouldBeCalledAfterContentViewIsSet() throws RuntimeException {
150        final Transcript transcript = new Transcript();
151        Activity customActivity = new Activity() {
152            @Override
153            public void onContentChanged() {
154                transcript.add("onContentChanged was called; title is \"" + shadowOf(findViewById(R.id.title)).innerText() + "\"");
155            }
156        };
157        customActivity.setContentView(R.layout.main);
158        transcript.assertEventsSoFar("onContentChanged was called; title is \"Main Layout\"");
159    }
160
161    @Test
162    public void shouldRetrievePackageNameFromTheManifest() throws Exception {
163        Robolectric.application = new ApplicationResolver(newConfig("TestAndroidManifestWithPackageName.xml")).resolveApplication();
164        assertThat("com.wacka.wa", equalTo(new Activity().getPackageName()));
165    }
166
167    @Test
168    public void shouldRunUiTasksImmediatelyByDefault() throws Exception {
169        TestRunnable runnable = new TestRunnable();
170        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
171        activity.runOnUiThread(runnable);
172        assertTrue(runnable.wasRun);
173    }
174
175    @Test
176    public void shouldQueueUiTasksWhenUiThreadIsPaused() throws Exception {
177        Robolectric.pauseMainLooper();
178
179        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
180        TestRunnable runnable = new TestRunnable();
181        activity.runOnUiThread(runnable);
182        assertFalse(runnable.wasRun);
183
184        Robolectric.unPauseMainLooper();
185        assertTrue(runnable.wasRun);
186    }
187
188    @Test
189    public void showDialog_shouldCreatePrepareAndShowDialog() {
190        final DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
191        final AtomicBoolean dialogWasShown = new AtomicBoolean(false);
192
193        new Dialog(activity) {
194            {
195                activity.dialog = this;
196            }
197
198            @Override
199            public void show() {
200                dialogWasShown.set(true);
201            }
202        };
203
204        activity.showDialog(1);
205
206        assertTrue(activity.createdDialog);
207        assertTrue(activity.preparedDialog);
208        assertTrue(dialogWasShown.get());
209    }
210
211    @Test
212    public void showDialog_shouldCreatePrepareAndShowDialogWithBundle() {
213        final DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
214        final AtomicBoolean dialogWasShown = new AtomicBoolean(false);
215
216        new Dialog(activity) {
217            {
218                activity.dialog = this;
219            }
220
221            @Override
222            public void show() {
223                dialogWasShown.set(true);
224            }
225        };
226
227        activity.showDialog(1, new Bundle());
228
229        assertTrue(activity.createdDialog);
230        assertTrue(activity.preparedDialogWithBundle);
231        assertTrue(dialogWasShown.get());
232    }
233
234    @Test
235    public void showDialog_shouldReuseDialogs() {
236        final DialogCreatingActivity activity = new DialogCreatingActivity();
237
238        activity.showDialog(1);
239
240        Dialog firstDialog = ShadowDialog.getLatestDialog();
241
242        activity.showDialog(1);
243
244        final Dialog secondDialog = ShadowDialog.getLatestDialog();
245
246        assertSame("dialogs should be the same instance", firstDialog, secondDialog);
247    }
248
249    @Test
250    public void showDialog_shouldShowDialog() throws Exception {
251        final DialogCreatingActivity activity = new DialogCreatingActivity();
252        activity.showDialog(1);
253        Dialog dialog = ShadowDialog.getLatestDialog();
254        assertTrue(dialog.isShowing());
255    }
256
257    @Test
258    public void dismissDialog_shouldDismissPreviouslyShownDialog() throws Exception {
259        final DialogCreatingActivity activity = new DialogCreatingActivity();
260        activity.showDialog(1);
261        activity.dismissDialog(1);
262        Dialog dialog = ShadowDialog.getLatestDialog();
263        assertFalse(dialog.isShowing());
264    }
265
266    @Test(expected = IllegalArgumentException.class)
267    public void dismissDialog_shouldThrowExceptionIfDialogWasNotPreviouslyShown() throws Exception {
268        final DialogCreatingActivity activity = new DialogCreatingActivity();
269        activity.dismissDialog(1);
270    }
271
272    @Test
273    public void removeDialog_shouldCreateDialogAgain() {
274        final DialogCreatingActivity activity = new DialogCreatingActivity();
275
276        activity.showDialog(1);
277        Dialog firstDialog = ShadowDialog.getLatestDialog();
278
279        activity.removeDialog(1);
280        assertNull(Robolectric.shadowOf(activity).getDialogById(1));
281
282        activity.showDialog(1);
283        Dialog secondDialog = ShadowDialog.getLatestDialog();
284
285        assertNotSame("dialogs should not be the same instance", firstDialog, secondDialog);
286    }
287
288    @Test
289    public void shouldCallOnCreateDialogFromShowDialog() {
290        ActivityWithOnCreateDialog activity = new ActivityWithOnCreateDialog();
291        activity.showDialog(123);
292        assertTrue(activity.onCreateDialogWasCalled);
293        assertThat(ShadowDialog.getLatestDialog(), CoreMatchers.<Object>notNullValue());
294    }
295
296    @Test
297    public void shouldCallFinishInOnBackPressed() {
298        Activity activity = new Activity();
299        activity.onBackPressed();
300
301        ShadowActivity shadowActivity = shadowOf(activity);
302        assertTrue(shadowActivity.isFinishing());
303    }
304
305    @Test
306    public void shouldSupportCurrentFocus() {
307        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
308        ShadowActivity shadow = shadowOf(activity);
309
310        assertNull(shadow.getCurrentFocus());
311        View view = new View(activity);
312        shadow.setCurrentFocus(view);
313        assertEquals(view, shadow.getCurrentFocus());
314    }
315
316    @Test
317    public void shouldSetOrientation() {
318        DialogLifeCycleActivity activity = new DialogLifeCycleActivity();
319        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
320        assertThat(activity.getRequestedOrientation(), equalTo(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT));
321    }
322
323    @Test
324    public void retrieveIdOfResource() {
325        Activity activity = new Activity();
326
327        int id1 = R.string.hello;
328        String string = activity.getString(id1);
329        assertEquals("Hello", string);
330
331        int id = activity.getResources().getIdentifier("hello", "string", "com.xtremelabs.robolectric");
332        assertTrue(id > 0);
333
334        String hello = activity.getResources().getString(id);
335        assertEquals("Hello", hello);
336    }
337
338    @Test
339    public void getCurrentFocus_shouldBeNullWhenNoFocusRequested() {
340        Activity activity = new Activity();
341        activity.setContentView(R.layout.main);
342
343        View focusedView = shadowOf(activity).getCurrentFocus();
344        assertNull(focusedView);
345    }
346
347    @Test
348    public void getCurrentFocus_shouldReturnSubViewAfterFocusedRequest() {
349        Activity activity = new Activity();
350        activity.setContentView(R.layout.main);
351
352        View view = activity.findViewById(R.id.button);
353        view.requestFocus();
354
355        View focusedView = shadowOf(activity).getCurrentFocus();
356        assertEquals(R.id.button, focusedView.getId());
357    }
358
359    @Test
360    public void retrieveIdOfNonExistingResource() {
361        Activity activity = new Activity();
362
363        int id = activity.getResources().getIdentifier("just_alot_of_crap", "string", "com.xtremelabs.robolectric");
364        assertTrue(id == 0);
365    }
366
367    @Test
368    public void shouldSetContentViewWithFrameLayoutAsParent() throws Exception {
369        Activity activity = new Activity();
370        activity.setContentView(R.layout.toplevel_merge);
371
372        View contentView = shadowOf(activity).getContentView();
373        assertInstanceOf(FrameLayout.class, contentView);
374        assertThat(((FrameLayout) contentView).getChildCount(), equalTo(2));
375    }
376
377    @Test
378    public void onKeyUp_recordsThatItWasCalled() throws Exception {
379        Activity activity = new Activity();
380        boolean consumed = activity.onKeyUp(KeyEvent.KEYCODE_0, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_0));
381
382        assertFalse(consumed);
383        assertTrue(shadowOf(activity).onKeyUpWasCalled());
384
385        shadowOf(activity).resetKeyUpWasCalled();
386        assertFalse(shadowOf(activity).onKeyUpWasCalled());
387    }
388
389    @Test
390    public void onKeyUp_callsOnBackPressedWhichFinishesTheActivity() throws Exception {
391        Activity activity = new Activity();
392        boolean consumed = activity.onKeyUp(KeyEvent.KEYCODE_BACK, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK));
393
394        assertTrue(consumed);
395        assertTrue(shadowOf(activity).onKeyUpWasCalled());
396        assertTrue(activity.isFinishing());
397    }
398
399    @Test
400    public void shouldGiveSharedPreferences() throws Exception {
401        Activity activity = new Activity();
402        SharedPreferences preferences = activity.getPreferences(Context.MODE_PRIVATE);
403        assertNotNull(preferences);
404        preferences.edit().putString("foo", "bar").commit();
405        assertThat(activity.getPreferences(Context.MODE_PRIVATE).getString("foo", null), equalTo("bar"));
406    }
407
408    @Test
409    public void shouldFindContentViewContainer() throws Exception {
410        Activity activity = new Activity();
411        View contentView = new View(activity);
412        activity.setContentView(contentView);
413
414        FrameLayout contentViewContainer = (FrameLayout) activity.findViewById(android.R.id.content);
415        assertThat(contentViewContainer.getChildAt(0), is(contentView));
416    }
417
418    @Test
419      public void createGoesThroughFullLifeCycle() throws Exception {
420        TestActivity activity = new TestActivity();
421
422        shadowOf(activity).create();
423
424        activity.transcript.assertEventsSoFar(
425                "onCreate",
426                "onStart",
427                "onPostCreate",
428                "onResume"
429        );
430    }
431
432
433    @Test
434    public void recreateGoesThroughFullLifeCycle() throws Exception {
435        TestActivity activity = new TestActivity();
436
437        ShadowActivity shadow = shadowOf(activity);
438        shadow.recreate();
439
440        activity.transcript.assertEventsSoFar(
441                "onSaveInstanceState",
442                "onPause",
443                "onStop",
444                "onRetainNonConfigurationInstance",
445                "onDestroy",
446                "onCreate",
447                "onStart",
448                "onRestoreInstanceState",
449                "onResume"
450        );
451
452        Integer storedValue = (Integer) activity.getLastNonConfigurationInstance();
453        assertEquals(5, storedValue.intValue());
454    }
455
456    @Test
457    public void startAndStopManagingCursorTracksCursors() throws Exception {
458        TestActivity activity = new TestActivity();
459
460        ShadowActivity shadow = shadowOf(activity);
461
462        assertThat( shadow.getManagedCursors(), notNullValue() );
463        assertThat( shadow.getManagedCursors().size(), equalTo(0) );
464
465        Cursor c = Robolectric.newInstanceOf(SQLiteCursor.class);
466        activity.startManagingCursor(c);
467
468        assertThat( shadow.getManagedCursors(), notNullValue() );
469        assertThat( shadow.getManagedCursors().size(), equalTo(1) );
470        assertThat( shadow.getManagedCursors().get(0), sameInstance(c) );
471
472        activity.stopManagingCursor(c);
473
474        assertThat( shadow.getManagedCursors(), notNullValue() );
475        assertThat( shadow.getManagedCursors().size(), equalTo(0) );
476    }
477
478    private static class TestActivity extends Activity {
479        Transcript transcript = new Transcript();
480
481        private boolean isRecreating = false;
482
483        @Override
484        public void onSaveInstanceState(Bundle outState) {
485            isRecreating = true;
486            transcript.add("onSaveInstanceState");
487            outState.putString("TestActivityKey", "TestActivityValue");
488            super.onSaveInstanceState(outState);
489        }
490
491        @Override
492        public void onRestoreInstanceState(Bundle savedInstanceState) {
493            transcript.add("onRestoreInstanceState");
494            assertTrue(savedInstanceState.containsKey("TestActivityKey"));
495            assertEquals("TestActivityValue", savedInstanceState.getString("TestActivityKey"));
496            super.onRestoreInstanceState(savedInstanceState);
497        }
498
499        @Override
500        public Object onRetainNonConfigurationInstance() {
501            transcript.add("onRetainNonConfigurationInstance");
502            return new Integer(5);
503        }
504
505        @Override
506        public void onPause() {
507            transcript.add("onPause");
508            super.onPause();
509        }
510
511        @Override
512        public void onDestroy() {
513            transcript.add("onDestroy");
514            super.onDestroy();
515        }
516
517        @Override
518        public void onCreate(Bundle savedInstanceState) {
519            transcript.add("onCreate");
520
521            if( isRecreating ) {
522                assertTrue(savedInstanceState.containsKey("TestActivityKey"));
523                assertEquals("TestActivityValue", savedInstanceState.getString("TestActivityKey"));
524            }
525
526            super.onCreate(savedInstanceState);
527        }
528
529        @Override
530        public void onStart() {
531            transcript.add("onStart");
532            super.onStart();
533        }
534
535        @Override
536        public void onPostCreate(Bundle savedInstanceState) {
537            transcript.add("onPostCreate");
538            super.onPostCreate(savedInstanceState);
539        }
540
541        @Override
542        public void onStop() {
543            transcript.add("onStop");
544            super.onStop();
545        }
546
547        @Override
548        public void onResume() {
549            transcript.add("onResume");
550            super.onResume();
551        }
552    }
553
554    private static class DialogCreatingActivity extends Activity {
555        @Override
556        protected Dialog onCreateDialog(int id) {
557            return new Dialog(this);
558        }
559    }
560
561    private static class DialogLifeCycleActivity extends Activity {
562        public boolean createdDialog = false;
563        public boolean preparedDialog = false;
564        public boolean preparedDialogWithBundle = false;
565        public Dialog dialog = null;
566
567        @Override
568        protected void onDestroy() {
569            super.onDestroy();
570        }
571
572        @Override
573        protected Dialog onCreateDialog(int id) {
574            createdDialog = true;
575            return dialog;
576        }
577
578        @Override
579        protected void onPrepareDialog(int id, Dialog dialog) {
580            preparedDialog = true;
581        }
582
583        @Override
584        protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
585            preparedDialogWithBundle = true;
586        }
587    }
588
589    private static class ActivityWithOnCreateDialog extends Activity {
590        boolean onCreateDialogWasCalled = false;
591
592        @Override
593        protected Dialog onCreateDialog(int id) {
594            onCreateDialogWasCalled = true;
595            return new Dialog(null);
596        }
597    }
598}
599