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