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