1 package org.robolectric; 2 3 import static android.os.Build.VERSION_CODES.Q; 4 import static org.robolectric.annotation.LooperMode.Mode.LEGACY; 5 import static org.robolectric.shadows.ShadowLooper.assertLooperMode; 6 import static org.robolectric.util.reflector.Reflector.reflector; 7 8 import android.app.Application; 9 import android.app.ResourcesManager; 10 import android.content.Context; 11 import android.content.res.CompatibilityInfo; 12 import android.content.res.Configuration; 13 import android.content.res.Resources; 14 import android.graphics.Bitmap; 15 import android.util.DisplayMetrics; 16 import android.view.Display; 17 import com.google.common.base.Supplier; 18 import java.nio.file.Path; 19 import org.robolectric.android.Bootstrap; 20 import org.robolectric.android.ConfigurationV25; 21 import org.robolectric.shadows.ShadowDisplayManager; 22 import org.robolectric.shadows.ShadowInstrumentation; 23 import org.robolectric.shadows.ShadowView; 24 import org.robolectric.util.Scheduler; 25 import org.robolectric.util.TempDirectory; 26 import org.robolectric.util.reflector.ForType; 27 28 public class RuntimeEnvironment { 29 /** 30 * @deprecated Use {@link #getApplication} instead. Note that unlike the alternative, this field 31 * is inherently incompatible with {@link 32 * org.robolectric.annotation.experimental.LazyApplication}. This field may be removed in a 33 * later release 34 */ 35 @Deprecated public static Context systemContext; 36 37 /** 38 * @deprecated Please use {#getApplication} instead. Accessing this field directly is inherently 39 * incompatible with {@link org.robolectric.annotation.experimental.LazyApplication} and 40 * Robolectric makes no guarantees if a test *modifies* this field during execution. 41 */ 42 @Deprecated public static volatile Application application; 43 44 private static volatile Thread mainThread; 45 private static volatile Object activityThread; 46 private static int apiLevel; 47 private static Scheduler masterScheduler; 48 private static TempDirectory tempDirectory = new TempDirectory("no-test-yet"); 49 private static Path androidFrameworkJar; 50 51 private static Supplier<Application> applicationSupplier; 52 private static final Object supplierLock = new Object(); 53 private static Supplier<Path> compileTimeSystemResourcesSupplier; 54 55 /** 56 * Get a reference to the {@link Application} under test. 57 * 58 * <p>The Application may be created a test setup time or created lazily at call time, based on 59 * the test's {@link org.robolectric.annotation.experimental.LazyApplication} setting. If lazy 60 * loading is enabled, this method must be called on the main/test thread. 61 * 62 * <p>An alternate API outside of Robolectric is {@link 63 * androidx.test.core.app.ApplicationProvider#getApplicationContext()}, which is preferable if you 64 * desire cross platform tests that work on the JVM and real Android devices. 65 */ getApplication()66 public static Application getApplication() { 67 // IMPORTANT NOTE: Given the order in which these are nulled out when cleaning up in 68 // AndroidTestEnvironment, the application null check must happen before the supplier null 69 // check. Otherwise the get() call can try to load an application that has already been 70 // loaded and cleaned up (as well as race with other threads trying to load the "correct" 71 // application) 72 if (application == null) { 73 synchronized (supplierLock) { 74 if (applicationSupplier != null) { 75 ShadowInstrumentation.runOnMainSyncNoIdle(() -> application = applicationSupplier.get()); 76 } 77 } 78 } 79 return application; 80 } 81 82 /** internal use only */ setApplicationSupplier(Supplier<Application> applicationSupplier)83 public static void setApplicationSupplier(Supplier<Application> applicationSupplier) { 84 synchronized (supplierLock) { 85 RuntimeEnvironment.applicationSupplier = applicationSupplier; 86 } 87 } 88 89 private static Class<? extends Application> applicationClass; 90 getConfiguredApplicationClass()91 public static Class<? extends Application> getConfiguredApplicationClass() { 92 return applicationClass; 93 } 94 setConfiguredApplicationClass(Class<? extends Application> clazz)95 public static void setConfiguredApplicationClass(Class<? extends Application> clazz) { 96 applicationClass = clazz; 97 } 98 99 /** 100 * Tests if the given thread is currently set as the main thread. 101 * 102 * @param thread the thread to test. 103 * @return true if the specified thread is the main thread, false otherwise. 104 * @see #isMainThread() 105 */ isMainThread(Thread thread)106 public static boolean isMainThread(Thread thread) { 107 assertLooperMode(LEGACY); 108 return thread == mainThread; 109 } 110 111 /** 112 * Tests if the current thread is currently set as the main thread. 113 * 114 * <p>Not supported in realistic looper mode. 115 * 116 * @return true if the current thread is the main thread, false otherwise. 117 */ isMainThread()118 public static boolean isMainThread() { 119 assertLooperMode(LEGACY); 120 return isMainThread(Thread.currentThread()); 121 } 122 123 /** 124 * Retrieves the main thread. The main thread is the thread to which the main looper is attached. 125 * Defaults to the thread that initialises the {@link RuntimeEnvironment} class. 126 * 127 * <p>Not supported in realistic looper mode. 128 * 129 * @return The main thread. 130 * @see #setMainThread(Thread) 131 * @see #isMainThread() 132 */ getMainThread()133 public static Thread getMainThread() { 134 assertLooperMode(LEGACY); 135 return mainThread; 136 } 137 138 /** 139 * Sets the main thread. The main thread is the thread to which the main looper is attached. 140 * Defaults to the thread that initialises the {@link RuntimeEnvironment} class. 141 * 142 * <p>Not supported in realistic looper mode. 143 * 144 * @param newMainThread the new main thread. 145 * @see #setMainThread(Thread) 146 * @see #isMainThread() 147 */ setMainThread(Thread newMainThread)148 public static void setMainThread(Thread newMainThread) { 149 assertLooperMode(LEGACY); 150 mainThread = newMainThread; 151 } 152 getActivityThread()153 public static Object getActivityThread() { 154 return activityThread; 155 } 156 setActivityThread(Object newActivityThread)157 public static void setActivityThread(Object newActivityThread) { 158 activityThread = newActivityThread; 159 } 160 161 /** 162 * Returns a qualifier string describing the current {@link Configuration} of the system 163 * resources. 164 * 165 * @return a qualifier string as described 166 * (https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules)[here]. 167 */ getQualifiers()168 public static String getQualifiers() { 169 Resources systemResources = Resources.getSystem(); 170 return getQualifiers(systemResources.getConfiguration(), systemResources.getDisplayMetrics()); 171 } 172 173 /** 174 * Returns a qualifier string describing the given configuration and display metrics. 175 * 176 * @param configuration the configuration. 177 * @param displayMetrics the display metrics. 178 * @return a qualifier string as described 179 * (https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules)[here]. 180 */ getQualifiers(Configuration configuration, DisplayMetrics displayMetrics)181 public static String getQualifiers(Configuration configuration, DisplayMetrics displayMetrics) { 182 return ConfigurationV25.resourceQualifierString(configuration, displayMetrics); 183 } 184 185 /** 186 * Overrides the current device configuration. 187 * 188 * <p>If {@param newQualifiers} starts with a plus ('+'), the prior configuration is used as the 189 * base configuration, with the given changes applied additively. Otherwise, default values are 190 * used for unspecified properties, as described <a 191 * href="http://robolectric.org/device-configuration/">here</a>. 192 * 193 * @param newQualifiers the qualifiers to apply 194 */ setQualifiers(String newQualifiers)195 public static void setQualifiers(String newQualifiers) { 196 ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers); 197 198 Configuration configuration; 199 DisplayMetrics displayMetrics = new DisplayMetrics(); 200 201 if (newQualifiers.startsWith("+")) { 202 configuration = new Configuration(Resources.getSystem().getConfiguration()); 203 displayMetrics.setTo(Resources.getSystem().getDisplayMetrics()); 204 } else { 205 configuration = new Configuration(); 206 } 207 Bootstrap.applyQualifiers(newQualifiers, getApiLevel(), configuration, displayMetrics); 208 if (ShadowView.useRealGraphics()) { 209 Bitmap.setDefaultDensity(displayMetrics.densityDpi); 210 } 211 212 updateConfiguration(configuration, displayMetrics); 213 } 214 setFontScale(float fontScale)215 public static void setFontScale(float fontScale) { 216 Resources systemResources = getApplication().getResources(); 217 DisplayMetrics displayMetrics = systemResources.getDisplayMetrics(); 218 Configuration configuration = systemResources.getConfiguration(); 219 220 displayMetrics.scaledDensity = displayMetrics.density * fontScale; 221 configuration.fontScale = fontScale; 222 223 updateConfiguration(configuration, displayMetrics); 224 } 225 getFontScale()226 public static float getFontScale() { 227 Resources systemResources = getApplication().getResources(); 228 return systemResources.getConfiguration().fontScale; 229 } 230 updateConfiguration( Configuration configuration, DisplayMetrics displayMetrics)231 private static void updateConfiguration( 232 Configuration configuration, DisplayMetrics displayMetrics) { 233 // Update the resources last so that listeners will have a consistent environment. 234 if (ResourcesManager.getInstance().getConfiguration() != null) { 235 if (System.getProperty("robolectric.configurationChangeFix", "true").equals("true")) { 236 if (getApiLevel() <= Q) { 237 reflector(ResourcesManagerReflector.class, ResourcesManager.getInstance()) 238 .applyConfigurationToResourcesLocked(configuration, null); 239 } else { 240 ResourcesManager.getInstance().applyConfigurationToResources(configuration, null); 241 } 242 } else { 243 ResourcesManager.getInstance().getConfiguration().updateFrom(configuration); 244 } 245 } 246 Resources.getSystem().updateConfiguration(configuration, displayMetrics); 247 if (RuntimeEnvironment.application != null) { 248 getApplication().getResources().updateConfiguration(configuration, displayMetrics); 249 } else { 250 // if application is not yet loaded, update the configuration in Bootstrap so that the 251 // changes will be propagated once the application is finally loaded 252 Bootstrap.updateDisplayResources(configuration, displayMetrics); 253 } 254 } 255 getApiLevel()256 public static int getApiLevel() { 257 return apiLevel; 258 } 259 260 /** 261 * Retrieves the current master scheduler. This scheduler is always used by the main {@link 262 * android.os.Looper Looper}, and if the global scheduler option is set it is also used for the 263 * background scheduler and for all other {@link android.os.Looper Looper}s 264 * 265 * @return The current master scheduler. 266 * @see #setMasterScheduler(Scheduler) see 267 * org.robolectric.Robolectric#getForegroundThreadScheduler() see 268 * org.robolectric.Robolectric#getBackgroundThreadScheduler() 269 */ getMasterScheduler()270 public static Scheduler getMasterScheduler() { 271 return masterScheduler; 272 } 273 274 /** 275 * Sets the current master scheduler. See {@link #getMasterScheduler()} for details. Note that 276 * this method is primarily intended to be called by the Robolectric core setup code. Changing the 277 * master scheduler during a test will have unpredictable results. 278 * 279 * @param masterScheduler the new master scheduler. 280 * @see #getMasterScheduler() see org.robolectric.Robolectric#getForegroundThreadScheduler() see 281 * org.robolectric.Robolectric#getBackgroundThreadScheduler() 282 */ setMasterScheduler(Scheduler masterScheduler)283 public static void setMasterScheduler(Scheduler masterScheduler) { 284 RuntimeEnvironment.masterScheduler = masterScheduler; 285 } 286 setTempDirectory(TempDirectory tempDirectory)287 public static void setTempDirectory(TempDirectory tempDirectory) { 288 RuntimeEnvironment.tempDirectory = tempDirectory; 289 } 290 getTempDirectory()291 public static TempDirectory getTempDirectory() { 292 return tempDirectory; 293 } 294 setAndroidFrameworkJarPath(Path localArtifactPath)295 public static void setAndroidFrameworkJarPath(Path localArtifactPath) { 296 RuntimeEnvironment.androidFrameworkJar = localArtifactPath; 297 } 298 getAndroidFrameworkJarPath()299 public static Path getAndroidFrameworkJarPath() { 300 return RuntimeEnvironment.androidFrameworkJar; 301 } 302 303 /** internal use only */ setCompileTimeSystemResources( Supplier<Path> compileTimeSystemResourcesSupplier)304 public static void setCompileTimeSystemResources( 305 Supplier<Path> compileTimeSystemResourcesSupplier) { 306 RuntimeEnvironment.compileTimeSystemResourcesSupplier = compileTimeSystemResourcesSupplier; 307 } 308 309 /** 310 * @deprecated obsolete do not use 311 */ 312 @Deprecated getCompileTimeSystemResourcesPath()313 public static Path getCompileTimeSystemResourcesPath() { 314 return compileTimeSystemResourcesSupplier.get(); 315 } 316 317 @ForType(ResourcesManager.class) 318 interface ResourcesManagerReflector { applyConfigurationToResourcesLocked( Configuration configuration, CompatibilityInfo compatibilityInfo)319 boolean applyConfigurationToResourcesLocked( 320 Configuration configuration, CompatibilityInfo compatibilityInfo); 321 } 322 } 323