1 package org.robolectric;
2 
3 import com.google.common.collect.Lists;
4 import com.google.common.collect.Sets;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Set;
11 import java.util.TreeSet;
12 import javax.annotation.Nonnull;
13 import javax.annotation.Nullable;
14 import org.robolectric.annotation.Config;
15 import org.robolectric.annotation.internal.ConfigUtils;
16 import org.robolectric.internal.SdkConfig;
17 
18 public class SdkPicker {
19   private final Set<SdkConfig> supportedSdks;
20   private final Set<SdkConfig> enabledSdks;
21   private final SdkConfig minSupportedSdk;
22   private final SdkConfig maxSupportedSdk;
23 
SdkPicker( @onnull Collection<SdkConfig> supportedSdks, @Nullable Collection<SdkConfig> enabledSdks)24   public SdkPicker(
25       @Nonnull Collection<SdkConfig> supportedSdks, @Nullable Collection<SdkConfig> enabledSdks) {
26     TreeSet<SdkConfig> sdkConfigs = new TreeSet<>(supportedSdks);
27     this.supportedSdks = sdkConfigs;
28     this.enabledSdks = enabledSdks == null ? null : new TreeSet<>(enabledSdks);
29     minSupportedSdk = sdkConfigs.first();
30     maxSupportedSdk = sdkConfigs.last();
31   }
32 
33   /**
34    * Enumerate the SDKs to be used for this test.
35    *
36    * @param config a {@link Config} specifying one or more SDKs
37    * @param usesSdk the {@link UsesSdk} for the test
38    * @return the list of candidate {@link SdkConfig}s.
39    * @since 3.9
40    */
41   @Nonnull
selectSdks(Config config, UsesSdk usesSdk)42   public List<SdkConfig> selectSdks(Config config, UsesSdk usesSdk) {
43     Set<SdkConfig> sdks = new TreeSet<>(configuredSdks(config, usesSdk));
44     if (enabledSdks != null) {
45       sdks = Sets.intersection(sdks, enabledSdks);
46     }
47     return Lists.newArrayList(sdks);
48   }
49 
50   @Nullable
enumerateEnabledSdks(String enabledSdks)51   protected static Set<SdkConfig> enumerateEnabledSdks(String enabledSdks) {
52     if (enabledSdks == null || enabledSdks.isEmpty()) {
53       return null;
54     } else {
55       Set<SdkConfig> enabledSdkConfigs = new HashSet<>();
56       for (int sdk : ConfigUtils.parseSdkArrayProperty(enabledSdks)) {
57         enabledSdkConfigs.add(new SdkConfig(sdk));
58       }
59       return enabledSdkConfigs;
60     }
61   }
62 
configuredSdks(Config config, UsesSdk usesSdk)63   protected Set<SdkConfig> configuredSdks(Config config, UsesSdk usesSdk) {
64     int appMinSdk = Math.max(usesSdk.getMinSdkVersion(), minSupportedSdk.getApiLevel());
65     int appTargetSdk = Math.max(usesSdk.getTargetSdkVersion(), minSupportedSdk.getApiLevel());
66     Integer appMaxSdk = usesSdk.getMaxSdkVersion();
67     if (appMaxSdk == null) {
68       appMaxSdk = maxSupportedSdk.getApiLevel();
69     }
70 
71     // For min/max SDK ranges...
72     int minSdk = config.minSdk();
73     int maxSdk = config.maxSdk();
74     if (minSdk != -1 || maxSdk != -1) {
75       int rangeMin = decodeSdk(minSdk, appMinSdk, appMinSdk, appTargetSdk, appMaxSdk);
76       int rangeMax = decodeSdk(maxSdk, appMaxSdk, appMinSdk, appTargetSdk, appMaxSdk);
77 
78       if (rangeMin > rangeMax && (minSdk == -1 || maxSdk == -1)) {
79         return Collections.emptySet();
80       }
81 
82       return sdkRange(rangeMin, rangeMax);
83     }
84 
85     // For explicitly-enumerated SDKs...
86     if (config.sdk().length == 0) {
87       if (appTargetSdk < appMinSdk) {
88         throw new IllegalArgumentException(
89             "Package targetSdkVersion=" + appTargetSdk + " < minSdkVersion=" + appMinSdk);
90       } else if (appMaxSdk != 0 && appTargetSdk > appMaxSdk) {
91         throw new IllegalArgumentException(
92             "Package targetSdkVersion=" + appTargetSdk + " > maxSdkVersion=" + appMaxSdk);
93       }
94       return Collections.singleton(new SdkConfig(appTargetSdk));
95     }
96 
97     if (config.sdk().length == 1 && config.sdk()[0] == Config.ALL_SDKS) {
98       return sdkRange(appMinSdk, appMaxSdk);
99     }
100 
101     Set<SdkConfig> sdkConfigs = new HashSet<>();
102     for (int sdk : config.sdk()) {
103       int decodedApiLevel = decodeSdk(sdk, appTargetSdk, appMinSdk, appTargetSdk, appMaxSdk);
104       sdkConfigs.add(new SdkConfig(decodedApiLevel));
105     }
106     return sdkConfigs;
107   }
108 
decodeSdk( int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk)109   protected int decodeSdk(
110       int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk) {
111     if (value == Config.DEFAULT_VALUE_INT) {
112       return defaultSdk;
113     } else if (value == Config.NEWEST_SDK) {
114       return appMaxSdk;
115     } else if (value == Config.OLDEST_SDK) {
116       return appMinSdk;
117     } else if (value == Config.TARGET_SDK) {
118       return appTargetSdk;
119     } else {
120       return value;
121     }
122   }
123 
124   @Nonnull
sdkRange(int minSdk, int maxSdk)125   protected Set<SdkConfig> sdkRange(int minSdk, int maxSdk) {
126     if (maxSdk < minSdk) {
127       throw new IllegalArgumentException("minSdk=" + minSdk + " is greater than maxSdk=" + maxSdk);
128     }
129 
130     Set<SdkConfig> sdkConfigs = new HashSet<>();
131     for (SdkConfig supportedSdk : supportedSdks) {
132       int apiLevel = supportedSdk.getApiLevel();
133       if (apiLevel >= minSdk && supportedSdk.getApiLevel() <= maxSdk) {
134         sdkConfigs.add(supportedSdk);
135       }
136     }
137 
138     if (sdkConfigs.isEmpty()) {
139       throw new IllegalArgumentException(
140           "No matching SDKs found for minSdk=" + minSdk + ", maxSdk=" + maxSdk);
141     }
142 
143     return sdkConfigs;
144   }
145 
146   @Nonnull
map(int... supportedSdks)147   static List<SdkConfig> map(int... supportedSdks) {
148     ArrayList<SdkConfig> sdkConfigs = new ArrayList<>();
149     for (int supportedSdk : supportedSdks) {
150       sdkConfigs.add(new SdkConfig(supportedSdk));
151     }
152     return sdkConfigs;
153   }
154 }
155