| /* |
| * 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.wificond; |
| |
| 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.Collections; |
| 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; |
| import java.util.stream.Collectors; |
| |
| /** |
| * This class encapsulates the interface the wificond (Wi-Fi Conductor) daemon presents to the |
| * Wi-Fi framework. The interface is only for use by the Wi-Fi framework and access is protected |
| * by SELinux permissions: only the system server and wpa_supplicant can use WifiCondManager. |
| * |
| * @hide |
| */ |
| @SystemApi |
| @SystemService(Context.WIFI_COND_SERVICE) |
| public class WifiCondManager { |
| private static final String TAG = "WifiCondManager"; |
| 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 WifiCondManager(Context context) { |
| mAlarmManager = context.getSystemService(AlarmManager.class); |
| mEventHandler = new Handler(context.getMainLooper()); |
| } |
| |
| /** @hide */ |
| @VisibleForTesting |
| public WifiCondManager(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.macAddress + " 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 WifiCondManager 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 WifiCondManager 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_COND_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 List of valid frequencies (MHz), or an empty list for error. |
| * @throws IllegalArgumentException if band is not recognized. |
| */ |
| public @NonNull List<Integer> getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { |
| if (mWificond == null) { |
| Log.e(TAG, "No valid wificond scanner interface handler"); |
| return Collections.emptyList(); |
| } |
| 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) { |
| return Collections.emptyList(); |
| } |
| return Arrays.stream(result).boxed().collect(Collectors.toList()); |
| } |
| |
| /** 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; |
| } |
| } |