1package org.robolectric.android.controller;
2
3import static org.assertj.core.api.Assertions.assertThat;
4import static org.junit.Assert.assertEquals;
5import static org.robolectric.Shadows.shadowOf;
6
7import android.app.Activity;
8import android.app.Fragment;
9import android.content.ComponentName;
10import android.content.Intent;
11import android.content.res.Configuration;
12import android.os.Build;
13import android.os.Bundle;
14import android.os.Handler;
15import android.os.Looper;
16import android.view.ContextThemeWrapper;
17import android.view.Window;
18import android.widget.LinearLayout;
19import java.util.ArrayList;
20import java.util.List;
21import org.junit.Before;
22import org.junit.Test;
23import org.junit.runner.RunWith;
24import org.robolectric.R;
25import org.robolectric.Robolectric;
26import org.robolectric.RobolectricTestRunner;
27import org.robolectric.RuntimeEnvironment;
28import org.robolectric.annotation.Config;
29import org.robolectric.shadows.ShadowLooper;
30import org.robolectric.util.Scheduler;
31import org.robolectric.util.TestRunnable;
32
33@RunWith(RobolectricTestRunner.class)
34public class ActivityControllerTest {
35  private static final List<String> transcript = new ArrayList<>();
36  private final ComponentName componentName = new ComponentName("org.robolectric", MyActivity.class.getName());
37  private final ActivityController<MyActivity> controller = Robolectric.buildActivity(MyActivity.class);
38
39  @Before
40  public void setUp() throws Exception {
41    transcript.clear();
42  }
43
44  @Test
45  public void canCreateActivityNotListedInManifest() {
46    Activity activity = Robolectric.setupActivity(Activity.class);
47    assertThat(activity).isNotNull();
48    assertThat(activity.getThemeResId()).isEqualTo(R.style.Theme_Robolectric);
49  }
50
51  public static class TestDelayedPostActivity extends Activity {
52    TestRunnable r1 = new TestRunnable();
53    TestRunnable r2 = new TestRunnable();
54    @Override
55    protected void onCreate(Bundle savedInstanceState) {
56      super.onCreate(savedInstanceState);
57      Handler h = new Handler();
58      h.post(r1);
59      h.postDelayed(r2, 60000);
60    }
61  }
62
63  @Test
64  public void pendingTasks_areRunEagerly_whenActivityIsStarted_andSchedulerUnPaused() {
65    final Scheduler s = Robolectric.getForegroundThreadScheduler();
66    final long startTime = s.getCurrentTime();
67    TestDelayedPostActivity activity = Robolectric.setupActivity(TestDelayedPostActivity.class);
68    assertThat(activity.r1.wasRun).as("immediate task").isTrue();
69    assertThat(s.getCurrentTime()).as("currentTime").isEqualTo(startTime);
70  }
71
72  @Test
73  public void delayedTasks_areNotRunEagerly_whenActivityIsStarted_andSchedulerUnPaused() {
74    // Regression test for issue #1509
75    final Scheduler s = Robolectric.getForegroundThreadScheduler();
76    final long startTime = s.getCurrentTime();
77    TestDelayedPostActivity activity = Robolectric.setupActivity(TestDelayedPostActivity.class);
78    assertThat(activity.r2.wasRun).as("before flush").isFalse();
79    assertThat(s.getCurrentTime()).as("currentTime before flush").isEqualTo(startTime);
80    s.advanceToLastPostedRunnable();
81    assertThat(activity.r2.wasRun).as("after flush").isTrue();
82    assertThat(s.getCurrentTime()).as("currentTime after flush").isEqualTo(startTime + 60000);
83  }
84
85  @Test
86  public void shouldSetIntent() throws Exception {
87    MyActivity myActivity = controller.create().get();
88    assertThat(myActivity.getIntent()).isNotNull();
89    assertThat(myActivity.getIntent().getComponent()).isEqualTo(componentName);
90  }
91
92  @Test
93  public void shouldSetIntentComponentWithCustomIntentWithoutComponentSet() throws Exception {
94    MyActivity myActivity = Robolectric.buildActivity(MyActivity.class, new Intent(Intent.ACTION_VIEW)).create().get();
95    assertThat(myActivity.getIntent().getAction()).isEqualTo(Intent.ACTION_VIEW);
96    assertThat(myActivity.getIntent().getComponent()).isEqualTo(componentName);
97  }
98
99  @Test
100  public void shouldSetIntentForGivenActivityInstance() throws Exception {
101    ActivityController<MyActivity> activityController = ActivityController.of(new MyActivity()).create();
102    assertThat(activityController.get().getIntent()).isNotNull();
103  }
104
105  @Test
106  public void whenLooperIsNotPaused_shouldCreateWithMainLooperPaused() throws Exception {
107    ShadowLooper.unPauseMainLooper();
108    controller.create();
109    assertThat(shadowOf(Looper.getMainLooper()).isPaused()).isFalse();
110    assertThat(transcript).contains("finishedOnCreate", "onCreate");
111  }
112
113  @Test
114  public void whenLooperIsAlreadyPaused_shouldCreateWithMainLooperPaused() throws Exception {
115    ShadowLooper.pauseMainLooper();
116    controller.create();
117    assertThat(shadowOf(Looper.getMainLooper()).isPaused()).isTrue();
118    assertThat(transcript).contains("finishedOnCreate");
119
120    ShadowLooper.unPauseMainLooper();
121    assertThat(transcript).contains("onCreate");
122  }
123
124  @Test
125  public void visible_addsTheDecorViewToTheWindowManager() {
126    controller.create().visible();
127    assertEquals(controller.get().getWindow().getDecorView().getParent().getClass().getName(), "android.view.ViewRootImpl");
128  }
129
130  @Test
131  public void start_callsPerformStartWhilePaused() {
132    controller.create().start();
133    assertThat(transcript).contains("finishedOnStart", "onStart");
134  }
135
136  @Test
137  public void stop_callsPerformStopWhilePaused() {
138    controller.create().start().stop();
139    assertThat(transcript).contains("finishedOnStop", "onStop");
140  }
141
142  @Test
143  public void restart_callsPerformRestartWhilePaused() {
144    controller.create().start().stop().restart();
145    assertThat(transcript).contains("finishedOnRestart", "onRestart");
146  }
147
148  @Test
149  public void pause_callsPerformPauseWhilePaused() {
150    controller.create().pause();
151    assertThat(transcript).contains("finishedOnPause", "onPause");
152  }
153
154  @Test
155  public void resume_callsPerformResumeWhilePaused() {
156    controller.create().start().resume();
157    assertThat(transcript).contains("finishedOnResume", "onResume");
158  }
159
160  @Test
161  public void destroy_callsPerformDestroyWhilePaused() {
162    controller.create().destroy();
163    assertThat(transcript).contains("finishedOnDestroy", "onDestroy");
164  }
165
166  @Test
167  public void postCreate_callsOnPostCreateWhilePaused() {
168    controller.create().postCreate(new Bundle());
169    assertThat(transcript).contains("finishedOnPostCreate", "onPostCreate");
170  }
171
172  @Test
173  public void postResume_callsOnPostResumeWhilePaused() {
174    controller.create().postResume();
175    assertThat(transcript).contains("finishedOnPostResume", "onPostResume");
176  }
177
178  @Test
179  public void restoreInstanceState_callsPerformRestoreInstanceStateWhilePaused() {
180    controller.create().restoreInstanceState(new Bundle());
181    assertThat(transcript).contains("finishedOnRestoreInstanceState", "onRestoreInstanceState");
182  }
183
184  @Test
185  public void newIntent_callsOnNewIntentWhilePaused() {
186    controller.create().newIntent(new Intent(Intent.ACTION_VIEW));
187    assertThat(transcript).contains("finishedOnNewIntent", "onNewIntent");
188  }
189
190  @Test
191  public void userLeaving_callsPerformUserLeavingWhilePaused() {
192    controller.create().userLeaving();
193    assertThat(transcript).contains("finishedOnUserLeaveHint", "onUserLeaveHint");
194  }
195
196  @Test
197  public void setup_callsLifecycleMethodsAndMakesVisible() {
198    controller.setup();
199    assertThat(transcript).contains("onCreate", "onStart", "onPostCreate", "onResume", "onPostResume");
200    assertEquals(controller.get().getWindow().getDecorView().getParent().getClass().getName(), "android.view.ViewRootImpl");
201  }
202
203  @Test
204  public void setupWithBundle_callsLifecycleMethodsAndMakesVisible() {
205    controller.setup(new Bundle());
206    assertThat(transcript).contains("onCreate", "onStart", "onRestoreInstanceState", "onPostCreate", "onResume", "onPostResume");
207    assertEquals(controller.get().getWindow().getDecorView().getParent().getClass().getName(), "android.view.ViewRootImpl");
208  }
209
210  @Test
211  @Config(sdk = Build.VERSION_CODES.KITKAT)
212  public void attach_shouldWorkWithAPI19() {
213    MyActivity activity = Robolectric.buildActivity(MyActivity.class).create().get();
214    assertThat(activity).isNotNull();
215  }
216
217  @Test
218  public void configurationChange_callsLifecycleMethodsAndAppliesConfig() {
219    Configuration config = new Configuration(RuntimeEnvironment.application.getResources().getConfiguration());
220    final float newFontScale = config.fontScale *= 2;
221
222    controller.setup();
223    transcript.clear();
224    controller.configurationChange(config);
225    assertThat(transcript).contains("onPause", "onStop", "onDestroy", "onCreate", "onStart", "onRestoreInstanceState", "onPostCreate", "onResume", "onPostResume");
226    assertThat(controller.get().getResources().getConfiguration().fontScale).isEqualTo(newFontScale);
227  }
228
229  @Test
230  public void configurationChange_callsOnConfigurationChangedAndAppliesConfigWhenAllManaged() {
231    Configuration config = new Configuration(RuntimeEnvironment.application.getResources().getConfiguration());
232    final float newFontScale = config.fontScale *= 2;
233
234    ActivityController<ConfigAwareActivity> configController =
235        Robolectric.buildActivity(ConfigAwareActivity.class).setup();
236    transcript.clear();
237    configController.configurationChange(config);
238    assertThat(transcript).contains("onConfigurationChanged");
239    assertThat(configController.get().getResources().getConfiguration().fontScale).isEqualTo(newFontScale);
240  }
241
242  @Test
243  public void configurationChange_callsLifecycleMethodsAndAppliesConfigWhenAnyNonManaged() {
244    Configuration config = new Configuration(RuntimeEnvironment.application.getResources().getConfiguration());
245    final float newFontScale = config.fontScale *= 2;
246    final int newOrientation = config.orientation = (config.orientation + 1) % 3;
247
248    ActivityController<ConfigAwareActivity> configController =
249        Robolectric.buildActivity(ConfigAwareActivity.class).setup();
250    transcript.clear();
251    configController.configurationChange(config);
252    assertThat(transcript).contains("onPause", "onStop", "onDestroy", "onCreate", "onStart", "onResume");
253    assertThat(configController.get().getResources().getConfiguration().fontScale).isEqualTo(newFontScale);
254    assertThat(configController.get().getResources().getConfiguration().orientation).isEqualTo(newOrientation);
255  }
256
257  @Test
258  @Config(qualifiers = "land")
259  public void noArgsConfigurationChange_appliesChangedSystemConfiguration() throws Exception {
260    ActivityController<ConfigAwareActivity> configController =
261        Robolectric.buildActivity(ConfigAwareActivity.class).setup();
262    RuntimeEnvironment.setQualifiers("port");
263    configController.configurationChange();
264    assertThat(configController.get().newConfig.orientation)
265        .isEqualTo(Configuration.ORIENTATION_PORTRAIT);
266  }
267
268  @Test
269  @Config(qualifiers = "land")
270  public void configurationChange_restoresTheme() {
271    Configuration config = new Configuration(RuntimeEnvironment.application.getResources().getConfiguration());
272    config.orientation = Configuration.ORIENTATION_PORTRAIT;
273
274    controller.get().setTheme(android.R.style.Theme_Black);
275    controller.setup();
276    transcript.clear();
277    controller.configurationChange(config);
278    int restoredTheme = shadowOf((ContextThemeWrapper) controller.get()).callGetThemeResId();
279    assertThat(restoredTheme).isEqualTo(android.R.style.Theme_Black);
280  }
281
282  @Test
283  @Config(qualifiers = "land")
284  public void configurationChange_reattachesRetainedFragments() {
285    Configuration config = new Configuration(RuntimeEnvironment.application.getResources().getConfiguration());
286    config.orientation = Configuration.ORIENTATION_PORTRAIT;
287
288    ActivityController<NonConfigStateActivity> configController =
289        Robolectric.buildActivity(NonConfigStateActivity.class).setup();
290    NonConfigStateActivity activity = configController.get();
291    Fragment retainedFragment = activity.retainedFragment;
292    Fragment otherFragment = activity.nonRetainedFragment;
293    configController.configurationChange(config);
294    activity = configController.get();
295
296    assertThat(activity.retainedFragment).isNotNull();
297    assertThat(activity.retainedFragment).isSameAs(retainedFragment);
298    assertThat(activity.nonRetainedFragment).isNotNull();
299    assertThat(activity.nonRetainedFragment).isNotSameAs(otherFragment);
300  }
301
302  public static class MyActivity extends Activity {
303    @Override
304    protected void onRestoreInstanceState(Bundle savedInstanceState) {
305      super.onRestoreInstanceState(savedInstanceState);
306      transcribeWhilePaused("onRestoreInstanceState");
307      transcript.add("finishedOnRestoreInstanceState");
308    }
309
310    @Override
311    protected void onCreate(Bundle savedInstanceState) {
312      super.onCreate(savedInstanceState);
313      getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
314      setContentView(new LinearLayout(RuntimeEnvironment.application));
315      transcribeWhilePaused("onCreate");
316      transcript.add("finishedOnCreate");
317    }
318
319    @Override
320    protected void onPostCreate(Bundle savedInstanceState) {
321      super.onPostCreate(savedInstanceState);
322      transcribeWhilePaused("onPostCreate");
323      transcript.add("finishedOnPostCreate");
324    }
325
326    @Override
327    protected void onPostResume() {
328      super.onPostResume();
329      transcribeWhilePaused("onPostResume");
330      transcript.add("finishedOnPostResume");
331    }
332
333    @Override
334    protected void onDestroy() {
335      super.onDestroy();
336      transcribeWhilePaused("onDestroy");
337      transcript.add("finishedOnDestroy");
338    }
339
340    @Override
341    protected void onStart() {
342      super.onStart();
343      transcribeWhilePaused("onStart");
344      transcript.add("finishedOnStart");
345    }
346
347    @Override
348    protected void onStop() {
349      super.onStop();
350      transcribeWhilePaused("onStop");
351      transcript.add("finishedOnStop");
352    }
353
354    @Override
355    protected void onResume() {
356      super.onResume();
357      transcribeWhilePaused("onResume");
358      transcript.add("finishedOnResume");
359    }
360
361    @Override
362    protected void onRestart() {
363      super.onRestart();
364      transcribeWhilePaused("onRestart");
365      transcript.add("finishedOnRestart");
366    }
367
368    @Override
369    protected void onPause() {
370      super.onPause();
371      transcribeWhilePaused("onPause");
372      transcript.add("finishedOnPause");
373    }
374
375    @Override
376    protected void onNewIntent(Intent intent) {
377      super.onNewIntent(intent);
378      transcribeWhilePaused("onNewIntent");
379      transcript.add("finishedOnNewIntent");
380    }
381
382    @Override
383    protected void onUserLeaveHint() {
384      super.onUserLeaveHint();
385      transcribeWhilePaused("onUserLeaveHint");
386      transcript.add("finishedOnUserLeaveHint");
387    }
388
389    @Override
390    public void onConfigurationChanged(Configuration newConfig) {
391      super.onConfigurationChanged(newConfig);
392      transcribeWhilePaused("onConfigurationChanged");
393      transcript.add("finishedOnConfigurationChanged");
394    }
395
396    private void transcribeWhilePaused(final String event) {
397      runOnUiThread(new Runnable() {
398        @Override public void run() {
399          transcript.add(event);
400        }
401      });
402    }
403  }
404
405  public static class ConfigAwareActivity extends MyActivity {
406
407    Configuration newConfig;
408
409    @Override
410    protected void onCreate(Bundle savedInstanceState) {
411      super.onCreate(savedInstanceState);
412      if (savedInstanceState != null) {
413        assertThat(savedInstanceState.getSerializable("test")).isNotNull();
414      }
415    }
416
417    @Override
418    public void onSaveInstanceState(Bundle outState) {
419      super.onSaveInstanceState(outState);
420      outState.putSerializable("test", new Exception());
421    }
422
423    @Override
424    public void onConfigurationChanged(Configuration newConfig) {
425      this.newConfig = new Configuration(newConfig);
426      super.onConfigurationChanged(newConfig);
427    }
428  }
429
430  public static final class NonConfigStateActivity extends Activity {
431    Fragment retainedFragment;
432    Fragment nonRetainedFragment;
433
434    @Override
435    protected void onCreate(Bundle savedInstanceState) {
436      super.onCreate(savedInstanceState);
437      if (savedInstanceState == null) {
438        retainedFragment = new Fragment();
439        retainedFragment.setRetainInstance(true);
440        nonRetainedFragment = new Fragment();
441        getFragmentManager().beginTransaction()
442            .add(android.R.id.content, retainedFragment, "retained")
443            .add(android.R.id.content, nonRetainedFragment, "non-retained")
444            .commit();
445      } else {
446        retainedFragment = getFragmentManager().findFragmentByTag("retained");
447        nonRetainedFragment = getFragmentManager().findFragmentByTag("non-retained");
448      }
449    }
450  }
451}
452