script-astra/Android/Sdk/sources/android-35/android/net/wifi/WifiScanner.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

2005 lines
81 KiB
Java
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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)}.
* <p>
* 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:
* <li> {@link #WIFI_BAND_6_GHZ} </li>
* <li> {@link #WIFI_BAND_24_5_6_GHZ} </li>
* <li> {@link #WIFI_BAND_24_5_WITH_DFS_6_GHZ} </li>
* <li> {@link #WIFI_BAND_24_5_6_60_GHZ} </li>
* <li> {@link #WIFI_BAND_24_5_WITH_DFS_6_60_GHZ} </li>
* <li> {@link #WIFI_BAND_ALL} </li>
**/
public static final int WIFI_RNR_ENABLED_IF_WIFI_BAND_6_GHZ_SCANNED = 0;
/**
* This constant is used for {@link ScanSettings#setRnrSetting(int)}.
* <p>
* 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)}.
* <p>
* 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<Integer> 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<Integer> 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<HiddenNetwork> hiddenNetworks = new ArrayList<>();
/**
* vendor IEs -- list of ScanResult.InformationElement, configured by App
* see {@link #setVendorIes(List)}
*/
@NonNull
private List<ScanResult.InformationElement> 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.
*
* <p>
* 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<ScanResult.InformationElement> 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<ScanResult.InformationElement> 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<ScanSettings> CREATOR =
new Creator<ScanSettings>() {
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<ScanResult> 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<ScanResult> 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.
* <p>
* For ex:
* <li> Scenario 1: Fully scanned 2.4Ghz band, partially scanned 5Ghz band
* - Returns {@link #WIFI_BAND_24_GHZ}
* </li>
* <li> Scenario 2: Partially scanned 2.4Ghz band and 5Ghz band
* - Returns {@link #WIFI_BAND_UNSPECIFIED}
* </li>
* </p>
*/
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<ScanData> CREATOR =
new Creator<ScanData>() {
public ScanData createFromParcel(Parcel in) {
int id = in.readInt();
int flags = in.readInt();
int bucketsScanned = in.readInt();
int bandsScanned = in.readInt();
List<ScanResult> 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<ParcelableScanData> CREATOR =
new Creator<ParcelableScanData>() {
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<ParcelableScanResults> CREATOR =
new Creator<ParcelableScanResults>() {
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<PnoSettings> CREATOR =
new Creator<PnoSettings>() {
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.
* <p>
* 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.
*
* <p>
* When an Access Points 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.
* </p>
*/
@NonNull
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public List<ScanResult> getSingleScanResults() {
try {
return mService.getSingleScanResults(mContext.getPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Retrieve the scan data cached by the hardware.
*
* <p>
* When an Access Points 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.
* </p>
*
* @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<ScanData> 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<WifiChangeSettings> CREATOR =
new Creator<WifiChangeSettings>() {
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<HotlistSettings> CREATOR =
new Creator<HotlistSettings>() {
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<ActionListener, ServiceListener> 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);
}
}
}