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)33   public void startActivity(Intent intent) {
34     ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
35     try {
36       Class<? extends Activity> activityClass = Class.forName(ai.name).asSubclass(Activity.class);
37       controller =
38           Robolectric.buildActivity(activityClass, intent)
39               .create()
40               .postCreate(null)
41               .start()
42               .resume()
43               .postResume()
44               .visible()
45               .windowFocusChanged(true);
46     } catch (ClassNotFoundException e) {
47       throw new RuntimeException("Could not load activity " + ai.name, e);
48     }
49   }
50 
51   @Override
getActivityResult()52   public ActivityResult getActivityResult() {
53     checkNotNull(controller);
54     checkState(controller.get().isFinishing(), "You must finish your Activity first");
55     ShadowActivity shadowActivity = Shadow.extract(controller.get());
56     return new ActivityResult(shadowActivity.getResultCode(), shadowActivity.getResultIntent());
57   }
58 
59   @Override
resumeActivity(Activity activity)60   public void resumeActivity(Activity activity) {
61     checkNotNull(controller);
62     checkState(controller.get() == activity);
63     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
64     switch (stage) {
65       case RESUMED:
66         return;
67       case PAUSED:
68         controller.stop().restart().start().resume().postResume();
69         return;
70       case STOPPED:
71         controller.restart().start().resume().postResume();
72         return;
73       default:
74         throw new IllegalStateException(
75             String.format(
76                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
77     }
78   }
79 
80   @Override
pauseActivity(Activity activity)81   public void pauseActivity(Activity activity) {
82     checkNotNull(controller);
83     checkState(controller.get() == activity);
84     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
85     switch (stage) {
86       case RESUMED:
87         controller.pause();
88         return;
89       case PAUSED:
90         return;
91       default:
92         throw new IllegalStateException(
93             String.format("Activity's stage must be RESUMED or PAUSED but was %s.", stage));
94     }
95   }
96 
97   @Override
stopActivity(Activity activity)98   public void stopActivity(Activity activity) {
99     checkNotNull(controller);
100     checkState(controller.get() == activity);
101     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
102     switch (stage) {
103       case RESUMED:
104         controller.pause().stop();
105         return;
106       case PAUSED:
107         controller.stop();
108         return;
109       case STOPPED:
110         return;
111       default:
112         throw new IllegalStateException(
113             String.format(
114                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
115     }
116   }
117 
118   @Override
recreateActivity(Activity activity)119   public void recreateActivity(Activity activity) {
120     checkNotNull(controller);
121     checkState(controller.get() == activity);
122     Stage originalStage =
123         ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
124 
125     // Move the activity stage to STOPPED before retrieving saveInstanceState.
126     stopActivity(activity);
127 
128     Bundle outState = new Bundle();
129     controller.saveInstanceState(outState);
130     Object nonConfigInstance = activity.onRetainNonConfigurationInstance();
131     controller.destroy();
132 
133     controller = Robolectric.buildActivity(activity.getClass(), activity.getIntent());
134     Activity recreatedActivity = controller.get();
135     Shadow.<ShadowActivity>extract(recreatedActivity)
136         .setLastNonConfigurationInstance(nonConfigInstance);
137     controller
138         .create(outState)
139         .postCreate(outState)
140         .start()
141         .restoreInstanceState(outState)
142         .resume()
143         .postResume()
144         .visible()
145         .windowFocusChanged(true);
146 
147     // Move to the original stage.
148     switch (originalStage) {
149       case RESUMED:
150         return;
151       case PAUSED:
152         pauseActivity(recreatedActivity);
153         return;
154       case STOPPED:
155         stopActivity(recreatedActivity);
156         return;
157       default:
158         throw new IllegalStateException(
159             String.format(
160                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", originalStage));
161     }
162   }
163 
164   @Override
finishActivity(Activity activity)165   public void finishActivity(Activity activity) {
166     checkNotNull(controller);
167     checkState(controller.get() == activity);
168     activity.finish();
169     Stage stage = ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity);
170     switch (stage) {
171       case RESUMED:
172         controller.pause().stop().destroy();
173         return;
174       case PAUSED:
175         controller.stop().destroy();
176         return;
177       case STOPPED:
178         controller.destroy();
179         return;
180       default:
181         throw new IllegalStateException(
182             String.format(
183                 "Activity's stage must be RESUMED, PAUSED or STOPPED but was %s.", stage));
184     }
185   }
186 
187   // TODO: just copy implementation from super. It looks like 'default' keyword from super is
188   // getting stripped from androidx.test.monitor maven artifact
189   @Override
getIntentForActivity(Class<? extends Activity> activityClass)190   public Intent getIntentForActivity(Class<? extends Activity> activityClass) {
191     Intent intent = Intent.makeMainActivity(new ComponentName(getTargetContext(), activityClass));
192     if (getTargetContext().getPackageManager().resolveActivity(intent, 0) != null) {
193       return intent;
194     }
195     return Intent.makeMainActivity(new ComponentName(getContext(), activityClass));
196   }
197 }
198