1 package org.robolectric; 2 3 import static android.os.Build.VERSION_CODES.P; 4 import static com.google.common.base.Preconditions.checkState; 5 6 import android.annotation.IdRes; 7 import android.annotation.RequiresApi; 8 import android.app.Activity; 9 import android.app.ActivityThread; 10 import android.app.AppComponentFactory; 11 import android.app.Fragment; 12 import android.app.IntentService; 13 import android.app.LoadedApk; 14 import android.app.Service; 15 import android.app.backup.BackupAgent; 16 import android.content.ContentProvider; 17 import android.content.Context; 18 import android.content.Intent; 19 import android.os.Bundle; 20 import android.os.Looper; 21 import android.util.AttributeSet; 22 import android.view.View; 23 import java.lang.reflect.Modifier; 24 import javax.annotation.Nullable; 25 import org.robolectric.android.AttributeSetBuilderImpl; 26 import org.robolectric.android.AttributeSetBuilderImpl.ArscResourceResolver; 27 import org.robolectric.android.controller.ActivityController; 28 import org.robolectric.android.controller.BackupAgentController; 29 import org.robolectric.android.controller.ContentProviderController; 30 import org.robolectric.android.controller.FragmentController; 31 import org.robolectric.android.controller.IntentServiceController; 32 import org.robolectric.android.controller.ServiceController; 33 import org.robolectric.shadows.ShadowApplication; 34 import org.robolectric.util.Logger; 35 import org.robolectric.util.ReflectionHelpers; 36 import org.robolectric.util.Scheduler; 37 38 public class Robolectric { 39 buildService(Class<T> serviceClass)40 public static <T extends Service> ServiceController<T> buildService(Class<T> serviceClass) { 41 return buildService(serviceClass, null); 42 } 43 buildService( Class<T> serviceClass, Intent intent)44 public static <T extends Service> ServiceController<T> buildService( 45 Class<T> serviceClass, Intent intent) { 46 return ServiceController.of(instantiateService(serviceClass, intent), intent); 47 } 48 setupService(Class<T> serviceClass)49 public static <T extends Service> T setupService(Class<T> serviceClass) { 50 return buildService(serviceClass).create().get(); 51 } 52 buildIntentService( Class<T> serviceClass)53 public static <T extends IntentService> IntentServiceController<T> buildIntentService( 54 Class<T> serviceClass) { 55 return buildIntentService(serviceClass, null); 56 } 57 buildIntentService( Class<T> serviceClass, Intent intent)58 public static <T extends IntentService> IntentServiceController<T> buildIntentService( 59 Class<T> serviceClass, Intent intent) { 60 return IntentServiceController.of(instantiateService(serviceClass, intent), intent); 61 } 62 setupIntentService(Class<T> serviceClass)63 public static <T extends IntentService> T setupIntentService(Class<T> serviceClass) { 64 return buildIntentService(serviceClass).create().get(); 65 } 66 buildContentProvider( Class<T> contentProviderClass)67 public static <T extends ContentProvider> ContentProviderController<T> buildContentProvider( 68 Class<T> contentProviderClass) { 69 return ContentProviderController.of(instantiateContentProvider(contentProviderClass)); 70 } 71 setupContentProvider(Class<T> contentProviderClass)72 public static <T extends ContentProvider> T setupContentProvider(Class<T> contentProviderClass) { 73 return buildContentProvider(contentProviderClass).create().get(); 74 } 75 setupContentProvider( Class<T> contentProviderClass, String authority)76 public static <T extends ContentProvider> T setupContentProvider( 77 Class<T> contentProviderClass, String authority) { 78 return buildContentProvider(contentProviderClass).create(authority).get(); 79 } 80 81 /** 82 * Creates a ActivityController for the given activity class. 83 * 84 * <p>Consider using {@link androidx.test.core.app.ActivityScenario} instead, which provides 85 * higher-level, streamlined APIs to control the lifecycle and it works with instrumentation tests 86 * too. 87 */ buildActivity(Class<T> activityClass)88 public static <T extends Activity> ActivityController<T> buildActivity(Class<T> activityClass) { 89 return buildActivity(activityClass, /* intent= */ null, /* activityOptions= */ null); 90 } 91 92 /** 93 * Creates a ActivityController for the given activity class with the intent. 94 * 95 * <p>Note: the activity class is not determined by the intent. 96 * 97 * <p>Consider using {@link androidx.test.core.app.ActivityScenario} instead, which provides 98 * higher-level, streamlined APIs to control the lifecycle and it works with instrumentation tests 99 * too. 100 */ buildActivity( Class<T> activityClass, Intent intent)101 public static <T extends Activity> ActivityController<T> buildActivity( 102 Class<T> activityClass, Intent intent) { 103 return buildActivity(activityClass, intent, /* activityOptions= */ null); 104 } 105 106 /** 107 * Creates a ActivityController for the given activity class with the intent and activity options. 108 * 109 * <p>Note: the activity class is not determined by the intent. 110 * 111 * <p>Note: Display ID is the only option currently supported in the options bundle. Other options 112 * are ignored. 113 * 114 * <p>Consider using {@link androidx.test.core.app.ActivityScenario} instead, which provides 115 * higher-level, streamlined APIs to control the lifecycle and it works with instrumentation tests 116 * too. 117 */ buildActivity( Class<T> activityClass, Intent intent, @Nullable Bundle activityOptions)118 public static <T extends Activity> ActivityController<T> buildActivity( 119 Class<T> activityClass, Intent intent, @Nullable Bundle activityOptions) { 120 checkState( 121 Thread.currentThread() == Looper.getMainLooper().getThread(), 122 "buildActivity must be called on main Looper thread"); 123 if (Modifier.isAbstract(activityClass.getModifiers())) { 124 throw new RuntimeException("buildActivity must be called with non-abstract class"); 125 } 126 return ActivityController.of( 127 instantiateActivity(activityClass, intent), intent, activityOptions); 128 } 129 130 /** 131 * Simulates starting activity with the given class type and returns its reference. 132 * 133 * <p>Use {@link androidx.test.core.app.ActivityScenario} instead, which works with 134 * instrumentation tests too. 135 * 136 * @deprecated use {@link androidx.test.core.app.ActivityScenario} 137 */ 138 @Deprecated 139 @SuppressWarnings("InlineMeSuggester") setupActivity(Class<T> activityClass)140 public static final <T extends Activity> T setupActivity(Class<T> activityClass) { 141 return buildActivity(activityClass).setup().get(); 142 } 143 144 /** 145 * Creates a FragmentController for the given fragment class. 146 * 147 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 148 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 149 * APIs and works with instrumentation tests too. 150 * 151 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 152 * to use androidx fragments, to test these use FragmentScenario. 153 */ 154 @Deprecated buildFragment(Class<T> fragmentClass)155 public static <T extends Fragment> FragmentController<T> buildFragment(Class<T> fragmentClass) { 156 return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass)); 157 } 158 159 /** 160 * Creates a FragmentController for the given fragment class with the arguments. 161 * 162 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 163 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 164 * APIs and works with instrumentation tests too. 165 * 166 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 167 * to use androidx fragments, to test these use FragmentScenario. 168 */ 169 @Deprecated buildFragment( Class<T> fragmentClass, Bundle arguments)170 public static <T extends Fragment> FragmentController<T> buildFragment( 171 Class<T> fragmentClass, Bundle arguments) { 172 return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), arguments); 173 } 174 175 /** 176 * Creates a FragmentController for the given fragment class in the specified host activity. 177 * 178 * <p>In general, it's a bad practice to design a fragment having dependency to a specific 179 * activity. Consider removing the dependency and use other {@link #buildFragment} method or 180 * {@link androidx.fragment.app.testing.FragmentScenario}. 181 * 182 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 183 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 184 * APIs and works with instrumentation tests too. 185 * 186 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 187 * to use androidx fragments, to test these use FragmentScenario. 188 */ 189 @Deprecated buildFragment( Class<T> fragmentClass, Class<? extends Activity> activityClass)190 public static <T extends Fragment> FragmentController<T> buildFragment( 191 Class<T> fragmentClass, Class<? extends Activity> activityClass) { 192 return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), activityClass); 193 } 194 195 /** 196 * Creates a FragmentController for the given fragment class. The given intent is set to the host 197 * activity. 198 * 199 * <p>Note: the host activity class is not determined by the intent. 200 * 201 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 202 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 203 * APIs and works with instrumentation tests too. 204 * 205 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 206 * to use androidx fragments, to test these use FragmentScenario. 207 */ 208 @Deprecated buildFragment( Class<T> fragmentClass, Intent intent)209 public static <T extends Fragment> FragmentController<T> buildFragment( 210 Class<T> fragmentClass, Intent intent) { 211 return FragmentController.of(ReflectionHelpers.callConstructor(fragmentClass), intent); 212 } 213 214 /** 215 * Creates a FragmentController for the given fragment class with the arguments. The given intent 216 * is set to the host activity. 217 * 218 * <p>Note: the host activity class is not determined by the intent. 219 * 220 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 221 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 222 * APIs and works with instrumentation tests too. 223 * 224 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 225 * to use androidx fragments, to test these use FragmentScenario. 226 */ 227 @Deprecated buildFragment( Class<T> fragmentClass, Intent intent, Bundle arguments)228 public static <T extends Fragment> FragmentController<T> buildFragment( 229 Class<T> fragmentClass, Intent intent, Bundle arguments) { 230 return FragmentController.of( 231 ReflectionHelpers.callConstructor(fragmentClass), intent, arguments); 232 } 233 234 /** 235 * Creates a FragmentController for the given fragment class in the specified host activity. The 236 * given intent is set to the host activity. 237 * 238 * <p>Note: the host activity class is not determined by the intent. 239 * 240 * <p>In general, it's a bad practice to design a fragment having dependency to a specific 241 * activity. Consider removing the dependency and use other {@link #buildFragment} method or 242 * {@link androidx.fragment.app.testing.FragmentScenario}. 243 * 244 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 245 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 246 * APIs and works with instrumentation tests too. 247 * 248 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 249 * to use androidx fragments, to test these use FragmentScenario. 250 */ 251 @Deprecated buildFragment( Class<T> fragmentClass, Class<? extends Activity> activityClass, Intent intent)252 public static <T extends Fragment> FragmentController<T> buildFragment( 253 Class<T> fragmentClass, Class<? extends Activity> activityClass, Intent intent) { 254 return FragmentController.of( 255 ReflectionHelpers.callConstructor(fragmentClass), activityClass, intent); 256 } 257 258 /** 259 * Creates a FragmentController for the given fragment class in the specified host activity with 260 * the arguments. 261 * 262 * <p>In general, it's a bad practice to design a fragment having dependency to a specific 263 * activity. Consider removing the dependency and use other {@link #buildFragment} method or 264 * {@link androidx.fragment.app.testing.FragmentScenario}. 265 * 266 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 267 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 268 * APIs and works with instrumentation tests too. 269 * 270 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 271 * to use androidx fragments, to test these use FragmentScenario. 272 */ 273 @Deprecated buildFragment( Class<T> fragmentClass, Class<? extends Activity> activityClass, Bundle arguments)274 public static <T extends Fragment> FragmentController<T> buildFragment( 275 Class<T> fragmentClass, Class<? extends Activity> activityClass, Bundle arguments) { 276 return FragmentController.of( 277 ReflectionHelpers.callConstructor(fragmentClass), activityClass, arguments); 278 } 279 280 /** 281 * Creates a FragmentController for the given fragment class in the specified host activity with 282 * the arguments. The given intent is set to the host activity. 283 * 284 * <p>Note: the host activity class is not determined by the intent. 285 * 286 * <p>In general, it's a bad practice to design a fragment having dependency to a specific 287 * activity. Consider removing the dependency and use other {@link #buildFragment} method or 288 * {@link androidx.fragment.app.testing.FragmentScenario}. 289 * 290 * <p>FragmentController provides low-level APIs to control its lifecycle. Please consider using 291 * {@link androidx.fragment.app.testing.FragmentScenario} instead, which provides higher level 292 * APIs and works with instrumentation tests too. 293 * 294 * @deprecated Native Fragments have been deprecated in Android P. Android encourages developers 295 * to use androidx fragments, to test these use FragmentScenario. 296 */ 297 @Deprecated buildFragment( Class<T> fragmentClass, Class<? extends Activity> activityClass, Intent intent, Bundle arguments)298 public static <T extends Fragment> FragmentController<T> buildFragment( 299 Class<T> fragmentClass, 300 Class<? extends Activity> activityClass, 301 Intent intent, 302 Bundle arguments) { 303 return FragmentController.of( 304 ReflectionHelpers.callConstructor(fragmentClass), activityClass, intent, arguments); 305 } 306 buildBackupAgent( Class<T> backupAgentClass)307 public static <T extends BackupAgent> BackupAgentController<T> buildBackupAgent( 308 Class<T> backupAgentClass) { 309 return BackupAgentController.of(ReflectionHelpers.callConstructor(backupAgentClass)); 310 } 311 setupBackupAgent(Class<T> backupAgentClass)312 public static <T extends BackupAgent> T setupBackupAgent(Class<T> backupAgentClass) { 313 return buildBackupAgent(backupAgentClass).create().get(); 314 } 315 316 /** 317 * Allows for the programmatic creation of an {@link AttributeSet}. 318 * 319 * <p>Useful for testing {@link View} classes without the need for creating XML snippets. 320 */ buildAttributeSet()321 public static org.robolectric.android.AttributeSetBuilder buildAttributeSet() { 322 323 return new AttributeSetBuilderImpl( 324 new ArscResourceResolver(RuntimeEnvironment.getApplication())) {}; 325 } 326 327 /** 328 * Builder of {@link AttributeSet}s. 329 * 330 * @deprecated Use {@link org.robolectric.android.AttributeSetBuilder} instead. 331 */ 332 @Deprecated 333 public interface AttributeSetBuilder { 334 /** 335 * Set an attribute to the given value. 336 * 337 * <p>The value will be interpreted according to the attribute's format. 338 * 339 * @param resId The attribute resource id to set. 340 * @param value The value to set. 341 * @return This {@link org.robolectric.android.AttributeSetBuilder}. 342 */ addAttribute(@dRes int resId, String value)343 AttributeSetBuilder addAttribute(@IdRes int resId, String value); 344 345 /** 346 * Set the style attribute to the given value. 347 * 348 * <p>The value will be interpreted as a resource reference. 349 * 350 * @param value The value for the specified attribute in this {@link AttributeSet}. 351 * @return This {@link org.robolectric.android.AttributeSetBuilder}. 352 */ setStyleAttribute(String value)353 AttributeSetBuilder setStyleAttribute(String value); 354 355 /** 356 * Build an {@link AttributeSet} with the antecedent attributes. 357 * 358 * @return A new {@link AttributeSet}. 359 */ build()360 AttributeSet build(); 361 } 362 363 /** 364 * Return the foreground scheduler (e.g. the UI thread scheduler). 365 * 366 * @return Foreground scheduler. 367 */ getForegroundThreadScheduler()368 public static Scheduler getForegroundThreadScheduler() { 369 return RuntimeEnvironment.getMasterScheduler(); 370 } 371 372 /** Execute all runnables that have been enqueued on the foreground scheduler. */ flushForegroundThreadScheduler()373 public static void flushForegroundThreadScheduler() { 374 getForegroundThreadScheduler().advanceToLastPostedRunnable(); 375 } 376 377 /** 378 * Return the background scheduler. 379 * 380 * @return Background scheduler. 381 */ getBackgroundThreadScheduler()382 public static Scheduler getBackgroundThreadScheduler() { 383 return ShadowApplication.getInstance().getBackgroundThreadScheduler(); 384 } 385 386 /** Execute all runnables that have been enqueued on the background scheduler. */ flushBackgroundThreadScheduler()387 public static void flushBackgroundThreadScheduler() { 388 getBackgroundThreadScheduler().advanceToLastPostedRunnable(); 389 } 390 391 @SuppressWarnings({"NewApi", "unchecked"}) instantiateService(Class<T> serviceClass, Intent intent)392 private static <T extends Service> T instantiateService(Class<T> serviceClass, Intent intent) { 393 if (RuntimeEnvironment.getApiLevel() >= P) { 394 final LoadedApk loadedApk = getLoadedApk(); 395 AppComponentFactory factory = getAppComponentFactory(loadedApk); 396 if (factory != null) { 397 try { 398 Service instance = 399 factory.instantiateService( 400 loadedApk.getClassLoader(), serviceClass.getName(), intent); 401 if (instance != null && serviceClass.isAssignableFrom(instance.getClass())) { 402 return (T) instance; 403 } 404 } catch (ReflectiveOperationException e) { 405 Logger.debug("Failed to instantiate Service using AppComponentFactory", e); 406 } 407 } 408 } 409 return ReflectionHelpers.callConstructor(serviceClass); 410 } 411 412 @SuppressWarnings({"NewApi", "unchecked"}) instantiateContentProvider(Class<T> providerClass)413 private static <T extends ContentProvider> T instantiateContentProvider(Class<T> providerClass) { 414 if (RuntimeEnvironment.getApiLevel() >= P) { 415 final LoadedApk loadedApk = getLoadedApk(); 416 AppComponentFactory factory = getAppComponentFactory(loadedApk); 417 if (factory != null) { 418 try { 419 ContentProvider instance = 420 factory.instantiateProvider(loadedApk.getClassLoader(), providerClass.getName()); 421 if (instance != null && providerClass.isAssignableFrom(instance.getClass())) { 422 return (T) instance; 423 } 424 } catch (ReflectiveOperationException e) { 425 Logger.debug("Failed to instantiate ContentProvider using AppComponentFactory", e); 426 } 427 } 428 } 429 return ReflectionHelpers.callConstructor(providerClass); 430 } 431 432 @SuppressWarnings({"NewApi", "unchecked"}) instantiateActivity(Class<T> activityClass, Intent intent)433 private static <T extends Activity> T instantiateActivity(Class<T> activityClass, Intent intent) { 434 if (RuntimeEnvironment.getApiLevel() >= P) { 435 final LoadedApk loadedApk = getLoadedApk(); 436 AppComponentFactory factory = getAppComponentFactory(loadedApk); 437 if (factory != null) { 438 try { 439 Activity instance = 440 factory.instantiateActivity( 441 loadedApk.getClassLoader(), activityClass.getName(), intent); 442 if (instance != null && activityClass.isAssignableFrom(instance.getClass())) { 443 return (T) instance; 444 } 445 } catch (ReflectiveOperationException e) { 446 Logger.debug("Failed to instantiate Activity using AppComponentFactory", e); 447 } 448 } 449 } 450 return ReflectionHelpers.callConstructor(activityClass); 451 } 452 453 @Nullable 454 @RequiresApi(api = P) getAppComponentFactory(final LoadedApk loadedApk)455 private static AppComponentFactory getAppComponentFactory(final LoadedApk loadedApk) { 456 if (RuntimeEnvironment.getApiLevel() < P) { 457 return null; 458 } 459 if (loadedApk == null || loadedApk.getApplicationInfo().appComponentFactory == null) { 460 return null; 461 } 462 return loadedApk.getAppFactory(); 463 } 464 getLoadedApk()465 private static LoadedApk getLoadedApk() { 466 final ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); 467 return activityThread.getPackageInfo( 468 activityThread.getApplication().getApplicationInfo(), null, Context.CONTEXT_INCLUDE_CODE); 469 } 470 } 471