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(sdkCollection, 40 systemProperties == null ? null : systemProperties.getProperty("robolectric.enabledSdks")); 41 } 42 43 @VisibleForTesting DefaultSdkPicker(@onnull SdkCollection sdkCollection, String enabledSdks)44 protected DefaultSdkPicker(@Nonnull SdkCollection sdkCollection, String enabledSdks) { 45 this.sdkCollection = sdkCollection; 46 this.enabledSdks = enumerateEnabledSdks(sdkCollection, enabledSdks); 47 48 SortedSet<Sdk> sdks = this.sdkCollection.getKnownSdks(); 49 try { 50 minKnownSdk = sdks.first(); 51 maxKnownSdk = sdks.last(); 52 } catch (NoSuchElementException e) { 53 throw new RuntimeException("no SDKs are supported among " + sdkCollection.getKnownSdks(), e); 54 } 55 } 56 57 /** 58 * Enumerate the SDKs to be used for this test. 59 * 60 * @param configuration a collection of configuration objects, including {@link Config} 61 * @param usesSdk the {@link UsesSdk} for the test 62 * @return the list of candidate {@link Sdk}s. 63 * @since 3.9 64 */ 65 @Override 66 @Nonnull selectSdks(Configuration configuration, UsesSdk usesSdk)67 public List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk) { 68 Config config = configuration.get(Config.class); 69 Set<Sdk> sdks = new TreeSet<>(configuredSdks(config, usesSdk)); 70 if (enabledSdks != null) { 71 sdks = Sets.intersection(sdks, enabledSdks); 72 } 73 return Lists.newArrayList(sdks); 74 } 75 76 @Nullable enumerateEnabledSdks( SdkCollection sdkCollection, String enabledSdksString)77 protected static Set<Sdk> enumerateEnabledSdks( 78 SdkCollection sdkCollection, String enabledSdksString) { 79 if (enabledSdksString == null || enabledSdksString.isEmpty()) { 80 return null; 81 } else { 82 Set<Sdk> enabledSdks = new HashSet<>(); 83 for (int sdk : ConfigUtils.parseSdkArrayProperty(enabledSdksString)) { 84 enabledSdks.add(sdkCollection.getSdk(sdk)); 85 } 86 return enabledSdks; 87 } 88 } 89 configuredSdks(Config config, UsesSdk usesSdk)90 protected Set<Sdk> configuredSdks(Config config, UsesSdk usesSdk) { 91 int appMinSdk = Math.max(usesSdk.getMinSdkVersion(), minKnownSdk.getApiLevel()); 92 int appTargetSdk = Math.max(usesSdk.getTargetSdkVersion(), minKnownSdk.getApiLevel()); 93 Integer appMaxSdk = usesSdk.getMaxSdkVersion(); 94 if (appMaxSdk == null) { 95 appMaxSdk = maxKnownSdk.getApiLevel(); 96 } 97 98 // For min/max SDK ranges... 99 int minSdk = config.minSdk(); 100 int maxSdk = config.maxSdk(); 101 if (minSdk != -1 || maxSdk != -1) { 102 int rangeMin = decodeSdk(minSdk, appMinSdk, appMinSdk, appTargetSdk, appMaxSdk); 103 int rangeMax = decodeSdk(maxSdk, appMaxSdk, appMinSdk, appTargetSdk, appMaxSdk); 104 105 if (rangeMin > rangeMax && (minSdk == -1 || maxSdk == -1)) { 106 return Collections.emptySet(); 107 } 108 109 return sdkRange(rangeMin, rangeMax); 110 } 111 112 // For explicitly-enumerated SDKs... 113 if (config.sdk().length == 0) { 114 if (appTargetSdk < appMinSdk) { 115 throw new IllegalArgumentException( 116 "Package targetSdkVersion=" + appTargetSdk + " < minSdkVersion=" + appMinSdk); 117 } else if (appMaxSdk != 0 && appTargetSdk > appMaxSdk) { 118 throw new IllegalArgumentException( 119 "Package targetSdkVersion=" + appTargetSdk + " > maxSdkVersion=" + appMaxSdk); 120 } 121 return Collections.singleton(sdkCollection.getSdk(appTargetSdk)); 122 } 123 124 if (config.sdk().length == 1 && config.sdk()[0] == Config.ALL_SDKS) { 125 return sdkRange(appMinSdk, appMaxSdk); 126 } 127 128 Set<Sdk> sdks = new HashSet<>(); 129 for (int sdk : config.sdk()) { 130 int decodedApiLevel = decodeSdk(sdk, appTargetSdk, appMinSdk, appTargetSdk, appMaxSdk); 131 sdks.add(sdkCollection.getSdk(decodedApiLevel)); 132 } 133 return sdks; 134 } 135 decodeSdk( int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk)136 protected int decodeSdk( 137 int value, int defaultSdk, int appMinSdk, int appTargetSdk, int appMaxSdk) { 138 if (value == Config.DEFAULT_VALUE_INT) { 139 return defaultSdk; 140 } else if (value == Config.NEWEST_SDK) { 141 return appMaxSdk; 142 } else if (value == Config.OLDEST_SDK) { 143 return appMinSdk; 144 } else if (value == Config.TARGET_SDK) { 145 return appTargetSdk; 146 } else { 147 return value; 148 } 149 } 150 151 @Nonnull sdkRange(int minSdk, int maxSdk)152 protected Set<Sdk> sdkRange(int minSdk, int maxSdk) { 153 if (maxSdk < minSdk) { 154 throw new IllegalArgumentException("minSdk=" + minSdk + " is greater than maxSdk=" + maxSdk); 155 } 156 157 Set<Sdk> sdks = new HashSet<>(); 158 for (Sdk knownSdk : sdkCollection.getKnownSdks()) { 159 int apiLevel = knownSdk.getApiLevel(); 160 if (apiLevel >= minSdk && knownSdk.getApiLevel() <= maxSdk) { 161 sdks.add(knownSdk); 162 } 163 } 164 165 if (sdks.isEmpty()) { 166 throw new IllegalArgumentException( 167 "No matching SDKs found for minSdk=" + minSdk + ", maxSdk=" + maxSdk); 168 } 169 170 return sdks; 171 } 172 173 } 174