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