blob: 3215246a9c1ffb1a03d24dcc4bf6733b9bb232db [file] [log] [blame]
/*
* Copyright (C) 2017 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.nl80211;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.AlarmManager;
import android.content.Context;
import android.net.wifi.SoftApInfo;
import android.net.wifi.WifiAnnotations;
import android.net.wifi.WifiScanner;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
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.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used
* to encapsulate the Wi-Fi 80211nl management interface. The
* interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
*
* @hide
*/
@SystemApi
@SystemService(Context.WIFI_NL80211_SERVICE)
public class WifiNl80211Manager {
private static final String TAG = "WifiNl80211Manager";
private boolean mVerboseLoggingEnabled = false;
/**
* The {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
* timeout, in milliseconds, after which
* {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
* {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}.
*/
private static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000;
private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"SCAN_TYPE_"},
value = {SCAN_TYPE_SINGLE_SCAN,
SCAN_TYPE_PNO_SCAN})
public @interface ScanResultType {}
/**
* Specifies a scan type: single scan initiated by the framework. Can be used in
* {@link #getScanResults(String, int)} to specify the type of scan result to fetch.
*/
public static final int SCAN_TYPE_SINGLE_SCAN = 0;
/**
* Specifies a scan type: PNO scan. Can be used in {@link #getScanResults(String, int)} to
* specify the type of scan result to fetch.
*/
public static final int SCAN_TYPE_PNO_SCAN = 1;
private AlarmManager mAlarmManager;
private Handler mEventHandler;
// Cached wificond binder handlers.
private IWificond mWificond;
private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
private Runnable mDeathEventHandler;
/**
* Ensures that no more than one sendMgmtFrame operation runs concurrently.
*/
private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
/**
* Interface used when waiting for scans to be completed (with results).
*/
public interface ScanEventCallback {
/**
* Called when scan results are available. Scans results should then be obtained from
* {@link #getScanResults(String, int)}.
*/
void onScanResultReady();
/**
* Called when a scan has failed.
*/
void onScanFailed();
}
/**
* Interface for a callback to provide information about PNO scan request requested with
* {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. Note that the
* callback are for the status of the request - not the scan itself. The results of the scan
* are returned with {@link ScanEventCallback}.
*/
public interface PnoScanRequestCallback {
/**
* Called when a PNO scan request has been successfully submitted.
*/
void onPnoRequestSucceeded();
/**
* Called when a PNO scan request fails.
*/
void onPnoRequestFailed();
}
private class ScanEventHandler extends IScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
ScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void OnScanResultReady() {
Log.d(TAG, "Scan result ready event");
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onScanResultReady());
}
@Override
public void OnScanFailed() {
Log.d(TAG, "Scan failed event");
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onScanFailed());
}
}
/**
* Result of a signal poll requested using {@link #signalPoll(String)}.
*/
public static class SignalPollResult {
/** @hide */
public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps,
int associationFrequencyMHz) {
this.currentRssiDbm = currentRssiDbm;
this.txBitrateMbps = txBitrateMbps;
this.rxBitrateMbps = rxBitrateMbps;
this.associationFrequencyMHz = associationFrequencyMHz;
}
/**
* RSSI value in dBM.
*/
public final int currentRssiDbm;
/**
* Transmission bit rate in Mbps.
*/
public final int txBitrateMbps;
/**
* Last received packet bit rate in Mbps.
*/
public final int rxBitrateMbps;
/**
* Association frequency in MHz.
*/
public final int associationFrequencyMHz;
}
/**
* Transmission counters obtained using {@link #getTxPacketCounters(String)}.
*/
public static class TxPacketCounters {
/** @hide */
public TxPacketCounters(int txPacketSucceeded, int txPacketFailed) {
this.txPacketSucceeded = txPacketSucceeded;
this.txPacketFailed = txPacketFailed;
}
/**
* Number of successfully transmitted packets.
*/
public final int txPacketSucceeded;
/**
* Number of packet transmission failures.
*/
public final int txPacketFailed;
}
/**
* Callbacks for SoftAp interface registered using
* {@link #registerApCallback(String, Executor, SoftApCallback)}.
*/
public interface SoftApCallback {
/**
* Invoked when there is a fatal failure and the SoftAp is shutdown.
*/
void onFailure();
/**
* Invoked when there is a change in the associated station (STA).
* @param client Information about the client whose status has changed.
* @param isConnected Indication as to whether the client is connected (true), or
* disconnected (false).
*/
void onConnectedClientsChanged(@NonNull NativeWifiClient client, boolean isConnected);
/**
* Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different
* channel. Also called on initial registration.
* @param frequencyMhz The new frequency of the SoftAp. A value of 0 is invalid and is an
* indication that the SoftAp is not enabled.
* @param bandwidth The new bandwidth of the SoftAp.
*/
void onSoftApChannelSwitched(int frequencyMhz, @WifiAnnotations.Bandwidth int bandwidth);
}
/**
* Callback to notify the results of a
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} call.
* Note: no callbacks will be triggered if the interface dies while sending a frame.
*/
public interface SendMgmtFrameCallback {
/**
* Called when the management frame was successfully sent and ACKed by the recipient.
* @param elapsedTimeMs The elapsed time between when the management frame was sent and when
* the ACK was processed, in milliseconds, as measured by wificond.
* This includes the time that the send frame spent queuing before it
* was sent, any firmware retries, and the time the received ACK spent
* queuing before it was processed.
*/
void onAck(int elapsedTimeMs);
/**
* Called when the send failed.
* @param reason The error code for the failure.
*/
void onFailure(@SendMgmtFrameError int reason);
}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
SEND_MGMT_FRAME_ERROR_NO_ACK,
SEND_MGMT_FRAME_ERROR_TIMEOUT,
SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
public @interface SendMgmtFrameError {}
// Send management frame error codes
/**
* Unknown error occurred during call to
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
*/
public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
/**
* Specifying the MCS rate in
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} is not
* supported by this device.
*/
public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
/**
* Driver reported that no ACK was received for the frame transmitted using
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
*/
public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
/**
* Error code for when the driver fails to report on the status of the frame sent by
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
* after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
*/
public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
/**
* An existing call to
* {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
* is in progress. Another frame cannot be sent until the first call completes.
*/
public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
/** @hide */
public WifiNl80211Manager(Context context) {
mAlarmManager = context.getSystemService(AlarmManager.class);
mEventHandler = new Handler(context.getMainLooper());
}
/** @hide */
@VisibleForTesting
public WifiNl80211Manager(Context context, IWificond wificond) {
this(context);
mWificond = wificond;
}
private class PnoScanEventHandler extends IPnoScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
PnoScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void OnPnoNetworkFound() {
Log.d(TAG, "Pno scan result event");
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onScanResultReady());
}
@Override
public void OnPnoScanFailed() {
Log.d(TAG, "Pno Scan failed event");
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onScanFailed());
}
}
/**
* Listener for AP Interface events.
*/
private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
private Executor mExecutor;
private SoftApCallback mSoftApListener;
ApInterfaceEventCallback(Executor executor, SoftApCallback listener) {
mExecutor = executor;
mSoftApListener = listener;
}
@Override
public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "onConnectedClientsChanged called with "
+ client.getMacAddress() + " isConnected: " + isConnected);
}
Binder.clearCallingIdentity();
mExecutor.execute(() -> mSoftApListener.onConnectedClientsChanged(client, isConnected));
}
@Override
public void onSoftApChannelSwitched(int frequency, int bandwidth) {
Binder.clearCallingIdentity();
mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency,
toFrameworkBandwidth(bandwidth)));
}
private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) {
switch(bandwidth) {
case IApInterfaceEventCallback.BANDWIDTH_INVALID:
return SoftApInfo.CHANNEL_WIDTH_INVALID;
case IApInterfaceEventCallback.BANDWIDTH_20_NOHT:
return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
case IApInterfaceEventCallback.BANDWIDTH_20:
return SoftApInfo.CHANNEL_WIDTH_20MHZ;
case IApInterfaceEventCallback.BANDWIDTH_40:
return SoftApInfo.CHANNEL_WIDTH_40MHZ;
case IApInterfaceEventCallback.BANDWIDTH_80:
return SoftApInfo.CHANNEL_WIDTH_80MHZ;
case IApInterfaceEventCallback.BANDWIDTH_80P80:
return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
case IApInterfaceEventCallback.BANDWIDTH_160:
return SoftApInfo.CHANNEL_WIDTH_160MHZ;
default:
return SoftApInfo.CHANNEL_WIDTH_INVALID;
}
}
}
/**
* Callback triggered by wificond.
*/
private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub {
private Executor mExecutor;
private SendMgmtFrameCallback mCallback;
private AlarmManager.OnAlarmListener mTimeoutCallback;
/**
* ensures that mCallback is only called once
*/
private boolean mWasCalled;
private void runIfFirstCall(Runnable r) {
if (mWasCalled) return;
mWasCalled = true;
mSendMgmtFrameInProgress.set(false);
r.run();
}
SendMgmtFrameEvent(@NonNull Executor executor, @NonNull SendMgmtFrameCallback callback) {
mExecutor = executor;
mCallback = callback;
// called in main thread
mTimeoutCallback = () -> runIfFirstCall(() -> {
if (mVerboseLoggingEnabled) {
Log.e(TAG, "Timed out waiting for ACK");
}
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT));
});
mWasCalled = false;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS,
TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler);
}
// called in binder thread
@Override
public void OnAck(int elapsedTimeMs) {
// post to main thread
mEventHandler.post(() -> runIfFirstCall(() -> {
mAlarmManager.cancel(mTimeoutCallback);
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs));
}));
}
// called in binder thread
@Override
public void OnFailure(int reason) {
// post to main thread
mEventHandler.post(() -> runIfFirstCall(() -> {
mAlarmManager.cancel(mTimeoutCallback);
Binder.clearCallingIdentity();
mExecutor.execute(() -> mCallback.onFailure(reason));
}));
}
}
/**
* Called by the binder subsystem upon remote object death.
* Invoke all the register death handlers and clear state.
* @hide
*/
@VisibleForTesting
public void binderDied() {
mEventHandler.post(() -> {
Log.e(TAG, "Wificond died!");
clearState();
// Invalidate the global wificond handle on death. Will be refreshed
// on the next setup call.
mWificond = null;
if (mDeathEventHandler != null) {
mDeathEventHandler.run();
}
});
}
/**
* Enable or disable verbose logging of the WifiNl80211Manager module.
* @param enable True to enable verbose logging. False to disable verbose logging.
*/
public void enableVerboseLogging(boolean enable) {
mVerboseLoggingEnabled = enable;
}
/**
* Register a death notification for the WifiNl80211Manager which acts as a proxy for the
* wificond daemon (i.e. the death listener will be called when and if the wificond daemon
* dies).
*
* @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies.
*/
public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) {
if (mDeathEventHandler != null) {
Log.e(TAG, "Death handler already present");
}
mDeathEventHandler = deathEventHandler;
}
/**
* Helper method to retrieve the global wificond handle and register for
* death notifications.
*/
private boolean retrieveWificondAndRegisterForDeath() {
if (mWificond != null) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "Wificond handle already retrieved");
}
// We already have a wificond handle.
return true;
}
IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE);
mWificond = IWificond.Stub.asInterface(binder);
if (mWificond == null) {
Log.e(TAG, "Failed to get reference to wificond");
return false;
}
try {
mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register death notification for wificond");
// The remote has already died.
return false;
}
return true;
}
/**
* Set up an interface for client (STA) mode.
*
* @param ifaceName Name of the interface to configure.
* @param executor The Executor on which to execute the callbacks.
* @param scanCallback A callback for framework initiated scans.
* @param pnoScanCallback A callback for PNO (offloaded) scans.
* @return true on success.
*/
public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
@NonNull @CallbackExecutor Executor executor,
@NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
Log.d(TAG, "Setting up interface for client mode");
if (!retrieveWificondAndRegisterForDeath()) {
return false;
}
if (scanCallback == null || pnoScanCallback == null || executor == null) {
Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks");
return false;
}
IClientInterface clientInterface = null;
try {
clientInterface = mWificond.createClientInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return false;
}
if (clientInterface == null) {
Log.e(TAG, "Could not get IClientInterface instance from wificond");
return false;
}
Binder.allowBlocking(clientInterface.asBinder());
// Refresh Handlers
mClientInterfaces.put(ifaceName, clientInterface);
try {
IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
if (wificondScanner == null) {
Log.e(TAG, "Failed to get WificondScannerImpl");
return false;
}
mWificondScanners.put(ifaceName, wificondScanner);
Binder.allowBlocking(wificondScanner.asBinder());
ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);
mScanEventHandlers.put(ifaceName, scanEventHandler);
wificondScanner.subscribeScanEvents(scanEventHandler);
PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor,
pnoScanCallback);
mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler);
wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
} catch (RemoteException e) {
Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
}
return true;
}
/**
* Tear down a specific client (STA) interface configured using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
*
* @param ifaceName Name of the interface to tear down.
* @return Returns true on success, false on failure (e.g. when called before an interface was
* set up).
*/
public boolean tearDownClientInterface(@NonNull String ifaceName) {
if (getClientInterface(ifaceName) == null) {
Log.e(TAG, "No valid wificond client interface handler");
return false;
}
try {
IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
if (scannerImpl != null) {
scannerImpl.unsubscribeScanEvents();
scannerImpl.unsubscribePnoScanEvents();
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
return false;
}
if (mWificond == null) {
Log.e(TAG, "Reference to wifiCond is null");
return false;
}
boolean success;
try {
success = mWificond.tearDownClientInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown client interface due to remote exception");
return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown client interface");
return false;
}
mClientInterfaces.remove(ifaceName);
mWificondScanners.remove(ifaceName);
mScanEventHandlers.remove(ifaceName);
mPnoScanEventHandlers.remove(ifaceName);
return true;
}
/**
* Set up interface as a Soft AP.
*
* @param ifaceName Name of the interface to configure.
* @return true on success.
*/
public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) {
Log.d(TAG, "Setting up interface for soft ap mode");
if (!retrieveWificondAndRegisterForDeath()) {
return false;
}
IApInterface apInterface = null;
try {
apInterface = mWificond.createApInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IApInterface due to remote exception");
return false;
}
if (apInterface == null) {
Log.e(TAG, "Could not get IApInterface instance from wificond");
return false;
}
Binder.allowBlocking(apInterface.asBinder());
// Refresh Handlers
mApInterfaces.put(ifaceName, apInterface);
return true;
}
/**
* Tear down a Soft AP interface configured using
* {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface to tear down.
* @return Returns true on success, false on failure (e.g. when called before an interface was
* set up).
*/
public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
if (getApInterface(ifaceName) == null) {
Log.e(TAG, "No valid wificond ap interface handler");
return false;
}
if (mWificond == null) {
Log.e(TAG, "Reference to wifiCond is null");
return false;
}
boolean success;
try {
success = mWificond.tearDownApInterface(ifaceName);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown AP interface due to remote exception");
return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown AP interface");
return false;
}
mApInterfaces.remove(ifaceName);
mApInterfaceListeners.remove(ifaceName);
return true;
}
/**
* Tear down all interfaces, whether clients (STA) or Soft AP.
*
* @return Returns true on success.
*/
public boolean tearDownInterfaces() {
Log.d(TAG, "tearing down interfaces in wificond");
// Explicitly refresh the wificodn handler because |tearDownInterfaces()|
// could be used to cleanup before we setup any interfaces.
if (!retrieveWificondAndRegisterForDeath()) {
return false;
}
try {
for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
entry.getValue().unsubscribeScanEvents();
entry.getValue().unsubscribePnoScanEvents();
}
mWificond.tearDownInterfaces();
clearState();
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to tear down interfaces due to remote exception");
}
return false;
}
/** Helper function to look up the interface handle using name */
private IClientInterface getClientInterface(@NonNull String ifaceName) {
return mClientInterfaces.get(ifaceName);
}
/**
* Request signal polling.
*
* @param ifaceName Name of the interface on which to poll. The interface must have been
* already set up using
*{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @return A {@link SignalPollResult} object containing interface statistics, or a null on
* error (e.g. the interface hasn't been set up yet).
*/
@Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
IClientInterface iface = getClientInterface(ifaceName);
if (iface == null) {
Log.e(TAG, "No valid wificond client interface handler");
return null;
}
int[] resultArray;
try {
resultArray = iface.signalPoll();
if (resultArray == null || resultArray.length != 4) {
Log.e(TAG, "Invalid signal poll result from wificond");
return null;
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to do signal polling due to remote exception");
return null;
}
return new SignalPollResult(resultArray[0], resultArray[1], resultArray[3], resultArray[2]);
}
/**
* Get current transmit (Tx) packet counters of the specified interface. The interface must
* have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface.
* @return {@link TxPacketCounters} of the current interface or null on error (e.g. when
* called before the interface has been set up).
*/
@Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
IClientInterface iface = getClientInterface(ifaceName);
if (iface == null) {
Log.e(TAG, "No valid wificond client interface handler");
return null;
}
int[] resultArray;
try {
resultArray = iface.getPacketCounters();
if (resultArray == null || resultArray.length != 2) {
Log.e(TAG, "Invalid signal poll result from wificond");
return null;
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to do signal polling due to remote exception");
return null;
}
return new TxPacketCounters(resultArray[0], resultArray[1]);
}
/** Helper function to look up the scanner impl handle using name */
private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
return mWificondScanners.get(ifaceName);
}
/**
* Fetch the latest scan results of the indicated type for the specified interface. Note that
* this method fetches the latest results - it does not initiate a scan. Initiating a scan can
* be done using {@link #startScan(String, int, Set, List)} or
* {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface.
* @param scanType The type of scan result to be returned, can be
* {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.
* @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when
* called before the interface has been set up).
*/
@NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
@ScanResultType int scanType) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return new ArrayList<>();
}
List<NativeScanResult> results = null;
try {
if (scanType == SCAN_TYPE_SINGLE_SCAN) {
results = Arrays.asList(scannerImpl.getScanResults());
} else {
results = Arrays.asList(scannerImpl.getPnoScanResults());
}
} catch (RemoteException e1) {
Log.e(TAG, "Failed to create ScanDetail ArrayList");
}
if (results == null) {
results = new ArrayList<>();
}
if (mVerboseLoggingEnabled) {
Log.d(TAG, "get " + results.size() + " scan results from wificond");
}
return results;
}
/**
* Return scan type for the parcelable {@link SingleScanSettings}
*/
private static int getScanType(@WifiAnnotations.ScanType int scanType) {
switch (scanType) {
case WifiScanner.SCAN_TYPE_LOW_LATENCY:
return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
case WifiScanner.SCAN_TYPE_LOW_POWER:
return IWifiScannerImpl.SCAN_TYPE_LOW_POWER;
case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
default:
throw new IllegalArgumentException("Invalid scan type " + scanType);
}
}
/**
* Start a scan using the specified parameters. A scan is an asynchronous operation. The
* result of the operation is returned in the {@link ScanEventCallback} registered when
* setting up an interface using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
* The latest scans can be obtained using {@link #getScanResults(String, int)} and using a
* {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface on which to initiate the scan.
* @param scanType Type of scan to perform, can be any of
* {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or
* {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}.
* @param freqs list of frequencies to scan for, if null scan all supported channels.
* @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
* no hidden frequencies will be scanned for.
* @return Returns true on success, false on failure (e.g. when called before the interface
* has been set up).
*/
public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
@Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return false;
}
SingleScanSettings settings = new SingleScanSettings();
try {
settings.scanType = getScanType(scanType);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid scan type ", e);
return false;
}
settings.channelSettings = new ArrayList<>();
settings.hiddenNetworks = new ArrayList<>();
if (freqs != null) {
for (Integer freq : freqs) {
ChannelSettings channel = new ChannelSettings();
channel.frequency = freq;
settings.channelSettings.add(channel);
}
}
if (hiddenNetworkSSIDs != null) {
for (byte[] ssid : hiddenNetworkSSIDs) {
HiddenNetwork network = new HiddenNetwork();
network.ssid = ssid;
// settings.hiddenNetworks is expected to be very small, so this shouldn't cause
// any performance issues.
if (!settings.hiddenNetworks.contains(network)) {
settings.hiddenNetworks.add(network);
}
}
}
try {
return scannerImpl.scan(settings);
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request scan due to remote exception");
}
return false;
}
/**
* Request a PNO (Preferred Network Offload). The offload request and the scans are asynchronous
* operations. The result of the request are returned in the {@code callback} parameter which
* is an {@link PnoScanRequestCallback}. The scan results are are return in the
* {@link ScanEventCallback} which is registered when setting up an interface using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
* The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the
* {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface on which to request a PNO.
* @param pnoSettings PNO scan configuration.
* @param executor The Executor on which to execute the callback.
* @param callback Callback for the results of the offload request.
* @return true on success, false on failure (e.g. when called before the interface has been set
* up).
*/
public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings,
@NonNull @CallbackExecutor Executor executor,
@NonNull PnoScanRequestCallback callback) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return false;
}
if (callback == null || executor == null) {
Log.e(TAG, "startPnoScan called with a null callback");
return false;
}
try {
boolean success = scannerImpl.startPnoScan(pnoSettings);
if (success) {
executor.execute(callback::onPnoRequestSucceeded);
} else {
executor.execute(callback::onPnoRequestFailed);
}
return success;
} catch (RemoteException e1) {
Log.e(TAG, "Failed to start pno scan due to remote exception");
}
return false;
}
/**
* Stop PNO scan configured with
* {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName Name of the interface on which the PNO scan was configured.
* @return true on success, false on failure (e.g. when called before the interface has been
* set up).
*/
public boolean stopPnoScan(@NonNull String ifaceName) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return false;
}
try {
return scannerImpl.stopPnoScan();
} catch (RemoteException e1) {
Log.e(TAG, "Failed to stop pno scan due to remote exception");
}
return false;
}
/**
* Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure
* callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then
* this method has no impact.
*
* @param ifaceName Name of the interface on which the scan was started.
*/
public void abortScan(@NonNull String ifaceName) {
IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
if (scannerImpl == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return;
}
try {
scannerImpl.abortScan();
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request abortScan due to remote exception");
}
}
/**
* Query the list of valid frequencies (in MHz) for the provided band.
* The result depends on the on the country code that has been set.
*
* @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
* The following bands are supported {@link @WifiScanner.WifiBandBasic}:
* WifiScanner.WIFI_BAND_24_GHZ
* WifiScanner.WIFI_BAND_5_GHZ
* WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
* WifiScanner.WIFI_BAND_6_GHZ
* @return frequencies vector of valid frequencies (MHz), or an empty array for error.
* @throws IllegalArgumentException if band is not recognized.
*/
public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
if (mWificond == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
return new int[0];
}
int[] result = null;
try {
switch (band) {
case WifiScanner.WIFI_BAND_24_GHZ:
result = mWificond.getAvailable2gChannels();
break;
case WifiScanner.WIFI_BAND_5_GHZ:
result = mWificond.getAvailable5gNonDFSChannels();
break;
case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
result = mWificond.getAvailableDFSChannels();
break;
case WifiScanner.WIFI_BAND_6_GHZ:
result = mWificond.getAvailable6gChannels();
break;
default:
throw new IllegalArgumentException("unsupported band " + band);
}
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
}
if (result == null) {
result = new int[0];
}
return result;
}
/** Helper function to look up the interface handle using name */
private IApInterface getApInterface(@NonNull String ifaceName) {
return mApInterfaces.get(ifaceName);
}
/**
* Get the device phy capabilities for a given interface.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has
* not been set up).
*/
@Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
if (mWificond == null) {
Log.e(TAG, "Can not query for device wiphy capabilities at this time");
return null;
}
try {
return mWificond.getDeviceWiphyCapabilities(ifaceName);
} catch (RemoteException e) {
return null;
}
}
/**
* Register the provided callback handler for SoftAp events. The interface must first be created
* using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
* the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
* method is provided).
* <p>
* Note that only one callback can be registered at a time - any registration overrides previous
* registrations.
*
* @param ifaceName Name of the interface on which to register the callback.
* @param executor The Executor on which to execute the callbacks.
* @param callback Callback for AP events.
* @return true on success, false on failure (e.g. when called on an interface which has not
* been set up).
*/
public boolean registerApCallback(@NonNull String ifaceName,
@NonNull @CallbackExecutor Executor executor,
@NonNull SoftApCallback callback) {
IApInterface iface = getApInterface(ifaceName);
if (iface == null) {
Log.e(TAG, "No valid ap interface handler");
return false;
}
if (callback == null || executor == null) {
Log.e(TAG, "registerApCallback called with a null callback");
return false;
}
try {
IApInterfaceEventCallback wificondCallback = new ApInterfaceEventCallback(executor,
callback);
mApInterfaceListeners.put(ifaceName, wificondCallback);
boolean success = iface.registerCallback(wificondCallback);
if (!success) {
Log.e(TAG, "Failed to register ap callback.");
return false;
}
} catch (RemoteException e) {
Log.e(TAG, "Exception in registering AP callback: " + e);
return false;
}
return true;
}
/**
* Send a management frame on the specified interface at the specified rate. Useful for probing
* the link with arbitrary frames.
*
* Note: The interface must have been already set up using
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
* @param ifaceName The interface on which to send the frame.
* @param frame The raw byte array of the management frame to tramit.
* @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the
* frame. Specified per IEEE 802.11.
* @param executor The Executor on which to execute the callbacks.
* @param callback A {@link SendMgmtFrameCallback} callback for results of the operation.
*/
public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, int mcs,
@NonNull @CallbackExecutor Executor executor,
@NonNull SendMgmtFrameCallback callback) {
if (callback == null || executor == null) {
Log.e(TAG, "callback cannot be null!");
return;
}
if (frame == null) {
Log.e(TAG, "frame cannot be null!");
executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
return;
}
// TODO (b/112029045) validate mcs
IClientInterface clientInterface = getClientInterface(ifaceName);
if (clientInterface == null) {
Log.e(TAG, "No valid wificond client interface handler");
executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
return;
}
if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) {
Log.e(TAG, "An existing management frame transmission is in progress!");
executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED));
return;
}
SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(executor, callback);
try {
clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs);
} catch (RemoteException e) {
Log.e(TAG, "Exception while starting link probe: " + e);
// Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that
// sendMgmtFrameEvent can clean up internal state, such as cancelling the timer.
sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
}
}
/**
* Clear all internal handles.
*/
private void clearState() {
// Refresh handlers
mClientInterfaces.clear();
mWificondScanners.clear();
mPnoScanEventHandlers.clear();
mScanEventHandlers.clear();
mApInterfaces.clear();
mApInterfaceListeners.clear();
mSendMgmtFrameInProgress.set(false);
}
/**
* OEM parsed security type
*/
public static class OemSecurityType {
/** The protocol defined in {@link android.net.wifi.WifiAnnotations.Protocol}. */
public final @WifiAnnotations.Protocol int protocol;
/**
* Supported key management types defined
* in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
*/
@NonNull public final List<Integer> keyManagement;
/**
* Supported pairwise cipher types defined
* in {@link android.net.wifi.WifiAnnotations.Cipher}.
*/
@NonNull public final List<Integer> pairwiseCipher;
/** The group cipher type defined in {@link android.net.wifi.WifiAnnotations.Cipher}. */
public final @WifiAnnotations.Cipher int groupCipher;
/**
* Default constructor for OemSecurityType
*
* @param protocol The protocol defined in
* {@link android.net.wifi.WifiAnnotations.Protocol}.
* @param keyManagement Supported key management types defined
* in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
* @param pairwiseCipher Supported pairwise cipher types defined
* in {@link android.net.wifi.WifiAnnotations.Cipher}.
* @param groupCipher The group cipher type defined
* in {@link android.net.wifi.WifiAnnotations.Cipher}.
*/
public OemSecurityType(
@WifiAnnotations.Protocol int protocol,
@NonNull List<Integer> keyManagement,
@NonNull List<Integer> pairwiseCipher,
@WifiAnnotations.Cipher int groupCipher) {
this.protocol = protocol;
this.keyManagement = (keyManagement != null)
? keyManagement : new ArrayList<Integer>();
this.pairwiseCipher = (pairwiseCipher != null)
? pairwiseCipher : new ArrayList<Integer>();
this.groupCipher = groupCipher;
}
}
/**
* OEM information element parser for security types not parsed by the framework.
*
* The OEM method should use the method inputs {@code id}, {@code idExt}, and {@code bytes}
* to perform the parsing. The method should place the results in an OemSecurityType objct.
*
* @param id The information element id.
* @param idExt The information element extension id. This is valid only when id is
* the extension id, {@code 255}.
* @param bytes The raw bytes of information element data, 'Element ID' and 'Length' are
* stripped off already.
* @return an OemSecurityType object if this IE is parsed successfully, null otherwise.
*/
@Nullable public static OemSecurityType parseOemSecurityTypeElement(
int id,
int idExt,
@NonNull byte[] bytes) {
return null;
}
}