1 package org.robolectric; 2 3 import static org.robolectric.annotation.LooperMode.Mode.LEGACY; 4 import static org.robolectric.shadows.ShadowLooper.assertLooperMode; 5 6 import android.app.Application; 7 import android.app.ResourcesManager; 8 import android.content.Context; 9 import android.content.res.Configuration; 10 import android.content.res.Resources; 11 import android.graphics.Bitmap; 12 import android.util.DisplayMetrics; 13 import android.view.Display; 14 import com.google.common.base.Supplier; 15 import java.nio.file.Path; 16 import org.robolectric.android.Bootstrap; 17 import org.robolectric.android.ConfigurationV25; 18 import org.robolectric.res.ResourceTable; 19 import org.robolectric.shadows.ShadowDisplayManager; 20 import org.robolectric.shadows.ShadowInstrumentation; 21 import org.robolectric.shadows.ShadowView; 22 import org.robolectric.util.Scheduler; 23 import org.robolectric.util.TempDirectory; 24 25 public class RuntimeEnvironment { 26 /** 27 * @deprecated Use {@link #getApplication} instead. Note that unlike the alternative, this field 28 * is inherently incompatible with {@link 29 * org.robolectric.annotation.experimental.LazyApplication}. This field may be removed in a 30 * later release 31 */ 32 @Deprecated public static Context systemContext; 33 34 /** 35 * @deprecated Please use {#getApplication} instead. Accessing this field directly is inherently 36 * incompatible with {@link org.robolectric.annotation.experimental.LazyApplication} and 37 * Robolectric makes no guarantees if a test *modifies* this field during execution. 38 */ 39 @Deprecated public static volatile Application application; 40 41 private static volatile Thread mainThread; 42 private static volatile Object activityThread; 43 private static int apiLevel; 44 private static Scheduler masterScheduler; 45 private static ResourceTable systemResourceTable; 46 private static ResourceTable appResourceTable; 47 private static ResourceTable compileTimeResourceTable; 48 private static TempDirectory tempDirectory = new TempDirectory("no-test-yet"); 49 private static Path androidFrameworkJar; 50 public static Path compileTimeSystemResourcesFile; 51 52 private static Supplier<Application> applicationSupplier; 53 private static final Object supplierLock = new Object(); 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 // TODO(paulsowden): Can we call ResourcesManager.getInstance().applyConfigurationToResources()? 235 if (ResourcesManager.getInstance().getConfiguration() != null) { 236 ResourcesManager.getInstance().getConfiguration().updateFrom(configuration); 237 } 238 Resources.getSystem().updateConfiguration(configuration, displayMetrics); 239 if (RuntimeEnvironment.application != null) { 240 getApplication().getResources().updateConfiguration(configuration, displayMetrics); 241 } else { 242 // if application is not yet loaded, update the configuration in Bootstrap so that the 243 // changes will be propagated once the application is finally loaded 244 Bootstrap.updateDisplayResources(configuration, displayMetrics); 245 } 246 } 247 getApiLevel()248 public static int getApiLevel() { 249 return apiLevel; 250 } 251 252 /** 253 * Retrieves the current master scheduler. This scheduler is always used by the main {@link 254 * android.os.Looper Looper}, and if the global scheduler option is set it is also used for the 255 * background scheduler and for all other {@link android.os.Looper Looper}s 256 * 257 * @return The current master scheduler. 258 * @see #setMasterScheduler(Scheduler) see 259 * org.robolectric.Robolectric#getForegroundThreadScheduler() see 260 * org.robolectric.Robolectric#getBackgroundThreadScheduler() 261 */ getMasterScheduler()262 public static Scheduler getMasterScheduler() { 263 return masterScheduler; 264 } 265 266 /** 267 * Sets the current master scheduler. See {@link #getMasterScheduler()} for details. Note that 268 * this method is primarily intended to be called by the Robolectric core setup code. Changing the 269 * master scheduler during a test will have unpredictable results. 270 * 271 * @param masterScheduler the new master scheduler. 272 * @see #getMasterScheduler() see org.robolectric.Robolectric#getForegroundThreadScheduler() see 273 * org.robolectric.Robolectric#getBackgroundThreadScheduler() 274 */ setMasterScheduler(Scheduler masterScheduler)275 public static void setMasterScheduler(Scheduler masterScheduler) { 276 RuntimeEnvironment.masterScheduler = masterScheduler; 277 } 278 setSystemResourceTable(ResourceTable systemResourceTable)279 public static void setSystemResourceTable(ResourceTable systemResourceTable) { 280 RuntimeEnvironment.systemResourceTable = systemResourceTable; 281 } 282 setAppResourceTable(ResourceTable appResourceTable)283 public static void setAppResourceTable(ResourceTable appResourceTable) { 284 RuntimeEnvironment.appResourceTable = appResourceTable; 285 } 286 getSystemResourceTable()287 public static ResourceTable getSystemResourceTable() { 288 return systemResourceTable; 289 } 290 getAppResourceTable()291 public static ResourceTable getAppResourceTable() { 292 return appResourceTable; 293 } 294 setCompileTimeResourceTable(ResourceTable compileTimeResourceTable)295 public static void setCompileTimeResourceTable(ResourceTable compileTimeResourceTable) { 296 RuntimeEnvironment.compileTimeResourceTable = compileTimeResourceTable; 297 } 298 getCompileTimeResourceTable()299 public static ResourceTable getCompileTimeResourceTable() { 300 return compileTimeResourceTable; 301 } 302 setTempDirectory(TempDirectory tempDirectory)303 public static void setTempDirectory(TempDirectory tempDirectory) { 304 RuntimeEnvironment.tempDirectory = tempDirectory; 305 } 306 getTempDirectory()307 public static TempDirectory getTempDirectory() { 308 return tempDirectory; 309 } 310 setAndroidFrameworkJarPath(Path localArtifactPath)311 public static void setAndroidFrameworkJarPath(Path localArtifactPath) { 312 RuntimeEnvironment.androidFrameworkJar = localArtifactPath; 313 } 314 getAndroidFrameworkJarPath()315 public static Path getAndroidFrameworkJarPath() { 316 return RuntimeEnvironment.androidFrameworkJar; 317 } 318 } 319