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