xref: /aosp_15_r20/external/robolectric/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkPicker.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.plugins;
2 
3 import com.google.auto.service.AutoService;
4 import com.google.common.annotations.VisibleForTesting;
5 import com.google.common.collect.Lists;
6 import com.google.common.collect.Sets;
7 import java.util.Collections;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.NoSuchElementException;
11 import java.util.Properties;
12 import java.util.Set;
13 import java.util.SortedSet;
14 import java.util.TreeSet;
15 import javax.annotation.Nonnull;
16 import javax.annotation.Nullable;
17 import javax.annotation.Priority;
18 import javax.inject.Inject;
19 import org.robolectric.annotation.Config;
20 import org.robolectric.annotation.internal.ConfigUtils;
21 import org.robolectric.pluginapi.Sdk;
22 import org.robolectric.pluginapi.SdkPicker;
23 import org.robolectric.pluginapi.UsesSdk;
24 import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration;
25 
26 /** Robolectric's default {@link SdkPicker}. */
27 @SuppressWarnings("NewApi")
28 @AutoService(SdkPicker.class)
29 @Priority(Integer.MIN_VALUE)
30 public class DefaultSdkPicker implements SdkPicker {
31   @Nonnull private final SdkCollection sdkCollection;
32 
33   private final Set<Sdk> enabledSdks;
34   @Nonnull private final Sdk minKnownSdk;
35   @Nonnull private final Sdk maxKnownSdk;
36 
37   @Inject
DefaultSdkPicker(@onnull SdkCollection sdkCollection, Properties systemProperties)38   public DefaultSdkPicker(@Nonnull SdkCollection sdkCollection, Properties systemProperties) {
39     this(
40         sdkCollection,
41         systemProperties == null ? null : systemProperties.getProperty("robolectric.enabledSdks"));
42   }
43 
44   @VisibleForTesting
DefaultSdkPicker(@onnull SdkCollection sdkCollection, String enabledSdks)45   protected DefaultSdkPicker(@Nonnull SdkCollection sdkCollection, String enabledSdks) {
46     this.sdkCollection = sdkCollection;
47     this.enabledSdks = enumerateEnabledSdks(sdkCollection, enabledSdks);
48 
49     SortedSet<Sdk> sdks = this.sdkCollection.getKnownSdks();
50     try {
51       minKnownSdk = sdks.first();
52       maxKnownSdk = sdks.last();
53     } catch (NoSuchElementException e) {
54       throw new RuntimeException("no SDKs are supported among " + sdkCollection.getKnownSdks(), e);
55     }
56   }
57 
58   /**
59    * Enumerate the SDKs to be used for this test.
60    *
61    * @param configuration a collection of configuration objects, including {@link Config}
62    * @param usesSdk the {@link UsesSdk} for the test
63    * @return the list of candidate {@link Sdk}s.
64    * @since 3.9
65    */
66   @Override
67   @Nonnull
selectSdks(Configuration configuration, UsesSdk usesSdk)68   public List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk) {
69     Config config = configuration.get(Config.class);
70     Set<Sdk> sdks = new TreeSet<>(configuredSdks(config, usesSdk));
71     if (enabledSdks != null) {
72       sdks = Sets.intersection(sdks, enabledSdks);
73     }
74     return Lists.newArrayList(sdks);
75   }
76 
77   @Nullable
enumerateEnabledSdks( SdkCollection sdkCollection, String enabledSdksString)78   protected static Set<Sdk> enumerateEnabledSdks(
79       SdkCollection sdkCollection, String enabledSdksString) {
80     if (enabledSdksString == null || enabledSdksString.isEmpty()) {
81       return null;
82     } else {
83       Set<Sdk> enabledSdks = new HashSet<>();
84       for (int sdk : ConfigUtils.parseSdkArrayProperty(enabledSdksString)) {
85         enabledSdks.add(sdkCollection.getSdk(sdk));
86       }
87       return enabledSdks;
88     }
89   }
90 
configuredSdks(Config config, UsesSdk usesSdk)91   protected Set<Sdk> configuredSdks(Config config, UsesSdk usesSdk) {
92     int appMinSdk = Math.max(usesSdk.getMinSdkVersion(), minKnownSdk.getApiLevel());
93     int appTargetSdk = Math.max(usesSdk.getTargetSdkVersion(), minKnownSdk.getApiLevel());
94     Integer appMaxSdk = usesSdk.getMaxSdkVersion();
95     if (appMaxSdk == null) {
96       appMaxSdk = maxKnownSdk.getApiLevel();
97     }
98 
99     // For min/max SDK ranges...
100     int minSdk = config.minSdk();
101     int maxSdk = config.maxSdk();
102     if (minSdk != -1 || maxSdk != -1) {
103       int rangeMin = decodeSdk(minSdk, appMinSdk, appMinSdk, appTargetSdk, appMaxSdk);
104       int rangeMax = decodeSdk(maxSdk, appMaxSdk, appMinSdk, appTargetSdk, appMaxSdk);
105 
106       if (rangeMin > rangeMax && (minSdk == -1 || maxSdk == -1)) {
107         return Collections.emptySet();
108       }
109 
110       return sdkRange(rangeMin, rangeMax);
111     }
112 
113     // For explicitly-enumerated SDKs...
114     if (config.sdk().length == 0) {
115       if (appTargetSdk < appMinSdk) {
116         throw new IllegalArgumentException(
117             "Package targetSdkVersion=" + appTargetSdk + " < minSdkVersion=" + appMinSdk);
118       } else if (appMaxSdk != 0 && appTargetSdk > appMaxSdk) {
119         throw new IllegalArgumentException(
120             "Package targetSdkVersion=" + appTargetSdk + " > maxSdkVersion=" + appMaxSdk);
121       }
122       return Collections.singleton(sdkCollection.getSdk(appTargetSdk));
123     }
124 
125     if (config.sdk().length == 1 && config.sdk()[0] == Config.ALL_SDKS) {
126       return sdkRange(appMinSdk, appMaxSdk);
127     }
128 
129     Set<Sdk> sdks = new HashSet<>();
130     for (int sdk : config.sdk()) {
131       int decodedApiLevel = decodeSdk(sdk, appTargetSdk, appMinSdk, appTargetSdk, appMaxSdk);
132       sdks.add(sdkCollection.getSdk(decodedApiLevel));
133     }
134     return sdks;
135   }
136 
decodeSdk( int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk)137   protected int decodeSdk(
138       int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk) {
139     if (value == Config.DEFAULT_VALUE_INT) {
140       return defaultSdk;
141     } else if (value == Config.NEWEST_SDK) {
142       return appMaxSdk;
143     } else if (value == Config.OLDEST_SDK) {
144       return appMinSdk;
145     } else if (value == Config.TARGET_SDK) {
146       return appTargetSdk;
147     } else {
148       return value;
149     }
150   }
151 
152   @Nonnull
sdkRange(int minSdk, int maxSdk)153   protected Set<Sdk> sdkRange(int minSdk, int maxSdk) {
154     if (maxSdk < minSdk) {
155       throw new IllegalArgumentException("minSdk=" + minSdk + " is greater than maxSdk=" + maxSdk);
156     }
157 
158     Set<Sdk> sdks = new HashSet<>();
159     for (Sdk knownSdk : sdkCollection.getKnownSdks()) {
160       int apiLevel = knownSdk.getApiLevel();
161       if (apiLevel >= minSdk && knownSdk.getApiLevel() <= maxSdk) {
162         sdks.add(knownSdk);
163       }
164     }
165 
166     if (sdks.isEmpty()) {
167       throw new IllegalArgumentException(
168           "No matching SDKs found for minSdk=" + minSdk + ", maxSdk=" + maxSdk);
169     }
170 
171     return sdks;
172   }
173 }
174