All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowWifiManager Maven / Gradle / Ivy

package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;

import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.AddNetworkResult;
import android.net.wifi.WifiManager.MulticastLock;
import android.net.wifi.WifiUsabilityStatsEntry;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Pair;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;

/** Shadow for {@link android.net.wifi.WifiManager}. */
@Implements(value = WifiManager.class, looseSignatures = true)
@SuppressWarnings("AndroidConcurrentHashMap")
public class ShadowWifiManager {
  private static final int LOCAL_HOST = 2130706433;

  private static float sSignalLevelInPercent = 1f;
  private boolean accessWifiStatePermission = true;
  private boolean changeWifiStatePermission = true;
  private int wifiState = WifiManager.WIFI_STATE_ENABLED;
  private boolean wasSaved = false;
  private WifiInfo wifiInfo;
  private List scanResults;
  private final Map networkIdToConfiguredNetworks =
      new LinkedHashMap<>();
  private Pair lastEnabledNetwork;
  private final Set enabledNetworks = new HashSet<>();
  private DhcpInfo dhcpInfo;
  private boolean startScanSucceeds = true;
  private boolean is5GHzBandSupported = false;
  private boolean isStaApConcurrencySupported = false;
  private AtomicInteger activeLockCount = new AtomicInteger(0);
  private final BitSet readOnlyNetworkIds = new BitSet();
  private final ConcurrentHashMap
      wifiUsabilityStatsListeners = new ConcurrentHashMap<>();
  private final List usabilityScores = new ArrayList<>();
  private Object networkScorer;
  @RealObject WifiManager wifiManager;
  private WifiConfiguration apConfig;
  private SoftApConfiguration softApConfig;

  @Implementation
  protected boolean setWifiEnabled(boolean wifiEnabled) {
    checkAccessWifiStatePermission();
    this.wifiState = wifiEnabled ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED;
    return true;
  }

  public void setWifiState(int wifiState) {
    checkAccessWifiStatePermission();
    this.wifiState = wifiState;
  }

  @Implementation
  protected boolean isWifiEnabled() {
    checkAccessWifiStatePermission();
    return wifiState == WifiManager.WIFI_STATE_ENABLED;
  }

  @Implementation
  protected int getWifiState() {
    checkAccessWifiStatePermission();
    return wifiState;
  }

  @Implementation
  protected WifiInfo getConnectionInfo() {
    checkAccessWifiStatePermission();
    if (wifiInfo == null) {
      wifiInfo = ReflectionHelpers.callConstructor(WifiInfo.class);
    }
    return wifiInfo;
  }

  @Implementation(minSdk = LOLLIPOP)
  protected boolean is5GHzBandSupported() {
    return is5GHzBandSupported;
  }

  /** Sets whether 5ghz band is supported. */
  public void setIs5GHzBandSupported(boolean is5GHzBandSupported) {
    this.is5GHzBandSupported = is5GHzBandSupported;
  }

  /** Returns last value provided to #setStaApConcurrencySupported. */
  @Implementation(minSdk = R)
  protected boolean isStaApConcurrencySupported() {
    return isStaApConcurrencySupported;
  }

  /** Sets whether STA/AP concurrency is supported. */
  public void setStaApConcurrencySupported(boolean isStaApConcurrencySupported) {
    this.isStaApConcurrencySupported = isStaApConcurrencySupported;
  }

  /** Sets the connection info as the provided {@link WifiInfo}. */
  public void setConnectionInfo(WifiInfo wifiInfo) {
    this.wifiInfo = wifiInfo;
  }

  /** Sets the return value of {@link #startScan}. */
  public void setStartScanSucceeds(boolean succeeds) {
    this.startScanSucceeds = succeeds;
  }

  @Implementation
  protected List getScanResults() {
    return scanResults;
  }

  /**
   * The original implementation allows this to be called by the Device Owner (DO), Profile Owner
   * (PO), callers with carrier privilege and system apps, but this shadow can be called by all apps
   * carrying the ACCESS_WIFI_STATE permission.
   *
   * 

This shadow is a wrapper for getConfiguredNetworks() and does not actually check the caller. */ @Implementation(minSdk = S) protected List getCallerConfiguredNetworks() { checkAccessWifiStatePermission(); return getConfiguredNetworks(); } @Implementation protected List getConfiguredNetworks() { final ArrayList wifiConfigurations = new ArrayList<>(); for (WifiConfiguration wifiConfiguration : networkIdToConfiguredNetworks.values()) { wifiConfigurations.add(wifiConfiguration); } return wifiConfigurations; } @Implementation(minSdk = LOLLIPOP) protected List getPrivilegedConfiguredNetworks() { return getConfiguredNetworks(); } @Implementation protected int addNetwork(WifiConfiguration config) { if (config == null) { return -1; } int networkId = networkIdToConfiguredNetworks.size(); config.networkId = -1; networkIdToConfiguredNetworks.put(networkId, makeCopy(config, networkId)); return networkId; } /** * The new version of {@link #addNetwork(WifiConfiguration)} which returns a more detailed failure * codes. The original implementation of this API is limited to Device Owner (DO), Profile Owner * (PO), system app, and privileged apps but this shadow can be called by all apps. */ @Implementation(minSdk = S) protected AddNetworkResult addNetworkPrivileged(WifiConfiguration config) { if (config == null) { throw new IllegalArgumentException("config cannot be null"); } int networkId = addNetwork(config); return new AddNetworkResult(AddNetworkResult.STATUS_SUCCESS, networkId); } @Implementation protected boolean removeNetwork(int netId) { networkIdToConfiguredNetworks.remove(netId); return true; } /** * Removes all configured networks regardless of the app that created the network. Can only be * called by a Device Owner (DO) app. * * @return {@code true} if at least one network is removed, {@code false} otherwise */ @Implementation(minSdk = S) protected boolean removeNonCallerConfiguredNetworks() { checkChangeWifiStatePermission(); checkDeviceOwner(); int previousSize = networkIdToConfiguredNetworks.size(); networkIdToConfiguredNetworks.clear(); return networkIdToConfiguredNetworks.size() < previousSize; } /** * Adds or updates a network which can later be retrieved with {@link #getWifiConfiguration(int)} * method. A null {@param config}, or one with a networkId less than 0, or a networkId that had * its updatePermission removed using the {@link #setUpdateNetworkPermission(int, boolean)} will * return -1, which indicates a failure to update. */ @Implementation protected int updateNetwork(WifiConfiguration config) { if (config == null || config.networkId < 0 || readOnlyNetworkIds.get(config.networkId)) { return -1; } networkIdToConfiguredNetworks.put(config.networkId, makeCopy(config, config.networkId)); return config.networkId; } @Implementation protected boolean saveConfiguration() { wasSaved = true; return true; } @Implementation protected boolean enableNetwork(int netId, boolean attemptConnect) { lastEnabledNetwork = new Pair<>(netId, attemptConnect); enabledNetworks.add(netId); return true; } @Implementation protected boolean disableNetwork(int netId) { return enabledNetworks.remove(netId); } @Implementation protected WifiManager.WifiLock createWifiLock(int lockType, String tag) { WifiManager.WifiLock wifiLock = ReflectionHelpers.callConstructor(WifiManager.WifiLock.class); shadowOf(wifiLock).setWifiManager(wifiManager); return wifiLock; } @Implementation protected WifiManager.WifiLock createWifiLock(String tag) { return createWifiLock(WifiManager.WIFI_MODE_FULL, tag); } @Implementation protected MulticastLock createMulticastLock(String tag) { MulticastLock multicastLock = ReflectionHelpers.callConstructor(MulticastLock.class); shadowOf(multicastLock).setWifiManager(wifiManager); return multicastLock; } @Implementation protected static int calculateSignalLevel(int rssi, int numLevels) { return (int) (sSignalLevelInPercent * (numLevels - 1)); } /** * Does nothing and returns the configured success status. * *

That is different from the Android implementation which always returns {@code true} up to * and including Android 8, and either {@code true} or {@code false} on Android 9+. * * @return the value configured by {@link #setStartScanSucceeds}, or {@code true} if that method * was never called. */ @Implementation protected boolean startScan() { if (getScanResults() != null && !getScanResults().isEmpty()) { new Handler(Looper.getMainLooper()) .post( () -> { Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); RuntimeEnvironment.getApplication().sendBroadcast(intent); }); } return startScanSucceeds; } @Implementation protected DhcpInfo getDhcpInfo() { return dhcpInfo; } @Implementation(minSdk = JELLY_BEAN_MR2) protected boolean isScanAlwaysAvailable() { return Settings.Global.getInt( getContext().getContentResolver(), Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1) == 1; } @HiddenApi @Implementation(minSdk = KITKAT) protected void connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener) { WifiInfo wifiInfo = getConnectionInfo(); String ssid = isQuoted(wifiConfiguration.SSID) ? stripQuotes(wifiConfiguration.SSID) : wifiConfiguration.SSID; ShadowWifiInfo shadowWifiInfo = Shadow.extract(wifiInfo); shadowWifiInfo.setSSID(ssid); shadowWifiInfo.setBSSID(wifiConfiguration.BSSID); shadowWifiInfo.setNetworkId(wifiConfiguration.networkId); setConnectionInfo(wifiInfo); // Now that we're "connected" to wifi, update Dhcp and point it to localhost. DhcpInfo dhcpInfo = new DhcpInfo(); dhcpInfo.gateway = LOCAL_HOST; dhcpInfo.ipAddress = LOCAL_HOST; setDhcpInfo(dhcpInfo); // Now add the network to ConnectivityManager. NetworkInfo networkInfo = ShadowNetworkInfo.newInstance( NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0 /* subType */, true /* isAvailable */, true /* isConnected */); ShadowConnectivityManager connectivityManager = Shadow.extract( RuntimeEnvironment.getApplication().getSystemService(Context.CONNECTIVITY_SERVICE)); connectivityManager.setActiveNetworkInfo(networkInfo); if (listener != null) { listener.onSuccess(); } } @HiddenApi @Implementation(minSdk = KITKAT) protected void connect(int networkId, WifiManager.ActionListener listener) { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.networkId = networkId; wifiConfiguration.SSID = ""; wifiConfiguration.BSSID = ""; connect(wifiConfiguration, listener); } private static boolean isQuoted(String str) { if (str == null || str.length() < 2) { return false; } return str.charAt(0) == '"' && str.charAt(str.length() - 1) == '"'; } private static String stripQuotes(String str) { return str.substring(1, str.length() - 1); } @Implementation protected boolean reconnect() { WifiConfiguration wifiConfiguration = getMostRecentNetwork(); if (wifiConfiguration == null) { return false; } connect(wifiConfiguration, null); return true; } private WifiConfiguration getMostRecentNetwork() { if (getLastEnabledNetwork() == null) { return null; } return getWifiConfiguration(getLastEnabledNetwork().first); } public static void setSignalLevelInPercent(float level) { if (level < 0 || level > 1) { throw new IllegalArgumentException("level needs to be between 0 and 1"); } sSignalLevelInPercent = level; } public void setAccessWifiStatePermission(boolean accessWifiStatePermission) { this.accessWifiStatePermission = accessWifiStatePermission; } public void setChangeWifiStatePermission(boolean changeWifiStatePermission) { this.changeWifiStatePermission = changeWifiStatePermission; } /** * Prevents a networkId from being updated using the {@link updateNetwork(WifiConfiguration)} * method. This is to simulate the case where a separate application creates a network, and the * Android security model prevents your application from updating it. */ public void setUpdateNetworkPermission(int networkId, boolean hasPermission) { readOnlyNetworkIds.set(networkId, !hasPermission); } public void setScanResults(List scanResults) { this.scanResults = scanResults; } public void setDhcpInfo(DhcpInfo dhcpInfo) { this.dhcpInfo = dhcpInfo; } public Pair getLastEnabledNetwork() { return lastEnabledNetwork; } /** Whether the network is enabled or not. */ public boolean isNetworkEnabled(int netId) { return enabledNetworks.contains(netId); } /** Returns the number of WifiLocks and MulticastLocks that are currently acquired. */ public int getActiveLockCount() { return activeLockCount.get(); } public boolean wasConfigurationSaved() { return wasSaved; } public void setIsScanAlwaysAvailable(boolean isScanAlwaysAvailable) { Settings.Global.putInt( getContext().getContentResolver(), Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, isScanAlwaysAvailable ? 1 : 0); } private void checkAccessWifiStatePermission() { if (!accessWifiStatePermission) { throw new SecurityException("Caller does not hold ACCESS_WIFI_STATE permission"); } } private void checkChangeWifiStatePermission() { if (!changeWifiStatePermission) { throw new SecurityException("Caller does not hold CHANGE_WIFI_STATE permission"); } } private void checkDeviceOwner() { if (!getContext() .getSystemService(DevicePolicyManager.class) .isDeviceOwnerApp(getContext().getPackageName())) { throw new SecurityException("Caller is not device owner"); } } private WifiConfiguration makeCopy(WifiConfiguration config, int networkId) { ShadowWifiConfiguration shadowWifiConfiguration = Shadow.extract(config); WifiConfiguration copy = shadowWifiConfiguration.copy(); copy.networkId = networkId; return copy; } public WifiConfiguration getWifiConfiguration(int netId) { return networkIdToConfiguredNetworks.get(netId); } @Implementation(minSdk = Q) @HiddenApi protected void addOnWifiUsabilityStatsListener(Object executorObject, Object listenerObject) { Executor executor = (Executor) executorObject; WifiManager.OnWifiUsabilityStatsListener listener = (WifiManager.OnWifiUsabilityStatsListener) listenerObject; wifiUsabilityStatsListeners.put(listener, executor); } @Implementation(minSdk = Q) @HiddenApi protected void removeOnWifiUsabilityStatsListener(Object listenerObject) { WifiManager.OnWifiUsabilityStatsListener listener = (WifiManager.OnWifiUsabilityStatsListener) listenerObject; wifiUsabilityStatsListeners.remove(listener); } @Implementation(minSdk = Q) @HiddenApi protected void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { synchronized (usabilityScores) { usabilityScores.add(new WifiUsabilityScore(seqNum, score, predictionHorizonSec)); } } /** * Implements setWifiConnectedNetworkScorer() with the generic Object input as * WifiConnectedNetworkScorer is a hidden/System API. */ @Implementation(minSdk = R) @HiddenApi protected boolean setWifiConnectedNetworkScorer(Object executorObject, Object scorerObject) { if (networkScorer == null) { networkScorer = scorerObject; return true; } else { return false; } } @Implementation(minSdk = R) @HiddenApi protected void clearWifiConnectedNetworkScorer() { networkScorer = null; } /** Returns if wifi connected betwork scorer enabled */ public boolean isWifiConnectedNetworkScorerEnabled() { return networkScorer != null; } @Implementation protected boolean setWifiApConfiguration(WifiConfiguration apConfig) { this.apConfig = apConfig; return true; } @Implementation protected WifiConfiguration getWifiApConfiguration() { return apConfig; } @Implementation(minSdk = R) protected boolean setSoftApConfiguration(SoftApConfiguration softApConfig) { this.softApConfig = softApConfig; return true; } @Implementation(minSdk = R) protected SoftApConfiguration getSoftApConfiguration() { return softApConfig; } /** * Returns wifi usability scores previous passed to {@link WifiManager#updateWifiUsabilityScore} */ public List getUsabilityScores() { synchronized (usabilityScores) { return ImmutableList.copyOf(usabilityScores); } } /** * Clears wifi usability scores previous passed to {@link WifiManager#updateWifiUsabilityScore} */ public void clearUsabilityScores() { synchronized (usabilityScores) { usabilityScores.clear(); } } /** * Post Wifi stats to any listeners registered with {@link * WifiManager#addOnWifiUsabilityStatsListener} */ public void postUsabilityStats( int seqNum, boolean isSameBssidAndFreq, WifiUsabilityStatsEntryBuilder statsBuilder) { WifiUsabilityStatsEntry stats = statsBuilder.build(); Set> toNotify = new ArraySet<>(); toNotify.addAll(wifiUsabilityStatsListeners.entrySet()); for (Map.Entry entry : toNotify) { entry .getValue() .execute( new Runnable() { // Using a lambda here means loading the ShadowWifiManager class tries // to load the WifiManager.OnWifiUsabilityStatsListener which fails if // not building against a system API. @Override public void run() { entry.getKey().onWifiUsabilityStats(seqNum, isSameBssidAndFreq, stats); } }); } } private Context getContext() { return ReflectionHelpers.getField(wifiManager, "mContext"); } @Implements(WifiManager.WifiLock.class) public static class ShadowWifiLock { private int refCount; private boolean refCounted = true; private boolean locked; private WifiManager wifiManager; public static final int MAX_ACTIVE_LOCKS = 50; private void setWifiManager(WifiManager wifiManager) { this.wifiManager = wifiManager; } @Implementation protected synchronized void acquire() { if (wifiManager != null) { shadowOf(wifiManager).activeLockCount.getAndIncrement(); } if (refCounted) { if (++refCount >= MAX_ACTIVE_LOCKS) { throw new UnsupportedOperationException("Exceeded maximum number of wifi locks"); } } else { locked = true; } } @Implementation protected synchronized void release() { if (wifiManager != null) { shadowOf(wifiManager).activeLockCount.getAndDecrement(); } if (refCounted) { if (--refCount < 0) throw new RuntimeException("WifiLock under-locked"); } else { locked = false; } } @Implementation protected synchronized boolean isHeld() { return refCounted ? refCount > 0 : locked; } @Implementation protected void setReferenceCounted(boolean refCounted) { this.refCounted = refCounted; } } @Implements(MulticastLock.class) public static class ShadowMulticastLock { private int refCount; private boolean refCounted = true; private boolean locked; static final int MAX_ACTIVE_LOCKS = 50; private WifiManager wifiManager; private void setWifiManager(WifiManager wifiManager) { this.wifiManager = wifiManager; } @Implementation protected void acquire() { if (wifiManager != null) { shadowOf(wifiManager).activeLockCount.getAndIncrement(); } if (refCounted) { if (++refCount >= MAX_ACTIVE_LOCKS) { throw new UnsupportedOperationException("Exceeded maximum number of wifi locks"); } } else { locked = true; } } @Implementation protected synchronized void release() { if (wifiManager != null) { shadowOf(wifiManager).activeLockCount.getAndDecrement(); } if (refCounted) { if (--refCount < 0) throw new RuntimeException("WifiLock under-locked"); } else { locked = false; } } @Implementation protected void setReferenceCounted(boolean refCounted) { this.refCounted = refCounted; } @Implementation protected synchronized boolean isHeld() { return refCounted ? refCount > 0 : locked; } } private static ShadowWifiLock shadowOf(WifiManager.WifiLock o) { return Shadow.extract(o); } private static ShadowMulticastLock shadowOf(WifiManager.MulticastLock o) { return Shadow.extract(o); } private static ShadowWifiManager shadowOf(WifiManager o) { return Shadow.extract(o); } /** Class to record scores passed to WifiManager#updateWifiUsabilityScore */ public static class WifiUsabilityScore { public final int seqNum; public final int score; public final int predictionHorizonSec; private WifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) { this.seqNum = seqNum; this.score = score; this.predictionHorizonSec = predictionHorizonSec; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy