/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.LOCATION_HARDWARE; import static android.Manifest.permission.NEARBY_WIFI_DEVICES; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.WorkSource; import android.text.TextUtils; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.internal.util.Protocol; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * This class provides a way to scan the Wifi universe around the device * @hide */ @SystemApi @SystemService(Context.WIFI_SCANNING_SERVICE) public class WifiScanner { /** @hide */ public static final int WIFI_BAND_INDEX_24_GHZ = 0; /** @hide */ public static final int WIFI_BAND_INDEX_5_GHZ = 1; /** @hide */ public static final int WIFI_BAND_INDEX_5_GHZ_DFS_ONLY = 2; /** @hide */ public static final int WIFI_BAND_INDEX_6_GHZ = 3; /** @hide */ public static final int WIFI_BAND_INDEX_60_GHZ = 4; /** @hide */ public static final int WIFI_BAND_COUNT = 5; /** * Reserved bit for Multi-internet connection only, not for scanning. * @hide */ public static final int WIFI_BAND_INDEX_5_GHZ_LOW = 29; /** * Reserved bit for Multi-internet connection only, not for scanning. * @hide */ public static final int WIFI_BAND_INDEX_5_GHZ_HIGH = 30; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"WIFI_BAND_INDEX_"}, value = { WIFI_BAND_INDEX_24_GHZ, WIFI_BAND_INDEX_5_GHZ, WIFI_BAND_INDEX_5_GHZ_DFS_ONLY, WIFI_BAND_INDEX_6_GHZ, WIFI_BAND_INDEX_60_GHZ}) public @interface WifiBandIndex {} /** no band specified; use channel list instead */ public static final int WIFI_BAND_UNSPECIFIED = 0; /** 2.4 GHz band */ public static final int WIFI_BAND_24_GHZ = 1 << WIFI_BAND_INDEX_24_GHZ; /** 5 GHz band excluding DFS channels */ public static final int WIFI_BAND_5_GHZ = 1 << WIFI_BAND_INDEX_5_GHZ; /** DFS channels from 5 GHz band only */ public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 1 << WIFI_BAND_INDEX_5_GHZ_DFS_ONLY; /** 6 GHz band */ public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ; /** 60 GHz band */ public static final int WIFI_BAND_60_GHZ = 1 << WIFI_BAND_INDEX_60_GHZ; /** * Reserved for Multi-internet connection only, not for scanning. * @hide */ public static final int WIFI_BAND_5_GHZ_LOW = 1 << WIFI_BAND_INDEX_5_GHZ_LOW; /** * Reserved for Multi-internet connection only, not for scanning. * @hide */ public static final int WIFI_BAND_5_GHZ_HIGH = 1 << WIFI_BAND_INDEX_5_GHZ_HIGH; /** * Combination of bands * Note that those are only the common band combinations, * other combinations can be created by combining any of the basic bands above */ /** Both 2.4 GHz band and 5 GHz band; no DFS channels */ public static final int WIFI_BAND_BOTH = WIFI_BAND_24_GHZ | WIFI_BAND_5_GHZ; /** * 2.4Ghz band + DFS channels from 5 GHz band only * @hide */ public static final int WIFI_BAND_24_GHZ_WITH_5GHZ_DFS = WIFI_BAND_24_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY; /** 5 GHz band including DFS channels */ public static final int WIFI_BAND_5_GHZ_WITH_DFS = WIFI_BAND_5_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY; /** Both 2.4 GHz band and 5 GHz band; with DFS channels */ public static final int WIFI_BAND_BOTH_WITH_DFS = WIFI_BAND_24_GHZ | WIFI_BAND_5_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY; /** 2.4 GHz band and 5 GHz band (no DFS channels) and 6 GHz */ public static final int WIFI_BAND_24_5_6_GHZ = WIFI_BAND_BOTH | WIFI_BAND_6_GHZ; /** 2.4 GHz band and 5 GHz band; with DFS channels and 6 GHz */ public static final int WIFI_BAND_24_5_WITH_DFS_6_GHZ = WIFI_BAND_BOTH_WITH_DFS | WIFI_BAND_6_GHZ; /** @hide */ public static final int WIFI_BAND_24_5_6_60_GHZ = WIFI_BAND_24_5_6_GHZ | WIFI_BAND_60_GHZ; /** @hide */ public static final int WIFI_BAND_24_5_WITH_DFS_6_60_GHZ = WIFI_BAND_24_5_6_60_GHZ | WIFI_BAND_5_GHZ_DFS_ONLY; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"WIFI_BAND_"}, value = { WIFI_BAND_UNSPECIFIED, WIFI_BAND_24_GHZ, WIFI_BAND_5_GHZ, WIFI_BAND_BOTH, WIFI_BAND_5_GHZ_DFS_ONLY, WIFI_BAND_24_GHZ_WITH_5GHZ_DFS, WIFI_BAND_5_GHZ_WITH_DFS, WIFI_BAND_BOTH_WITH_DFS, WIFI_BAND_6_GHZ, WIFI_BAND_24_5_6_GHZ, WIFI_BAND_24_5_WITH_DFS_6_GHZ, WIFI_BAND_60_GHZ, WIFI_BAND_24_5_6_60_GHZ, WIFI_BAND_24_5_WITH_DFS_6_60_GHZ}) public @interface WifiBand {} /** * All bands * @hide */ public static final int WIFI_BAND_ALL = (1 << WIFI_BAND_COUNT) - 1; /** Minimum supported scanning period */ public static final int MIN_SCAN_PERIOD_MS = 1000; /** Maximum supported scanning period */ public static final int MAX_SCAN_PERIOD_MS = 1024000; /** No Error */ public static final int REASON_SUCCEEDED = 0; /** Unknown error */ public static final int REASON_UNSPECIFIED = -1; /** Invalid listener */ public static final int REASON_INVALID_LISTENER = -2; /** Invalid request */ public static final int REASON_INVALID_REQUEST = -3; /** Invalid request */ public static final int REASON_NOT_AUTHORIZED = -4; /** An outstanding request with the same listener hasn't finished yet. */ public static final int REASON_DUPLICATE_REQEUST = -5; /** Busy - Due to Connection in progress, processing another scan request etc. */ public static final int REASON_BUSY = -6; /** Abort - Due to another high priority operation like roaming, offload scan etc. */ public static final int REASON_ABORT = -7; /** No such device - Wrong interface or interface doesn't exist. */ public static final int REASON_NO_DEVICE = -8; /** Invalid argument - Wrong/unsupported argument passed in scan params. */ public static final int REASON_INVALID_ARGS = -9; /** Timeout - Device didn't respond back with scan results */ public static final int REASON_TIMEOUT = -10; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "REASON_" }, value = { REASON_SUCCEEDED, REASON_UNSPECIFIED, REASON_INVALID_LISTENER, REASON_INVALID_REQUEST, REASON_NOT_AUTHORIZED, REASON_DUPLICATE_REQEUST, REASON_BUSY, REASON_ABORT, REASON_NO_DEVICE, REASON_INVALID_ARGS, REASON_TIMEOUT, }) public @interface ScanStatusCode {} /** @hide */ public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels"; /** * This constant is used for {@link ScanSettings#setRnrSetting(int)}. *

* Scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced Neighbor Report (RNR) if the 6Ghz * band is explicitly requested to be scanned and the current country code supports scanning * of at least one 6Ghz channel. The 6Ghz band is explicitly requested if the * ScanSetting.band parameter is set to one of: *

  • {@link #WIFI_BAND_6_GHZ}
  • *
  • {@link #WIFI_BAND_24_5_6_GHZ}
  • *
  • {@link #WIFI_BAND_24_5_WITH_DFS_6_GHZ}
  • *
  • {@link #WIFI_BAND_24_5_6_60_GHZ}
  • *
  • {@link #WIFI_BAND_24_5_WITH_DFS_6_60_GHZ}
  • *
  • {@link #WIFI_BAND_ALL}
  • **/ public static final int WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED = 0; /** * This constant is used for {@link ScanSettings#setRnrSetting(int)}. *

    * Request to scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced Neighbor Report (RNR) * when the current country code supports scanning of at least one 6Ghz channel. **/ public static final int WIFI_RNR_ENABLED = 1; /** * This constant is used for {@link ScanSettings#setRnrSetting(int)}. *

    * Do not request to scan 6Ghz APs co-located with 2.4/5Ghz APs using * Reduced Neighbor Report (RNR) **/ public static final int WIFI_RNR_NOT_NEEDED = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"RNR_"}, value = { WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED, WIFI_RNR_ENABLED, WIFI_RNR_NOT_NEEDED}) public @interface RnrSetting {} /** * Maximum length in bytes of all vendor specific information elements (IEs) allowed to set. * @hide */ public static final int WIFI_SCANNER_SETTINGS_VENDOR_ELEMENTS_MAX_LEN = 512; /** * Information Element head: id (1 byte) + length (1 byte) * @hide */ public static final int WIFI_IE_HEAD_LEN = 2; /** * Generic action callback invocation interface * @hide */ @SystemApi public static interface ActionListener { public void onSuccess(); public void onFailure(int reason, String description); } /** * Test if scan is a full scan. i.e. scanning all available bands. * For backward compatibility, since some apps don't include 6GHz or 60Ghz in their requests * yet, lacking 6GHz or 60Ghz band does not cause the result to be false. * * @param bandsScanned bands that are fully scanned * @param excludeDfs when true, DFS band is excluded from the check * @return true if all bands are scanned, false otherwise * * @hide */ public static boolean isFullBandScan(@WifiBand int bandsScanned, boolean excludeDfs) { return (bandsScanned | WIFI_BAND_6_GHZ | WIFI_BAND_60_GHZ | (excludeDfs ? WIFI_BAND_5_GHZ_DFS_ONLY : 0)) == WIFI_BAND_ALL; } /** * Returns a list of all the possible channels for the given band(s). * * @param band one of the WifiScanner#WIFI_BAND_* constants, e.g. {@link #WIFI_BAND_24_GHZ} * @return a list of all the frequencies, in MHz, for the given band(s) e.g. channel 1 is * 2412, or null if an error occurred. */ @NonNull @RequiresPermission(NEARBY_WIFI_DEVICES) public List getAvailableChannels(int band) { try { Bundle extras = new Bundle(); if (SdkLevel.isAtLeastS()) { extras.putParcelable(WifiManager.EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE, mContext.getAttributionSource()); } Bundle bundle = mService.getAvailableChannels(band, mContext.getOpPackageName(), mContext.getAttributionTag(), extras); List channels = bundle.getIntegerArrayList(GET_AVAILABLE_CHANNELS_EXTRA); return channels == null ? new ArrayList<>() : channels; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private class ServiceListener extends IWifiScannerListener.Stub { private ActionListener mActionListener; private Executor mExecutor; ServiceListener(ActionListener listener, Executor executor) { mActionListener = listener; mExecutor = executor; } @Override public void onSuccess() { if (mActionListener == null) return; Binder.clearCallingIdentity(); mExecutor.execute(mActionListener::onSuccess); } @Override public void onFailure(int reason, String description) { if (mActionListener == null) return; Binder.clearCallingIdentity(); mExecutor.execute(() -> mActionListener.onFailure(reason, description)); removeListener(mActionListener); } /** * reports results retrieved from background scan and single shot scans */ @Override public void onResults(WifiScanner.ScanData[] results) { if (mActionListener == null) return; if (!(mActionListener instanceof ScanListener)) return; ScanListener scanListener = (ScanListener) mActionListener; Binder.clearCallingIdentity(); mExecutor.execute( () -> scanListener.onResults(results)); } /** * reports full scan result for each access point found in scan */ @Override public void onFullResult(ScanResult fullScanResult) { if (mActionListener == null) return; if (!(mActionListener instanceof ScanListener)) return; ScanListener scanListener = (ScanListener) mActionListener; Binder.clearCallingIdentity(); mExecutor.execute( () -> scanListener.onFullResult(fullScanResult)); } @Override public void onSingleScanCompleted() { if (DBG) Log.d(TAG, "single scan completed"); removeListener(mActionListener); } /** * Invoked when one of the PNO networks are found in scan results. */ @Override public void onPnoNetworkFound(ScanResult[] results) { if (mActionListener == null) return; if (!(mActionListener instanceof PnoScanListener)) return; PnoScanListener pnoScanListener = (PnoScanListener) mActionListener; Binder.clearCallingIdentity(); mExecutor.execute( () -> pnoScanListener.onPnoNetworkFound(results)); } } /** * provides channel specification for scanning */ public static class ChannelSpec { /** * channel frequency in MHz; for example channel 1 is specified as 2412 */ public int frequency; /** * if true, scan this channel in passive fashion. * This flag is ignored on DFS channel specification. * @hide */ public boolean passive; /* ignored on DFS channels */ /** * how long to dwell on this channel * @hide */ public int dwellTimeMS; /* not supported for now */ /** * default constructor for channel spec */ public ChannelSpec(int frequency) { this.frequency = frequency; passive = false; dwellTimeMS = 0; } } /** * reports {@link ScanListener#onResults} when underlying buffers are full * this is simply the lack of the {@link #REPORT_EVENT_AFTER_EACH_SCAN} flag * @deprecated It is not supported anymore. */ @Deprecated public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; /** * reports {@link ScanListener#onResults} after each scan */ public static final int REPORT_EVENT_AFTER_EACH_SCAN = (1 << 0); /** * reports {@link ScanListener#onFullResult} whenever each beacon is discovered */ public static final int REPORT_EVENT_FULL_SCAN_RESULT = (1 << 1); /** * Do not place scans in the chip's scan history buffer */ public static final int REPORT_EVENT_NO_BATCH = (1 << 2); /** * Optimize the scan for lower latency. * @see ScanSettings#type */ public static final int SCAN_TYPE_LOW_LATENCY = 0; /** * Optimize the scan for lower power usage. * @see ScanSettings#type */ public static final int SCAN_TYPE_LOW_POWER = 1; /** * Optimize the scan for higher accuracy. * @see ScanSettings#type */ public static final int SCAN_TYPE_HIGH_ACCURACY = 2; /** * Max valid value of SCAN_TYPE_ * @hide */ public static final int SCAN_TYPE_MAX = 2; /** {@hide} */ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; /** {@hide} */ public static final String SCAN_PARAMS_WORK_SOURCE_KEY = "WorkSource"; /** {@hide} */ public static final String REQUEST_PACKAGE_NAME_KEY = "PackageName"; /** {@hide} */ public static final String REQUEST_FEATURE_ID_KEY = "FeatureId"; /** * scan configuration parameters to be sent to {@link #startBackgroundScan} */ public static class ScanSettings implements Parcelable { /** Hidden network to be scanned for. */ public static class HiddenNetwork { /** SSID of the network */ @NonNull public final String ssid; /** Default constructor for HiddenNetwork. */ public HiddenNetwork(@NonNull String ssid) { this.ssid = ssid; } } /** one of the WIFI_BAND values */ public int band; /** * one of the {@code WIFI_RNR_*} values. */ private int mRnrSetting = WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED; /** * See {@link #set6GhzPscOnlyEnabled} */ private boolean mEnable6GhzPsc = false; /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ public ChannelSpec[] channels; /** * List of hidden networks to scan for. Explicit probe requests are sent out for such * networks during scan. Only valid for single scan requests. */ @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final List hiddenNetworks = new ArrayList<>(); /** * vendor IEs -- list of ScanResult.InformationElement, configured by App * see {@link #setVendorIes(List)} */ @NonNull private List mVendorIes = new ArrayList<>(); /** * period of background scan; in millisecond, 0 => single shot scan * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int periodInMs; /** * must have a valid REPORT_EVENT value * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int reportEvents; /** * defines number of bssids to cache from each scan * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int numBssidsPerScan; /** * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL * to wake up at fixed interval * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int maxScansToCache; /** * if maxPeriodInMs is non zero or different than period, then this bucket is * a truncated binary exponential backoff bucket and the scan period will grow * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount)) * to maxPeriodInMs * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int maxPeriodInMs; /** * for truncated binary exponential back off bucket, number of scans to perform * for a given period * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public int stepCount; /** * Flag to indicate if the scan settings are targeted for PNO scan. * {@hide} */ public boolean isPnoScan; /** * Indicate the type of scan to be performed by the wifi chip. * * On devices with multiple hardware radio chains (and hence different modes of scan), * this type serves as an indication to the hardware on what mode of scan to perform. * Only apps holding {@link android.Manifest.permission.NETWORK_STACK} permission can set * this value. * * Note: This serves as an intent and not as a stipulation, the wifi chip * might honor or ignore the indication based on the current radio conditions. Always * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration * used to receive the corresponding scan result. * * One of {@link #SCAN_TYPE_LOW_LATENCY}, {@link #SCAN_TYPE_LOW_POWER}, * {@link #SCAN_TYPE_HIGH_ACCURACY}. * Default value: {@link #SCAN_TYPE_LOW_LATENCY}. */ @WifiAnnotations.ScanType @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type = SCAN_TYPE_LOW_LATENCY; /** * This scan request may ignore location settings while receiving scans. This should only * be used in emergency situations. * {@hide} */ @SystemApi public boolean ignoreLocationSettings; /** * This scan request will be hidden from app-ops noting for location information. This * should only be used by FLP/NLP module on the device which is using the scan results to * compute results for behalf on their clients. FLP/NLP module using this flag should ensure * that they note in app-ops the eventual delivery of location information computed using * these results to their client . * {@hide} */ @SystemApi public boolean hideFromAppOps; /** * Configure whether it is needed to scan 6Ghz non Preferred Scanning Channels when scanning * {@link #WIFI_BAND_6_GHZ}. If set to true and a band that contains * {@link #WIFI_BAND_6_GHZ} is configured for scanning, then only scan 6Ghz PSC channels in * addition to any other bands configured for scanning. Note, 6Ghz non-PSC channels that * are co-located with 2.4/5Ghz APs could still be scanned via the * {@link #setRnrSetting(int)} API. * *

    * For example, given a ScanSettings with band set to {@link #WIFI_BAND_24_5_WITH_DFS_6_GHZ} * If this API is set to "true" then the ScanSettings is configured to scan all of 2.4Ghz * + all of 5Ghz(DFS and non-DFS) + 6Ghz PSC channels. If this API is set to "false", then * the ScanSetting is configured to scan all of 2.4Ghz + all of 5Ghz(DFS and non_DFS) * + all of 6Ghz channels. * @param enable true to only scan 6Ghz PSC channels, false to scan all 6Ghz channels. */ @RequiresApi(Build.VERSION_CODES.S) public void set6GhzPscOnlyEnabled(boolean enable) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mEnable6GhzPsc = enable; } /** * See {@link #set6GhzPscOnlyEnabled} */ @RequiresApi(Build.VERSION_CODES.S) public boolean is6GhzPscOnlyEnabled() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return mEnable6GhzPsc; } /** * Configure when to scan 6Ghz APs co-located with 2.4/5Ghz APs using Reduced * Neighbor Report (RNR). * @param rnrSetting one of the {@code WIFI_RNR_*} values */ @RequiresApi(Build.VERSION_CODES.S) public void setRnrSetting(@RnrSetting int rnrSetting) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } if (rnrSetting < WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED || rnrSetting > WIFI_RNR_NOT_NEEDED) { throw new IllegalArgumentException("Invalid rnrSetting"); } mRnrSetting = rnrSetting; } /** * See {@link #setRnrSetting} */ @RequiresApi(Build.VERSION_CODES.S) public @RnrSetting int getRnrSetting() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return mRnrSetting; } /** * Set vendor IEs in scan probe req. * * @param vendorIes List of ScanResult.InformationElement configured by App. */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public void setVendorIes(@NonNull List vendorIes) { if (!SdkLevel.isAtLeastU()) { throw new UnsupportedOperationException(); } mVendorIes.clear(); int totalBytes = 0; for (ScanResult.InformationElement e : vendorIes) { if (e.id != ScanResult.InformationElement.EID_VSA) { throw new IllegalArgumentException("received InformationElement which is not " + "a Vendor Specific IE (VSIE). VSIEs have an ID = ScanResult" + ".InformationElement.EID_VSA."); } if (e.bytes == null || e.bytes.length > 0xff) { throw new IllegalArgumentException("received InformationElement whose payload " + "is null or size is greater than 255."); } // The total bytes of an IE is EID (1 byte) + length (1 byte) + payload length. totalBytes += WIFI_IE_HEAD_LEN + e.bytes.length; if (totalBytes > WIFI_SCANNER_SETTINGS_VENDOR_ELEMENTS_MAX_LEN) { throw new IllegalArgumentException( "received InformationElement whose total size is greater than " + WIFI_SCANNER_SETTINGS_VENDOR_ELEMENTS_MAX_LEN + "."); } } mVendorIes.addAll(vendorIes); } /** * See {@link #setVendorIes(List)} */ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public @NonNull List getVendorIes() { if (!SdkLevel.isAtLeastU()) { throw new UnsupportedOperationException(); } return mVendorIes; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(band); dest.writeInt(periodInMs); dest.writeInt(reportEvents); dest.writeInt(numBssidsPerScan); dest.writeInt(maxScansToCache); dest.writeInt(maxPeriodInMs); dest.writeInt(stepCount); dest.writeInt(isPnoScan ? 1 : 0); dest.writeInt(type); dest.writeInt(ignoreLocationSettings ? 1 : 0); dest.writeInt(hideFromAppOps ? 1 : 0); dest.writeInt(mRnrSetting); dest.writeBoolean(mEnable6GhzPsc); if (channels != null) { dest.writeInt(channels.length); for (int i = 0; i < channels.length; i++) { dest.writeInt(channels[i].frequency); dest.writeInt(channels[i].dwellTimeMS); dest.writeInt(channels[i].passive ? 1 : 0); } } else { dest.writeInt(0); } dest.writeInt(hiddenNetworks.size()); for (HiddenNetwork hiddenNetwork : hiddenNetworks) { dest.writeString(hiddenNetwork.ssid); } dest.writeTypedList(mVendorIes); } /** Implement the Parcelable interface */ public static final @NonNull Creator CREATOR = new Creator() { public ScanSettings createFromParcel(Parcel in) { ScanSettings settings = new ScanSettings(); settings.band = in.readInt(); settings.periodInMs = in.readInt(); settings.reportEvents = in.readInt(); settings.numBssidsPerScan = in.readInt(); settings.maxScansToCache = in.readInt(); settings.maxPeriodInMs = in.readInt(); settings.stepCount = in.readInt(); settings.isPnoScan = in.readInt() == 1; settings.type = in.readInt(); settings.ignoreLocationSettings = in.readInt() == 1; settings.hideFromAppOps = in.readInt() == 1; settings.mRnrSetting = in.readInt(); settings.mEnable6GhzPsc = in.readBoolean(); int num_channels = in.readInt(); settings.channels = new ChannelSpec[num_channels]; for (int i = 0; i < num_channels; i++) { int frequency = in.readInt(); ChannelSpec spec = new ChannelSpec(frequency); spec.dwellTimeMS = in.readInt(); spec.passive = in.readInt() == 1; settings.channels[i] = spec; } int numNetworks = in.readInt(); settings.hiddenNetworks.clear(); for (int i = 0; i < numNetworks; i++) { String ssid = in.readString(); settings.hiddenNetworks.add(new HiddenNetwork(ssid)); } in.readTypedList(settings.mVendorIes, ScanResult.InformationElement.CREATOR); return settings; } public ScanSettings[] newArray(int size) { return new ScanSettings[size]; } }; } /** * All the information garnered from a single scan */ public static class ScanData implements Parcelable { /** scan identifier */ private int mId; /** additional information about scan * 0 => no special issues encountered in the scan * non-zero => scan was truncated, so results may not be complete */ private int mFlags; /** * Indicates the buckets that were scanned to generate these results. * This is not relevant to WifiScanner API users and is used internally. * {@hide} */ private int mBucketsScanned; /** * Bands scanned. One of the WIFI_BAND values. * Will be {@link #WIFI_BAND_UNSPECIFIED} if the list of channels do not fully cover * any of the bands. * {@hide} */ private int mScannedBands; /** all scan results discovered in this scan, sorted by timestamp in ascending order */ private final List mResults; /** {@hide} */ public ScanData() { mResults = new ArrayList<>(); } public ScanData(int id, int flags, ScanResult[] results) { mId = id; mFlags = flags; mResults = new ArrayList<>(Arrays.asList(results)); } /** {@hide} */ public ScanData(int id, int flags, int bucketsScanned, int bandsScanned, ScanResult[] results) { this(id, flags, bucketsScanned, bandsScanned, new ArrayList<>(Arrays.asList(results))); } /** {@hide} */ public ScanData(int id, int flags, int bucketsScanned, int bandsScanned, List results) { mId = id; mFlags = flags; mBucketsScanned = bucketsScanned; mScannedBands = bandsScanned; mResults = results; } public ScanData(ScanData s) { mId = s.mId; mFlags = s.mFlags; mBucketsScanned = s.mBucketsScanned; mScannedBands = s.mScannedBands; mResults = new ArrayList<>(); for (ScanResult scanResult : s.mResults) { mResults.add(new ScanResult(scanResult)); } } public int getId() { return mId; } public int getFlags() { return mFlags; } /** {@hide} */ public int getBucketsScanned() { return mBucketsScanned; } /** * Retrieve the bands that were fully scanned for this ScanData instance. "fully" here * refers to all the channels available in the band based on the current regulatory * domain. * * @return Bitmask of {@link #WIFI_BAND_24_GHZ}, {@link #WIFI_BAND_5_GHZ}, * {@link #WIFI_BAND_5_GHZ_DFS_ONLY}, {@link #WIFI_BAND_6_GHZ} & {@link #WIFI_BAND_60_GHZ} * values. Each bit is set only if all the channels in the corresponding band is scanned. * Will be {@link #WIFI_BAND_UNSPECIFIED} if the list of channels do not fully cover * any of the bands. *

    * For ex: *

  • Scenario 1: Fully scanned 2.4Ghz band, partially scanned 5Ghz band * - Returns {@link #WIFI_BAND_24_GHZ} *
  • *
  • Scenario 2: Partially scanned 2.4Ghz band and 5Ghz band * - Returns {@link #WIFI_BAND_UNSPECIFIED} *
  • *

    */ public @WifiBand int getScannedBands() { return getScannedBandsInternal(); } /** * Same as {@link #getScannedBands()}. For use in the wifi stack without version check. * * {@hide} */ public @WifiBand int getScannedBandsInternal() { return mScannedBands; } public ScanResult[] getResults() { return mResults.toArray(new ScanResult[0]); } /** {@hide} */ public void addResults(@NonNull ScanResult[] newResults) { for (ScanResult result : newResults) { mResults.add(new ScanResult(result)); } } /** {@hide} */ public void addResults(@NonNull ScanData s) { mScannedBands |= s.mScannedBands; mFlags |= s.mFlags; addResults(s.getResults()); } /** {@hide} */ public boolean isFullBandScanResults() { return (mScannedBands & WifiScanner.WIFI_BAND_24_GHZ) != 0 && (mScannedBands & WifiScanner.WIFI_BAND_5_GHZ) != 0; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); dest.writeInt(mFlags); dest.writeInt(mBucketsScanned); dest.writeInt(mScannedBands); dest.writeParcelableList(mResults, 0); } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public ScanData createFromParcel(Parcel in) { int id = in.readInt(); int flags = in.readInt(); int bucketsScanned = in.readInt(); int bandsScanned = in.readInt(); List results = new ArrayList<>(); in.readParcelableList(results, ScanResult.class.getClassLoader()); return new ScanData(id, flags, bucketsScanned, bandsScanned, results); } public ScanData[] newArray(int size) { return new ScanData[size]; } }; } public static class ParcelableScanData implements Parcelable { public ScanData mResults[]; public ParcelableScanData(ScanData[] results) { mResults = results; } public ScanData[] getResults() { return mResults; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { if (mResults != null) { dest.writeInt(mResults.length); for (int i = 0; i < mResults.length; i++) { ScanData result = mResults[i]; result.writeToParcel(dest, flags); } } else { dest.writeInt(0); } } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public ParcelableScanData createFromParcel(Parcel in) { int n = in.readInt(); ScanData results[] = new ScanData[n]; for (int i = 0; i < n; i++) { results[i] = ScanData.CREATOR.createFromParcel(in); } return new ParcelableScanData(results); } public ParcelableScanData[] newArray(int size) { return new ParcelableScanData[size]; } }; } public static class ParcelableScanResults implements Parcelable { public ScanResult mResults[]; public ParcelableScanResults(ScanResult[] results) { mResults = results; } public ScanResult[] getResults() { return mResults; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { if (mResults != null) { dest.writeInt(mResults.length); for (int i = 0; i < mResults.length; i++) { ScanResult result = mResults[i]; result.writeToParcel(dest, flags); } } else { dest.writeInt(0); } } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public ParcelableScanResults createFromParcel(Parcel in) { int n = in.readInt(); ScanResult results[] = new ScanResult[n]; for (int i = 0; i < n; i++) { results[i] = ScanResult.CREATOR.createFromParcel(in); } return new ParcelableScanResults(results); } public ParcelableScanResults[] newArray(int size) { return new ParcelableScanResults[size]; } }; } /** {@hide} */ public static final String PNO_PARAMS_PNO_SETTINGS_KEY = "PnoSettings"; /** {@hide} */ public static final String PNO_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; /** * PNO scan configuration parameters to be sent to {@link #startPnoScan}. * Note: This structure needs to be in sync with |wifi_epno_params| struct in gscan HAL API. * {@hide} */ public static class PnoSettings implements Parcelable { /** * Pno network to be added to the PNO scan filtering. * {@hide} */ public static class PnoNetwork { /* * Pno flags bitmask to be set in {@link #PnoNetwork.flags} */ /** Whether directed scan needs to be performed (for hidden SSIDs) */ public static final byte FLAG_DIRECTED_SCAN = (1 << 0); /** Whether PNO event shall be triggered if the network is found on A band */ public static final byte FLAG_A_BAND = (1 << 1); /** Whether PNO event shall be triggered if the network is found on G band */ public static final byte FLAG_G_BAND = (1 << 2); /** * Whether strict matching is required * If required then the firmware must store the network's SSID and not just a hash */ public static final byte FLAG_STRICT_MATCH = (1 << 3); /** * If this SSID should be considered the same network as the currently connected * one for scoring. */ public static final byte FLAG_SAME_NETWORK = (1 << 4); /* * Code for matching the beacon AUTH IE - additional codes. Bitmask to be set in * {@link #PnoNetwork.authBitField} */ /** Open Network */ public static final byte AUTH_CODE_OPEN = (1 << 0); /** WPA_PSK or WPA2PSK */ public static final byte AUTH_CODE_PSK = (1 << 1); /** any EAPOL */ public static final byte AUTH_CODE_EAPOL = (1 << 2); /** SSID of the network */ public String ssid; /** Bitmask of the FLAG_XXX */ public byte flags = 0; /** Bitmask of the ATUH_XXX */ public byte authBitField = 0; /** frequencies on which the particular network needs to be scanned for */ public int[] frequencies = {}; /** * default constructor for PnoNetwork */ public PnoNetwork(String ssid) { this.ssid = ssid; } @Override public int hashCode() { return Objects.hash(ssid, flags, authBitField); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PnoNetwork)) { return false; } PnoNetwork lhs = (PnoNetwork) obj; return TextUtils.equals(this.ssid, lhs.ssid) && this.flags == lhs.flags && this.authBitField == lhs.authBitField; } } /** Connected vs Disconnected PNO flag {@hide} */ public boolean isConnected; /** Minimum 5GHz RSSI for a BSSID to be considered */ public int min5GHzRssi; /** Minimum 2.4GHz RSSI for a BSSID to be considered */ public int min24GHzRssi; /** Minimum 6GHz RSSI for a BSSID to be considered */ public int min6GHzRssi; /** Iterations of Pno scan */ public int scanIterations; /** Multiplier of Pno scan interval */ public int scanIntervalMultiplier; /** Pno Network filter list */ public PnoNetwork[] networkList; /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(isConnected ? 1 : 0); dest.writeInt(min5GHzRssi); dest.writeInt(min24GHzRssi); dest.writeInt(min6GHzRssi); dest.writeInt(scanIterations); dest.writeInt(scanIntervalMultiplier); if (networkList != null) { dest.writeInt(networkList.length); for (int i = 0; i < networkList.length; i++) { dest.writeString(networkList[i].ssid); dest.writeByte(networkList[i].flags); dest.writeByte(networkList[i].authBitField); dest.writeIntArray(networkList[i].frequencies); } } else { dest.writeInt(0); } } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public PnoSettings createFromParcel(Parcel in) { PnoSettings settings = new PnoSettings(); settings.isConnected = in.readInt() == 1; settings.min5GHzRssi = in.readInt(); settings.min24GHzRssi = in.readInt(); settings.min6GHzRssi = in.readInt(); settings.scanIterations = in.readInt(); settings.scanIntervalMultiplier = in.readInt(); int numNetworks = in.readInt(); settings.networkList = new PnoNetwork[numNetworks]; for (int i = 0; i < numNetworks; i++) { String ssid = in.readString(); PnoNetwork network = new PnoNetwork(ssid); network.flags = in.readByte(); network.authBitField = in.readByte(); network.frequencies = in.createIntArray(); settings.networkList[i] = network; } return settings; } public PnoSettings[] newArray(int size) { return new PnoSettings[size]; } }; } /** * interface to get scan events on; specify this on {@link #startBackgroundScan} or * {@link #startScan} */ public interface ScanListener extends ActionListener { /** * Framework co-ordinates scans across multiple apps; so it may not give exactly the * same period requested. If period of a scan is changed; it is reported by this event. * @deprecated Background scan support has always been hardware vendor dependent. This * support may not be present on newer devices. Use {@link #startScan(ScanSettings, * ScanListener)} instead for single scans. */ @Deprecated public void onPeriodChanged(int periodInMs); /** * reports results retrieved from background scan and single shot scans */ public void onResults(ScanData[] results); /** * reports full scan result for each access point found in scan */ public void onFullResult(ScanResult fullScanResult); } /** * interface to get PNO scan events on; specify this on {@link #startDisconnectedPnoScan} and * {@link #startConnectedPnoScan}. * {@hide} */ public interface PnoScanListener extends ScanListener { /** * Invoked when one of the PNO networks are found in scan results. */ void onPnoNetworkFound(ScanResult[] results); } /** * Enable/Disable wifi scanning. * * @param enable set to true to enable scanning, set to false to disable all types of scanning. * * @see WifiManager#ACTION_WIFI_SCAN_AVAILABILITY_CHANGED * {@hide} */ @SystemApi @RequiresPermission(Manifest.permission.NETWORK_STACK) public void setScanningEnabled(boolean enable) { try { mService.setScanningEnabled(enable, Process.myTid(), mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Register a listener that will receive results from all single scans. * Either the {@link ScanListener#onSuccess()} or {@link ScanListener#onFailure(int, String)} * method will be called once when the listener is registered. * Afterwards (assuming onSuccess was called), all subsequent single scan results will be * delivered to the listener. It is possible that onFullResult will not be called for all * results of the first scan if the listener was registered during the scan. *

    * On {@link android.os.Build.VERSION_CODES#TIRAMISU} or above this API can be called by * an app with either {@link android.Manifest.permission#LOCATION_HARDWARE} or * {@link android.Manifest.permission#NETWORK_STACK}. On platform versions prior to * {@link android.os.Build.VERSION_CODES#TIRAMISU}, the caller must have * {@link android.Manifest.permission#NETWORK_STACK}. * * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this request, and must also be specified to cancel the request. * Multiple requests should also not share this object. * @throws SecurityException if the caller does not have permission. */ @RequiresPermission(anyOf = { Manifest.permission.LOCATION_HARDWARE, Manifest.permission.NETWORK_STACK}) public void registerScanListener(@NonNull @CallbackExecutor Executor executor, @NonNull ScanListener listener) { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(listener, "listener cannot be null"); ServiceListener serviceListener = new ServiceListener(listener, executor); if (!addListener(listener, serviceListener)) { Binder.clearCallingIdentity(); executor.execute(() -> // TODO: fix the typo in WifiScanner system API. listener.onFailure(REASON_DUPLICATE_REQEUST, // NOTYPO "Outstanding request with same key not stopped yet")); return; } try { mService.registerScanListener(serviceListener, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { Log.e(TAG, "Failed to register listener " + listener); removeListener(listener); throw e.rethrowFromSystemServer(); } } /** * Overload of {@link #registerScanListener(Executor, ScanListener)} that executes the callback * synchronously. * @hide */ @RequiresPermission(Manifest.permission.NETWORK_STACK) public void registerScanListener(@NonNull ScanListener listener) { registerScanListener(new SynchronousExecutor(), listener); } /** * Deregister a listener for ongoing single scans * @param listener specifies which scan to cancel; must be same object as passed in {@link * #registerScanListener} */ public void unregisterScanListener(@NonNull ScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); ServiceListener serviceListener = getServiceListener(listener); if (serviceListener == null) { Log.e(TAG, "listener does not exist"); return; } try { mService.unregisterScanListener(serviceListener, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { Log.e(TAG, "failed to unregister listener"); throw e.rethrowFromSystemServer(); } finally { removeListener(listener); } } /** * Check whether the Wi-Fi subsystem has started a scan and is waiting for scan results. * @return true if a scan initiated via * {@link WifiScanner#startScan(ScanSettings, ScanListener)} or * {@link WifiManager#startScan()} is in progress. * false if there is currently no scanning initiated by {@link WifiScanner} or * {@link WifiManager}, but it's still possible the wifi radio is scanning for * another reason. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean isScanning() { try { return mService.isScanning(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** start wifi scan in background * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(ScanSettings settings, ScanListener listener) { startBackgroundScan(settings, listener, null); } /** start wifi scan in background * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param workSource WorkSource to blame for power usage * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * @deprecated Background scan support has always been hardware vendor dependent. This support * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { Objects.requireNonNull(listener, "listener cannot be null"); if (getServiceListener(listener) != null) return; ServiceListener serviceListener = new ServiceListener(listener, new SynchronousExecutor()); if (!addListener(listener, serviceListener)) { Log.e(TAG, "listener already exist!"); return; } try { mService.startBackgroundScan(serviceListener, settings, workSource, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * stop an ongoing wifi scan * @param listener specifies which scan to cancel; must be same object as passed in {@link * #startBackgroundScan} * @deprecated Background scan support has always been hardware vendor dependent. This support * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopBackgroundScan(ScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); ServiceListener serviceListener = getServiceListener(listener); if (serviceListener == null) { Log.e(TAG, "listener does not exist"); return; } try { mService.stopBackgroundScan(serviceListener, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { removeListener(listener); } } /** * reports currently available scan results on appropriate listeners * @return true if all scan results were reported correctly * @deprecated Background scan support has always been hardware vendor dependent. This support * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)} * instead for single scans. */ @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults() { try { return mService.getScanResults(mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(ScanSettings settings, ScanListener listener) { startScan(settings, listener, null); } /** * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * @param workSource WorkSource to blame for power usage */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) { startScan(settings, new SynchronousExecutor(), listener, workSource); } /** * starts a single scan and reports results asynchronously * @param settings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * @param workSource WorkSource to blame for power usage * @hide */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor, ScanListener listener, WorkSource workSource) { Objects.requireNonNull(listener, "listener cannot be null"); if (getServiceListener(listener) != null) return; ServiceListener serviceListener = new ServiceListener(listener, executor); if (!addListener(listener, serviceListener)) { Log.e(TAG, "listener already exist!"); return; } try { mService.startScan(serviceListener, settings, workSource, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * stops an ongoing single shot scan; only useful after {@link #startScan} if onResults() * hasn't been called on the listener, ignored otherwise * @param listener */ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopScan(ScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); ServiceListener serviceListener = getServiceListener(listener); if (serviceListener == null) { Log.e(TAG, "listener does not exist"); return; } try { mService.stopScan(serviceListener, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { removeListener(listener); } } /** * Retrieve the most recent scan results from a single scan request. * *

    * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the * returned scan results should include separate scan result for each BSSID within the * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs. * Original Multi-BSSID Element will be included in the Information Elements attached to * each of the scan results. * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an * optional requirement for devices running with older WiFi generations. *

    */ @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public List getSingleScanResults() { try { return mService.getSingleScanResults(mContext.getPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Retrieve the scan data cached by the hardware. * *

    * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the * returned scan results should include separate scan result for each BSSID within the * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs. * Original Multi-BSSID Element will be included in the Information Elements attached to * each of the scan results. * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an * optional requirement for devices running with older WiFi generations. *

    * * @param executor The executor on which callback will be invoked. * @param resultsCallback An asynchronous callback that will return the cached scan data. * * @throws UnsupportedOperationException if the API is not supported on this SDK version. * @throws SecurityException if the caller does not have permission. * @throws NullPointerException if the caller provided invalid inputs. */ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, LOCATION_HARDWARE}) public void getCachedScanData(@NonNull @CallbackExecutor Executor executor, @NonNull Consumer resultsCallback) { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null"); try { mService.getCachedScanData(mContext.getPackageName(), mContext.getAttributionTag(), new IScanDataListener.Stub() { @Override public void onResult(@NonNull ScanData scanData) { Binder.clearCallingIdentity(); executor.execute(() -> { resultsCallback.accept(scanData); }); } }); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private void startPnoScan(PnoScanListener listener, Executor executor, ScanSettings scanSettings, PnoSettings pnoSettings) { // Set the PNO scan flag. scanSettings.isPnoScan = true; if (getServiceListener(listener) != null) return; ServiceListener serviceListener = new ServiceListener(listener, executor); if (!addListener(listener, serviceListener)) { Log.w(TAG, "listener already exist!"); } try { mService.startPnoScan(serviceListener, scanSettings, pnoSettings, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Start wifi connected PNO scan * @param scanSettings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param pnoSettings specifies various parameters for PNO; for more information look at * {@link PnoSettings} * @param executor the Executor on which to run the callback. * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * {@hide} */ public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); pnoSettings.isConnected = true; startPnoScan(listener, executor, scanSettings, pnoSettings); } /** * Start wifi disconnected PNO scan * @param scanSettings specifies various parameters for the scan; for more information look at * {@link ScanSettings} * @param pnoSettings specifies various parameters for PNO; for more information look at * {@link PnoSettings} * @param listener specifies the object to report events to. This object is also treated as a * key for this scan, and must also be specified to cancel the scan. Multiple * scans should also not share this object. * {@hide} */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings, @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null"); pnoSettings.isConnected = false; startPnoScan(listener, executor, scanSettings, pnoSettings); } /** * Stop an ongoing wifi PNO scan * @param listener specifies which scan to cancel; must be same object as passed in {@link * #startPnoScan} * {@hide} */ @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void stopPnoScan(ScanListener listener) { Objects.requireNonNull(listener, "listener cannot be null"); ServiceListener serviceListener = getServiceListener(listener); if (serviceListener == null) { Log.e(TAG, "listener does not exist"); return; } try { mService.stopPnoScan(serviceListener, mContext.getOpPackageName(), mContext.getAttributionTag()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { removeListener(listener); } } /** * Enable verbose logging. For internal use by wifi framework only. * @param enabled whether verbose logging is enabled * @hide */ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void enableVerboseLogging(boolean enabled) { try { mService.enableVerboseLogging(enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** specifies information about an access point of interest */ @Deprecated public static class BssidInfo { /** bssid of the access point; in XX:XX:XX:XX:XX:XX format */ public String bssid; /** low signal strength threshold; more information at {@link ScanResult#level} */ public int low; /* minimum RSSI */ /** high signal threshold; more information at {@link ScanResult#level} */ public int high; /* maximum RSSI */ /** channel frequency (in KHz) where you may find this BSSID */ public int frequencyHint; } /** @hide */ @SystemApi @Deprecated public static class WifiChangeSettings implements Parcelable { public int rssiSampleSize; /* sample size for RSSI averaging */ public int lostApSampleSize; /* samples to confirm AP's loss */ public int unchangedSampleSize; /* samples to confirm no change */ public int minApsBreachingThreshold; /* change threshold to trigger event */ public int periodInMs; /* scan period in millisecond */ public BssidInfo[] bssidInfos; /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public WifiChangeSettings createFromParcel(Parcel in) { return new WifiChangeSettings(); } public WifiChangeSettings[] newArray(int size) { return new WifiChangeSettings[size]; } }; } /** configure WifiChange detection * @param rssiSampleSize number of samples used for RSSI averaging * @param lostApSampleSize number of samples to confirm an access point's loss * @param unchangedSampleSize number of samples to confirm there are no changes * @param minApsBreachingThreshold minimum number of access points that need to be * out of range to detect WifiChange * @param periodInMs indicates period of scan to find changes * @param bssidInfos access points to watch */ @Deprecated @SuppressLint("RequiresPermission") public void configureWifiChange( int rssiSampleSize, /* sample size for RSSI averaging */ int lostApSampleSize, /* samples to confirm AP's loss */ int unchangedSampleSize, /* samples to confirm no change */ int minApsBreachingThreshold, /* change threshold to trigger event */ int periodInMs, /* period of scan */ BssidInfo[] bssidInfos /* signal thresholds to cross */ ) { throw new UnsupportedOperationException(); } /** * interface to get wifi change events on; use this on {@link #startTrackingWifiChange} */ @Deprecated public interface WifiChangeListener extends ActionListener { /** indicates that changes were detected in wifi environment * @param results indicate the access points that exhibited change */ public void onChanging(ScanResult[] results); /* changes are found */ /** indicates that no wifi changes are being detected for a while * @param results indicate the access points that are bing monitored for change */ public void onQuiescence(ScanResult[] results); /* changes settled down */ } /** * track changes in wifi environment * @param listener object to report events on; this object must be unique and must also be * provided on {@link #stopTrackingWifiChange} */ @Deprecated @SuppressLint("RequiresPermission") public void startTrackingWifiChange(WifiChangeListener listener) { throw new UnsupportedOperationException(); } /** * stop tracking changes in wifi environment * @param listener object that was provided to report events on {@link * #stopTrackingWifiChange} */ @Deprecated @SuppressLint("RequiresPermission") public void stopTrackingWifiChange(WifiChangeListener listener) { throw new UnsupportedOperationException(); } /** @hide */ @SystemApi @Deprecated @SuppressLint("RequiresPermission") public void configureWifiChange(WifiChangeSettings settings) { throw new UnsupportedOperationException(); } /** interface to receive hotlist events on; use this on {@link #setHotlist} */ @Deprecated public static interface BssidListener extends ActionListener { /** indicates that access points were found by on going scans * @param results list of scan results, one for each access point visible currently */ public void onFound(ScanResult[] results); /** indicates that access points were missed by on going scans * @param results list of scan results, for each access point that is not visible anymore */ public void onLost(ScanResult[] results); } /** @hide */ @SystemApi @Deprecated public static class HotlistSettings implements Parcelable { public BssidInfo[] bssidInfos; public int apLostThreshold; /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** Implement the Parcelable interface {@hide} */ public void writeToParcel(Parcel dest, int flags) { } /** Implement the Parcelable interface {@hide} */ public static final @NonNull Creator CREATOR = new Creator() { public HotlistSettings createFromParcel(Parcel in) { HotlistSettings settings = new HotlistSettings(); return settings; } public HotlistSettings[] newArray(int size) { return new HotlistSettings[size]; } }; } /** * set interesting access points to find * @param bssidInfos access points of interest * @param apLostThreshold number of scans needed to indicate that AP is lost * @param listener object provided to report events on; this object must be unique and must * also be provided on {@link #stopTrackingBssids} */ @Deprecated @SuppressLint("RequiresPermission") public void startTrackingBssids(BssidInfo[] bssidInfos, int apLostThreshold, BssidListener listener) { throw new UnsupportedOperationException(); } /** * remove tracking of interesting access points * @param listener same object provided in {@link #startTrackingBssids} */ @Deprecated @SuppressLint("RequiresPermission") public void stopTrackingBssids(BssidListener listener) { throw new UnsupportedOperationException(); } /* private members and methods */ private static final String TAG = "WifiScanner"; private static final boolean DBG = false; /* commands for Wifi Service */ private static final int BASE = Protocol.BASE_WIFI_SCANNER; /** @hide */ public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; /** @hide */ public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; /** @hide */ public static final int CMD_GET_SCAN_RESULTS = BASE + 4; /** @hide */ public static final int CMD_SCAN_RESULT = BASE + 5; /** @hide */ public static final int CMD_CACHED_SCAN_DATA = BASE + 6; /** @hide */ public static final int CMD_OP_SUCCEEDED = BASE + 17; /** @hide */ public static final int CMD_OP_FAILED = BASE + 18; /** @hide */ public static final int CMD_FULL_SCAN_RESULT = BASE + 20; /** @hide */ public static final int CMD_START_SINGLE_SCAN = BASE + 21; /** @hide */ public static final int CMD_STOP_SINGLE_SCAN = BASE + 22; /** @hide */ public static final int CMD_SINGLE_SCAN_COMPLETED = BASE + 23; /** @hide */ public static final int CMD_START_PNO_SCAN = BASE + 24; /** @hide */ public static final int CMD_STOP_PNO_SCAN = BASE + 25; /** @hide */ public static final int CMD_PNO_NETWORK_FOUND = BASE + 26; /** @hide */ public static final int CMD_REGISTER_SCAN_LISTENER = BASE + 27; /** @hide */ public static final int CMD_DEREGISTER_SCAN_LISTENER = BASE + 28; /** @hide */ public static final int CMD_GET_SINGLE_SCAN_RESULTS = BASE + 29; /** @hide */ public static final int CMD_ENABLE = BASE + 30; /** @hide */ public static final int CMD_DISABLE = BASE + 31; private Context mContext; private IWifiScanner mService; private final Object mListenerMapLock = new Object(); private final Map mListenerMap = new HashMap<>(); /** * Create a new WifiScanner instance. * Applications will almost always want to use * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. * * @param context the application context * @param service the Binder interface for {@link Context#WIFI_SCANNING_SERVICE} * @param looper the Looper used to deliver callbacks * * @hide */ public WifiScanner(@NonNull Context context, @NonNull IWifiScanner service, @NonNull Looper looper) { mContext = context; mService = service; } // Add a listener into listener map. If the listener already exists, return INVALID_KEY and // send an error message to internal handler; Otherwise add the listener to the listener map and // return the key of the listener. private boolean addListener(ActionListener listener, ServiceListener serviceListener) { synchronized (mListenerMapLock) { boolean keyExists = mListenerMap.containsKey(listener); // Note we need to put the listener into listener map even if it's a duplicate as the // internal handler will need the key to find the listener. In case of duplicates, // removing duplicate key logic will be handled in internal handler. if (keyExists) { if (DBG) Log.d(TAG, "listener key already exists"); return false; } mListenerMap.put(listener, serviceListener); return true; } } private ServiceListener getServiceListener(ActionListener listener) { if (listener == null) return null; synchronized (mListenerMapLock) { return mListenerMap.get(listener); } } private void removeListener(ActionListener listener) { if (listener == null) return; synchronized (mListenerMapLock) { mListenerMap.remove(listener); } } }