xref: /aosp_15_r20/external/robolectric/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
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