• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.android.internal;
2 
3 import static androidx.test.InstrumentationRegistry.getContext;
4 import static androidx.test.InstrumentationRegistry.getTargetContext;
5 import static com.google.common.base.Preconditions.checkNotNull;
6 import static com.google.common.base.Preconditions.checkState;
7 
8 import android.app.Activity;
9 import android.app.Instrumentation.ActivityResult;
10 import android.content.ComponentName;
11 import android.content.Intent;
12 import android.content.pm.ActivityInfo;
13 import android.os.Bundle;
14 import androidx.test.internal.platform.app.ActivityInvoker;
15 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
16 import androidx.test.runner.lifecycle.Stage;
17 import javax.annotation.Nullable;
18 import org.robolectric.Robolectric;
19 import org.robolectric.android.controller.ActivityController;
20 import org.robolectric.shadow.api.Shadow;
21 import org.robolectric.shadows.ShadowActivity;
22 
23 /**
24  * An {@link ActivityInvoker} that drives {@link Activity} lifecycles manually.
25  *
26  * <p>All the methods in this class are blocking API.
27  */
28 public class LocalActivityInvoker implements ActivityInvoker {
29 
30   @Nullable private ActivityController<? extends Activity> controller;
31 
32   @Override
startActivity(Intent intent, @Nullable Bundle activityOptions)33   public void startActivity(Intent intent, @Nullable Bundle activityOptions) {
34     startActivity(intent);
35   }
36 
37   @Override
startActivity(Intent intent)38   public void startActivity(Intent intent) {
39     ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
40     try {
41       Class<? extends Activity> activityClass = Class.forName(ai.name).asSubclass(Activity.class);
42       controller =
43           Robolectric.buildActivity(activityClass, intent)
44               .create()
45               .postCreate(null)
46               .start()
47               .resume()
48               .postResume()
49               .visible()
50               .windowFocusChanged(true);
51     } catch (ClassNotFoundException e) {
52       throw new RuntimeException("Could not load activity " + ai.name, e);
53     }
54   }
55 
56   @Override
getActivityResult()57   public ActivityResult getActivityResult() {
58     checkNotNull(controller);
59     checkState(controller.get().isFinishing(), "You must finish your Activity first");
60     ShadowActivity shadowActivity = Shadow.extract(controller.get());
61     return new ActivityResult(shadowActivity.getResultCode(), shadowActivity.getResultIntent());
62   }
63 
64   @Override
resumeActivity(Activity activity)65   public void resumeActivity(Activity activity) {
66     checkNotNull(controller);
67     checkState(controller.get() == activity);
68     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
69     switch (stage) {
70       case RESUMED:
71         return;
72       case PAUSED:
73         controller.stop().restart().start().resume().postResume();
74         return;
75       case STOPPED:
76         controller.restart().start().resume().postResume();
77         return;
78       default:
79         throw new IllegalStateException(
80             String.format(
81                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
82     }
83   }
84 
85   @Override
pauseActivity(Activity activity)86   public void pauseActivity(Activity activity) {
87     checkNotNull(controller);
88     checkState(controller.get() == activity);
89     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
90     switch (stage) {
91       case RESUMED:
92         controller.pause();
93         return;
94       case PAUSED:
95         return;
96       default:
97         throw new IllegalStateException(
98             String.format("Activity's stage must be RESUMED or PAUSED but was %s.", stage));
99     }
100   }
101 
102   @Override
stopActivity(Activity activity)103   public void stopActivity(Activity activity) {
104     checkNotNull(controller);
105     checkState(controller.get() == activity);
106     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
107     switch (stage) {
108       case RESUMED:
109         controller.pause().stop();
110         return;
111       case PAUSED:
112         controller.stop();
113         return;
114       case STOPPED:
115         return;
116       default:
117         throw new IllegalStateException(
118             String.format(
119                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
120     }
121   }
122 
123   @Override
recreateActivity(Activity activity)124   public void recreateActivity(Activity activity) {
125     checkNotNull(controller);
126     checkState(controller.get() == activity);
127     Stage originalStage =
128         ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
129 
130     // Move the activity stage to STOPPED before retrieving saveInstanceState.
131     stopActivity(activity);
132 
133     Bundle outState = new Bundle();
134     controller.saveInstanceState(outState);
135     Object nonConfigInstance = activity.onRetainNonConfigurationInstance();
136     controller.destroy();
137 
138     controller = Robolectric.buildActivity(activity.getClass(), activity.getIntent());
139     Activity recreatedActivity = controller.get();
140     Shadow.<ShadowActivity>extract(recreatedActivity)
141         .setLastNonConfigurationInstance(nonConfigInstance);
142     controller
143         .create(outState)
144         .postCreate(outState)
145         .start()
146         .restoreInstanceState(outState)
147         .resume()
148         .postResume()
149         .visible()
150         .windowFocusChanged(true);
151 
152     // Move to the original stage.
153     switch (originalStage) {
154       case RESUMED:
155         return;
156       case PAUSED:
157         pauseActivity(recreatedActivity);
158         return;
159       case STOPPED:
160         stopActivity(recreatedActivity);
161         return;
162       default:
163         throw new IllegalStateException(
164             String.format(
165                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", originalStage));
166     }
167   }
168 
169   @Override
finishActivity(Activity activity)170   public void finishActivity(Activity activity) {
171     checkNotNull(controller);
172     checkState(controller.get() == activity);
173     activity.finish();
174     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
175     switch (stage) {
176       case RESUMED:
177         controller.pause().stop().destroy();
178         return;
179       case PAUSED:
180         controller.stop().destroy();
181         return;
182       case STOPPED:
183         controller.destroy();
184         return;
185       default:
186         throw new IllegalStateException(
187             String.format(
188                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
189     }
190   }
191 
192   // TODO: just copy implementation from super. It looks like 'default' keyword from super is
193   // getting stripped from androidx.test.monitor maven artifact
194   @Override
getIntentForActivity(Class<? extends Activity> activityClass)195   public Intent getIntentForActivity(Class<? extends Activity> activityClass) {
196     Intent intent = Intent.makeMainActivity(new ComponentName(getTargetContext(), activityClass));
197     if (getTargetContext().getPackageManager().resolveActivity(intent, 0) != null) {
198       return intent;
199     }
200     return Intent.makeMainActivity(new ComponentName(getContext(), activityClass));
201   }
202 }
203