1 package org.robolectric.shadows; 2 3 import android.annotation.RequiresApi; 4 import android.net.wifi.ScanResult; 5 import android.net.wifi.WifiSsid; 6 import android.os.Build; 7 import android.os.Build.VERSION; 8 import android.os.Build.VERSION_CODES; 9 import com.google.errorprone.annotations.CanIgnoreReturnValue; 10 import java.time.Duration; 11 import java.util.concurrent.TimeUnit; 12 import javax.annotation.Nullable; 13 14 /** 15 * Builder class for {@link ScanResult} allowing for more accurate construction of Wi-Fi scan 16 * results in test code. 17 */ 18 public final class WifiScanResultBuilder { 19 private static final int UNSPECIFIED = -1; 20 private static final int NATIVE_BUILDER_MIN_SDK = VERSION_CODES.VANILLA_ICE_CREAM; 21 private static final boolean USE_NATIVE_BUILDER = Build.VERSION.SDK_INT >= NATIVE_BUILDER_MIN_SDK; 22 23 // Available in or before API 21+ 24 @Nullable private String ssid; 25 @Nullable private String bssid; 26 @Nullable private String capabilities; 27 private int rssi = UNSPECIFIED; 28 private int frequency = UNSPECIFIED; 29 private Duration timeSinceSeen = Duration.ZERO; 30 31 // Added in API 23 32 private int channelWidth = ScanResult.CHANNEL_WIDTH_20MHZ; 33 private int centerFreq0 = UNSPECIFIED; 34 private int centerFreq1 = UNSPECIFIED; 35 private boolean is80211McRttResponder = false; 36 37 // Added in API 33 38 @Nullable private WifiSsid wifiSsid; 39 40 // Added in API 35. Past this API level all setters are delegated to the real ScanResult.Builder. 41 @Nullable private ScanResult.Builder realBuilder; 42 WifiScanResultBuilder()43 public WifiScanResultBuilder() {} 44 45 /** 46 * Sets the value of the {@link ScanResult#capabilities} field. 47 * 48 * @return this builder, for chaining 49 */ 50 @CanIgnoreReturnValue setCapabilities(@ullable String capabilities)51 public WifiScanResultBuilder setCapabilities(@Nullable String capabilities) { 52 if (USE_NATIVE_BUILDER) { 53 ensureRealBuilder().setCaps(capabilities); 54 } else { 55 this.capabilities = capabilities; 56 } 57 return this; 58 } 59 60 /** 61 * Sets the value of the {@link ScanResult#BSSID} field. 62 * 63 * @return this builder, for chaining 64 */ 65 @CanIgnoreReturnValue setBssid(@ullable String bssid)66 public WifiScanResultBuilder setBssid(@Nullable String bssid) { 67 if (USE_NATIVE_BUILDER) { 68 ensureRealBuilder().setBssid(bssid); 69 } else { 70 this.bssid = bssid; 71 } 72 return this; 73 } 74 75 /** 76 * Sets the value of the {@link ScanResult#level} field (aka RSSI). 77 * 78 * @return this builder, for chaining 79 */ 80 @CanIgnoreReturnValue setRssi(int rssi)81 public WifiScanResultBuilder setRssi(int rssi) { 82 if (USE_NATIVE_BUILDER) { 83 ensureRealBuilder().setRssi(rssi); 84 } else { 85 this.rssi = rssi; 86 } 87 return this; 88 } 89 90 /** 91 * Sets the value of the frequency, in MHz, returned by the {@link ScanResult#frequency} field. 92 * 93 * @return this builder, for chaining 94 */ 95 @CanIgnoreReturnValue setFrequency(int frequency)96 public WifiScanResultBuilder setFrequency(int frequency) { 97 if (USE_NATIVE_BUILDER) { 98 ensureRealBuilder().setFrequency(frequency); 99 } else { 100 this.frequency = frequency; 101 } 102 return this; 103 } 104 105 /** 106 * Sets the value of the {@link ScanResult#SSID} field. On API 33 and above, this also sets the 107 * return value of {@link ScanResult#getWifiSsid()}. 108 * 109 * @param ssid the name of the network, or null; this should be either a UTF-8 encoded string, 110 * surrounded by quotes (e.g. '"Network name"'), or an unquoted hex-encoded string (e.g. 111 * '0a8b2c1f'). 112 * @return this builder, for chaining 113 */ 114 @CanIgnoreReturnValue setSsid(@ullable String ssid)115 public WifiScanResultBuilder setSsid(@Nullable String ssid) { 116 if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { 117 setWifiSsid(ssid != null ? WifiSsid.fromString(ssid) : null); 118 } else { 119 this.ssid = ssid; 120 } 121 return this; 122 } 123 124 /** 125 * Sets the duration since boot since the result was seen. 126 * 127 * @see ScanResult#timestamp 128 * @return this builder, for chaining 129 */ 130 @CanIgnoreReturnValue setTimeSinceSeen(Duration timeSinceSeen)131 public WifiScanResultBuilder setTimeSinceSeen(Duration timeSinceSeen) { 132 if (USE_NATIVE_BUILDER) { 133 ensureRealBuilder().setTsf(TimeUnit.MILLISECONDS.toMicros(timeSinceSeen.toMillis())); 134 } else { 135 this.timeSinceSeen = timeSinceSeen; 136 } 137 return this; 138 } 139 140 /** 141 * Sets the value returned by {@link ScanResult#getWifiSsid()}, additionally setting the 142 * deprecated {@link ScanResult#SSID} field. 143 * 144 * @return this builder, for chaining 145 */ 146 @RequiresApi(VERSION_CODES.TIRAMISU) 147 @CanIgnoreReturnValue setWifiSsid(@ullable WifiSsid wifiSsid)148 public WifiScanResultBuilder setWifiSsid(@Nullable WifiSsid wifiSsid) { 149 if (USE_NATIVE_BUILDER) { 150 ensureRealBuilder().setWifiSsid(wifiSsid); 151 } else { 152 this.wifiSsid = wifiSsid; 153 this.ssid = wifiSsid == null ? null : wifiSsid.toString(); 154 } 155 return this; 156 } 157 158 /** 159 * Sets the value returned by {@link ScanResult#channelWidth}. This should be one of the constants 160 * in {@link ScanResult} such as {@link ScanResult#CHANNEL_WIDTH_20MHZ}. 161 * 162 * @return this builder, for chaining 163 */ 164 @RequiresApi(VERSION_CODES.M) 165 @CanIgnoreReturnValue setChannelWidth(int channelWidth)166 public WifiScanResultBuilder setChannelWidth(int channelWidth) { 167 if (USE_NATIVE_BUILDER) { 168 ensureRealBuilder().setChannelWidth(channelWidth); 169 } else { 170 this.channelWidth = channelWidth; 171 } 172 return this; 173 } 174 175 /** 176 * Sets the center frequency, in MHz, of the first segment (see {@link ScanResult#centerFreq0}). 177 * 178 * @return this builder, for chaining 179 */ 180 @RequiresApi(VERSION_CODES.M) 181 @CanIgnoreReturnValue setCenterFreq0(int centerFreq0)182 public WifiScanResultBuilder setCenterFreq0(int centerFreq0) { 183 if (USE_NATIVE_BUILDER) { 184 ensureRealBuilder().setCenterFreq0(centerFreq0); 185 } else { 186 this.centerFreq0 = centerFreq0; 187 } 188 return this; 189 } 190 191 /** 192 * Sets the center frequency, in MHz, of the second segment (see {@link ScanResult#centerFreq1}). 193 * 194 * @return this builder, for chaining 195 */ 196 @CanIgnoreReturnValue 197 @RequiresApi(VERSION_CODES.M) setCenterFreq1(int centerFreq1)198 public WifiScanResultBuilder setCenterFreq1(int centerFreq1) { 199 if (USE_NATIVE_BUILDER) { 200 ensureRealBuilder().setCenterFreq1(centerFreq1); 201 } else { 202 this.centerFreq1 = centerFreq1; 203 } 204 return this; 205 } 206 207 /** 208 * Sets the return value of {@link ScanResult#is80211mcResponder()}. 209 * 210 * @return this builder, for chaining 211 */ 212 @CanIgnoreReturnValue 213 @RequiresApi(VERSION_CODES.M) setIs80211McRttResponder(boolean is80211McRttResponder)214 public WifiScanResultBuilder setIs80211McRttResponder(boolean is80211McRttResponder) { 215 if (USE_NATIVE_BUILDER) { 216 ensureRealBuilder().setIs80211McRTTResponder(is80211McRttResponder); 217 } else { 218 this.is80211McRttResponder = is80211McRttResponder; 219 } 220 return this; 221 } 222 223 /** 224 * Sets the return value of {@link ScanResult#is80211azNtbResponder()}. 225 * 226 * @return this builder, for chaining 227 */ 228 @CanIgnoreReturnValue 229 @RequiresApi(VERSION_CODES.VANILLA_ICE_CREAM) setIs80211azNtbRttResponder(boolean is80211azNtbRttResponder)230 public WifiScanResultBuilder setIs80211azNtbRttResponder(boolean is80211azNtbRttResponder) { 231 ensureRealBuilder().setIs80211azNtbRTTResponder(is80211azNtbRttResponder); 232 return this; 233 } 234 235 /** 236 * Sets the return value of {@link ScanResult#isTwtResponder()}. 237 * 238 * @return this builder, for chaining 239 */ 240 @CanIgnoreReturnValue 241 @RequiresApi(VERSION_CODES.VANILLA_ICE_CREAM) setIsTwtResponder(boolean isTwtResponder)242 public WifiScanResultBuilder setIsTwtResponder(boolean isTwtResponder) { 243 ensureRealBuilder().setIsTwtResponder(isTwtResponder); 244 return this; 245 } 246 247 /** Returns a new {@link ScanResult} instance as configured in this builder. */ build()248 public ScanResult build() { 249 if (USE_NATIVE_BUILDER) { 250 return ensureRealBuilder().build(); 251 } 252 253 long timestampMicros = TimeUnit.MILLISECONDS.toMicros(timeSinceSeen.toMillis()); 254 ScanResult scanResult; 255 if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { 256 scanResult = 257 new ScanResult( 258 wifiSsid, 259 bssid, 260 /* hessid= */ UNSPECIFIED, 261 /* anqpDomainId= */ UNSPECIFIED, 262 /* osuProviders= */ null, 263 capabilities, 264 rssi, 265 frequency, 266 timestampMicros); 267 } else { 268 scanResult = new ScanResult(); 269 scanResult.SSID = ssid; 270 scanResult.BSSID = bssid; 271 scanResult.capabilities = capabilities; 272 scanResult.level = rssi; 273 scanResult.frequency = frequency; 274 scanResult.timestamp = timestampMicros; 275 } 276 277 if (VERSION.SDK_INT >= VERSION_CODES.M) { 278 scanResult.channelWidth = channelWidth; 279 scanResult.centerFreq0 = centerFreq0; 280 scanResult.centerFreq1 = centerFreq1; 281 if (is80211McRttResponder) { 282 scanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER); 283 } 284 } 285 286 return scanResult; 287 } 288 289 @RequiresApi(NATIVE_BUILDER_MIN_SDK) ensureRealBuilder()290 private ScanResult.Builder ensureRealBuilder() { 291 if (realBuilder == null) { 292 realBuilder = new ScanResult.Builder(); 293 } 294 return realBuilder; 295 } 296 } 297