blob: fd90c35df31e7c54426186cc4cf0a423d0a83c34 [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 com.android.server.wifi;
import android.annotation.NonNull;
import android.hardware.wifi.V1_0.IWifiApIface;
import android.hardware.wifi.V1_0.IWifiChip;
import android.hardware.wifi.V1_0.IWifiChipEventCallback;
import android.hardware.wifi.V1_0.IWifiIface;
import android.hardware.wifi.V1_0.IWifiStaIface;
import android.hardware.wifi.V1_0.IWifiStaIfaceEventCallback;
import android.hardware.wifi.V1_0.IfaceType;
import android.hardware.wifi.V1_0.StaBackgroundScanBucketEventReportSchemeMask;
import android.hardware.wifi.V1_0.StaBackgroundScanBucketParameters;
import android.hardware.wifi.V1_0.StaBackgroundScanParameters;
import android.hardware.wifi.V1_0.StaLinkLayerIfaceStats;
import android.hardware.wifi.V1_0.StaLinkLayerRadioStats;
import android.hardware.wifi.V1_0.StaLinkLayerStats;
import android.hardware.wifi.V1_0.StaRoamingConfig;
import android.hardware.wifi.V1_0.StaRoamingState;
import android.hardware.wifi.V1_0.StaScanData;
import android.hardware.wifi.V1_0.StaScanDataFlagMask;
import android.hardware.wifi.V1_0.StaScanResult;
import android.hardware.wifi.V1_0.WifiBand;
import android.hardware.wifi.V1_0.WifiDebugHostWakeReasonStats;
import android.hardware.wifi.V1_0.WifiDebugPacketFateFrameType;
import android.hardware.wifi.V1_0.WifiDebugRingBufferFlags;
import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
import android.hardware.wifi.V1_0.WifiDebugRxPacketFate;
import android.hardware.wifi.V1_0.WifiDebugRxPacketFateReport;
import android.hardware.wifi.V1_0.WifiDebugTxPacketFate;
import android.hardware.wifi.V1_0.WifiDebugTxPacketFateReport;
import android.hardware.wifi.V1_0.WifiInformationElement;
import android.hardware.wifi.V1_0.WifiStatus;
import android.hardware.wifi.V1_0.WifiStatusCode;
import android.hardware.wifi.V1_2.IWifiChipEventCallback.IfaceInfo;
import android.net.MacAddress;
import android.net.apf.ApfCapabilities;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiSsid;
import android.os.Handler;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.util.MutableBoolean;
import android.util.MutableLong;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
import com.android.server.wifi.WifiLinkLayerStats.ChannelStats;
import com.android.server.wifi.util.ArrayUtils;
import com.android.server.wifi.util.BitMask;
import com.android.server.wifi.util.NativeUtil;
import com.google.errorprone.annotations.CompileTimeConstant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Vendor HAL via HIDL
*/
public class WifiVendorHal {
private static final WifiLog sNoLog = new FakeWifiLog();
/**
* Chatty logging should use mVerboseLog
*/
@VisibleForTesting
WifiLog mVerboseLog = sNoLog;
/**
* Errors should use mLog
*/
@VisibleForTesting
WifiLog mLog = new LogcatLog("WifiVendorHal");
/**
* Enables or disables verbose logging
*
* @param verbose - with the obvious interpretation
*/
public void enableVerboseLogging(boolean verbose) {
synchronized (sLock) {
if (verbose) {
mVerboseLog = mLog;
enter("verbose=true").flush();
} else {
enter("verbose=false").flush();
mVerboseLog = sNoLog;
}
}
}
/**
* Checks for a successful status result.
*
* Failures are logged to mLog.
*
* @param status is the WifiStatus generated by a hal call
* @return true for success, false for failure
*/
private boolean ok(WifiStatus status) {
if (status.code == WifiStatusCode.SUCCESS) return true;
Thread cur = Thread.currentThread();
StackTraceElement[] trace = cur.getStackTrace();
mLog.err("% failed %")
.c(niceMethodName(trace, 3))
.c(status.toString())
.flush();
return false;
}
/**
* Logs the argument along with the method name.
*
* Always returns its argument.
*/
private boolean boolResult(boolean result) {
if (mVerboseLog == sNoLog) return result;
// Currently only seen if verbose logging is on
Thread cur = Thread.currentThread();
StackTraceElement[] trace = cur.getStackTrace();
mVerboseLog.err("% returns %")
.c(niceMethodName(trace, 3))
.c(result)
.flush();
return result;
}
/**
* Logs the argument along with the method name.
*
* Always returns its argument.
*/
private String stringResult(String result) {
if (mVerboseLog == sNoLog) return result;
// Currently only seen if verbose logging is on
Thread cur = Thread.currentThread();
StackTraceElement[] trace = cur.getStackTrace();
mVerboseLog.err("% returns %")
.c(niceMethodName(trace, 3))
.c(result)
.flush();
return result;
}
/**
* Logs the argument along with the method name.
*
* Always returns its argument.
*/
private byte[] byteArrayResult(byte[] result) {
if (mVerboseLog == sNoLog) return result;
// Currently only seen if verbose logging is on
Thread cur = Thread.currentThread();
StackTraceElement[] trace = cur.getStackTrace();
mVerboseLog.err("% returns %")
.c(niceMethodName(trace, 3))
.c(result == null ? "(null)" : HexDump.dumpHexString(result))
.flush();
return result;
}
/**
* Logs at method entry
*
* @param format string with % placeholders
* @return LogMessage formatter (remember to .flush())
*/
private WifiLog.LogMessage enter(@CompileTimeConstant final String format) {
if (mVerboseLog == sNoLog) return sNoLog.info(format);
return mVerboseLog.trace(format, 1);
}
/**
* Gets the method name and line number from a stack trace.
*
* Attempts to skip frames created by lambdas to get a human-sensible name.
*
* @param trace, fo example obtained by Thread.currentThread().getStackTrace()
* @param start frame number to log, typically 3
* @return string containing the method name and line number
*/
private static String niceMethodName(StackTraceElement[] trace, int start) {
if (start >= trace.length) return "";
StackTraceElement s = trace[start];
String name = s.getMethodName();
if (name.contains("lambda$")) {
// Try to find a friendlier method name
String myFile = s.getFileName();
if (myFile != null) {
for (int i = start + 1; i < trace.length; i++) {
if (myFile.equals(trace[i].getFileName())) {
name = trace[i].getMethodName();
break;
}
}
}
}
return (name + "(l." + s.getLineNumber() + ")");
}
// Vendor HAL HIDL interface objects.
private IWifiChip mIWifiChip;
private HashMap<String, IWifiStaIface> mIWifiStaIfaces = new HashMap<>();
private HashMap<String, IWifiApIface> mIWifiApIfaces = new HashMap<>();
private HalDeviceManager.InterfaceAvailableForRequestListener
mStaIfaceAvailableForRequestListener;
private HalDeviceManager.InterfaceAvailableForRequestListener
mApIfaceAvailableForRequestListener;
private final HalDeviceManager mHalDeviceManager;
private final HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
private final IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
private final ChipEventCallback mIWifiChipEventCallback;
private final ChipEventCallbackV12 mIWifiChipEventCallbackV12;
private final ChipEventCallbackV14 mIWifiChipEventCallbackV14;
// Plumbing for event handling.
//
// Being final fields, they can be accessed without synchronization under
// some reasonable assumptions. See
// https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
private final Handler mHalEventHandler;
public WifiVendorHal(HalDeviceManager halDeviceManager, Handler handler) {
mHalDeviceManager = halDeviceManager;
mHalEventHandler = handler;
mHalDeviceManagerStatusCallbacks = new HalDeviceManagerStatusListener();
mIWifiStaIfaceEventCallback = new StaIfaceEventCallback();
mIWifiChipEventCallback = new ChipEventCallback();
mIWifiChipEventCallbackV12 = new ChipEventCallbackV12();
mIWifiChipEventCallbackV14 = new ChipEventCallbackV14();
}
public static final Object sLock = new Object();
private void handleRemoteException(RemoteException e) {
String methodName = niceMethodName(Thread.currentThread().getStackTrace(), 3);
mVerboseLog.err("% RemoteException in HIDL call %").c(methodName).c(e.toString()).flush();
clearState();
}
private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
/**
* Initialize the Hal device manager and register for status callbacks.
*
* @param handler Handler to notify if the vendor HAL dies.
* @return true on success, false otherwise.
*/
public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
synchronized (sLock) {
mHalDeviceManager.initialize();
mHalDeviceManager.registerStatusListener(
mHalDeviceManagerStatusCallbacks, mHalEventHandler);
mDeathEventHandler = handler;
return true;
}
}
private WifiNative.VendorHalRadioModeChangeEventHandler mRadioModeChangeEventHandler;
/**
* Register to listen for radio mode change events from the HAL.
*
* @param handler Handler to notify when the vendor HAL detects a radio mode change.
*/
public void registerRadioModeChangeHandler(
WifiNative.VendorHalRadioModeChangeEventHandler handler) {
synchronized (sLock) {
mRadioModeChangeEventHandler = handler;
}
}
/**
* Returns whether the vendor HAL is supported on this device or not.
*/
public boolean isVendorHalSupported() {
synchronized (sLock) {
return mHalDeviceManager.isSupported();
}
}
/**
* Bring up the HIDL Vendor HAL and configure for AP (Access Point) mode
*
* @return true for success
*/
public boolean startVendorHalAp() {
synchronized (sLock) {
if (!startVendorHal()) {
return false;
}
if (TextUtils.isEmpty(createApIface(null))) {
stopVendorHal();
return false;
}
return true;
}
}
/**
* Bring up the HIDL Vendor HAL and configure for STA (Station) mode
*
* @return true for success
*/
public boolean startVendorHalSta() {
synchronized (sLock) {
if (!startVendorHal()) {
return false;
}
if (TextUtils.isEmpty(createStaIface(null))) {
stopVendorHal();
return false;
}
return true;
}
}
/**
* Bring up the HIDL Vendor HAL.
* @return true on success, false otherwise.
*/
public boolean startVendorHal() {
synchronized (sLock) {
if (!mHalDeviceManager.start()) {
mLog.err("Failed to start vendor HAL").flush();
return false;
}
mLog.info("Vendor Hal started successfully").flush();
return true;
}
}
/**
* Register a STA iface availability listener listed with {@link HalDeviceManager}.
*
* @param listener Instance of {@link WifiNative.InterfaceAvailableForRequestListener}.
*/
public void registerStaIfaceAvailabilityListener(
@NonNull WifiNative.InterfaceAvailableForRequestListener listener) {
synchronized (sLock) {
Preconditions.checkState(mStaIfaceAvailableForRequestListener == null);
mStaIfaceAvailableForRequestListener =
(isAvailable) -> listener.onAvailabilityChanged(isAvailable);
if (mHalDeviceManager.isStarted()) {
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
IfaceType.STA, mStaIfaceAvailableForRequestListener,
mHalEventHandler);
}
}
}
/**
* Register a AP iface availability listener listed with {@link HalDeviceManager}.
*
* @param listener Instance of {@link WifiNative.InterfaceAvailableForRequestListener}.
*
*/
public void registerApIfaceAvailabilityListener(
@NonNull WifiNative.InterfaceAvailableForRequestListener listener) {
synchronized (sLock) {
Preconditions.checkState(mApIfaceAvailableForRequestListener == null);
mApIfaceAvailableForRequestListener =
(isAvailable) -> listener.onAvailabilityChanged(isAvailable);
if (mHalDeviceManager.isStarted()) {
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
IfaceType.AP, mApIfaceAvailableForRequestListener,
mHalEventHandler);
}
}
}
/** Helper method to lookup the corresponding STA iface object using iface name. */
private IWifiStaIface getStaIface(@NonNull String ifaceName) {
synchronized (sLock) {
return mIWifiStaIfaces.get(ifaceName);
}
}
private class StaInterfaceDestroyedListenerInternal implements InterfaceDestroyedListener {
private final InterfaceDestroyedListener mExternalListener;
StaInterfaceDestroyedListenerInternal(InterfaceDestroyedListener externalListener) {
mExternalListener = externalListener;
}
@Override
public void onDestroyed(@NonNull String ifaceName) {
synchronized (sLock) {
mIWifiStaIfaces.remove(ifaceName);
}
if (mExternalListener != null) {
mExternalListener.onDestroyed(ifaceName);
}
}
}
/**
* Create a STA iface using {@link HalDeviceManager}.
*
* @param destroyedListener Listener to be invoked when the interface is destroyed.
* @return iface name on success, null otherwise.
*/
public String createStaIface(InterfaceDestroyedListener destroyedListener) {
synchronized (sLock) {
IWifiStaIface iface = mHalDeviceManager.createStaIface(
new StaInterfaceDestroyedListenerInternal(destroyedListener), null);
if (iface == null) {
mLog.err("Failed to create STA iface").flush();
return stringResult(null);
}
String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
if (TextUtils.isEmpty(ifaceName)) {
mLog.err("Failed to get iface name").flush();
return stringResult(null);
}
if (!registerStaIfaceCallback(iface)) {
mLog.err("Failed to register STA iface callback").flush();
return stringResult(null);
}
if (!retrieveWifiChip((IWifiIface) iface)) {
mLog.err("Failed to get wifi chip").flush();
return stringResult(null);
}
enableLinkLayerStats(iface);
mIWifiStaIfaces.put(ifaceName, iface);
return ifaceName;
}
}
/**
* Remove a STA iface using {@link HalDeviceManager}.
*
* @param ifaceName Name of the interface being removed.
* @return true on success, false otherwise.
*/
public boolean removeStaIface(@NonNull String ifaceName) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
if (!mHalDeviceManager.removeIface((IWifiIface) iface)) {
mLog.err("Failed to remove STA iface").flush();
return boolResult(false);
}
mIWifiStaIfaces.remove(ifaceName);
return true;
}
}
/** Helper method to lookup the corresponding AP iface object using iface name. */
private IWifiApIface getApIface(@NonNull String ifaceName) {
synchronized (sLock) {
return mIWifiApIfaces.get(ifaceName);
}
}
private class ApInterfaceDestroyedListenerInternal implements InterfaceDestroyedListener {
private final InterfaceDestroyedListener mExternalListener;
ApInterfaceDestroyedListenerInternal(InterfaceDestroyedListener externalListener) {
mExternalListener = externalListener;
}
@Override
public void onDestroyed(@NonNull String ifaceName) {
synchronized (sLock) {
mIWifiApIfaces.remove(ifaceName);
}
if (mExternalListener != null) {
mExternalListener.onDestroyed(ifaceName);
}
}
}
/**
* Create a AP iface using {@link HalDeviceManager}.
*
* @param destroyedListener Listener to be invoked when the interface is destroyed.
* @return iface name on success, null otherwise.
*/
public String createApIface(InterfaceDestroyedListener destroyedListener) {
synchronized (sLock) {
IWifiApIface iface = mHalDeviceManager.createApIface(
new ApInterfaceDestroyedListenerInternal(destroyedListener), null);
if (iface == null) {
mLog.err("Failed to create AP iface").flush();
return stringResult(null);
}
String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
if (TextUtils.isEmpty(ifaceName)) {
mLog.err("Failed to get iface name").flush();
return stringResult(null);
}
if (!retrieveWifiChip((IWifiIface) iface)) {
mLog.err("Failed to get wifi chip").flush();
return stringResult(null);
}
mIWifiApIfaces.put(ifaceName, iface);
return ifaceName;
}
}
/**
* Remove an AP iface using {@link HalDeviceManager}.
*
* @param ifaceName Name of the interface being removed.
* @return true on success, false otherwise.
*/
public boolean removeApIface(@NonNull String ifaceName) {
synchronized (sLock) {
IWifiApIface iface = getApIface(ifaceName);
if (iface == null) return boolResult(false);
if (!mHalDeviceManager.removeIface((IWifiIface) iface)) {
mLog.err("Failed to remove AP iface").flush();
return boolResult(false);
}
mIWifiApIfaces.remove(ifaceName);
return true;
}
}
private boolean retrieveWifiChip(IWifiIface iface) {
synchronized (sLock) {
boolean registrationNeeded = mIWifiChip == null;
mIWifiChip = mHalDeviceManager.getChip(iface);
if (mIWifiChip == null) {
mLog.err("Failed to get the chip created for the Iface").flush();
return false;
}
if (!registrationNeeded) {
return true;
}
if (!registerChipCallback()) {
mLog.err("Failed to register chip callback").flush();
mIWifiChip = null;
return false;
}
return true;
}
}
/**
* Registers the sta iface callback.
*/
private boolean registerStaIfaceCallback(IWifiStaIface iface) {
synchronized (sLock) {
if (iface == null) return boolResult(false);
if (mIWifiStaIfaceEventCallback == null) return boolResult(false);
try {
WifiStatus status =
iface.registerEventCallback(mIWifiStaIfaceEventCallback);
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Registers the sta iface callback.
*/
private boolean registerChipCallback() {
synchronized (sLock) {
if (mIWifiChip == null) return boolResult(false);
try {
WifiStatus status;
android.hardware.wifi.V1_4.IWifiChip iWifiChipV14 = getWifiChipForV1_4Mockable();
android.hardware.wifi.V1_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable();
if (iWifiChipV14 != null) {
status = iWifiChipV14.registerEventCallback_1_4(mIWifiChipEventCallbackV14);
} else if (iWifiChipV12 != null) {
status = iWifiChipV12.registerEventCallback_1_2(mIWifiChipEventCallbackV12);
} else {
status = mIWifiChip.registerEventCallback(mIWifiChipEventCallback);
}
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Stops the HAL
*/
public void stopVendorHal() {
synchronized (sLock) {
mHalDeviceManager.stop();
clearState();
mLog.info("Vendor Hal stopped").flush();
}
}
/**
* Clears the state associated with a started Iface
*
* Caller should hold the lock.
*/
private void clearState() {
mIWifiChip = null;
mIWifiStaIfaces.clear();
mIWifiApIfaces.clear();
mDriverDescription = null;
mFirmwareDescription = null;
}
/**
* Tests whether the HAL is started and atleast one iface is up.
*/
public boolean isHalStarted() {
// For external use only. Methods in this class should test for null directly.
synchronized (sLock) {
return (!mIWifiStaIfaces.isEmpty() || !mIWifiApIfaces.isEmpty());
}
}
/**
* Gets the scan capabilities
*
* @param ifaceName Name of the interface.
* @param capabilities object to be filled in
* @return true for success, false for failure
*/
public boolean getBgScanCapabilities(
@NonNull String ifaceName, WifiNative.ScanCapabilities capabilities) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
MutableBoolean ans = new MutableBoolean(false);
WifiNative.ScanCapabilities out = capabilities;
iface.getBackgroundScanCapabilities((status, cap) -> {
if (!ok(status)) return;
mVerboseLog.info("scan capabilities %").c(cap.toString()).flush();
out.max_scan_cache_size = cap.maxCacheSize;
out.max_ap_cache_per_scan = cap.maxApCachePerScan;
out.max_scan_buckets = cap.maxBuckets;
out.max_rssi_sample_size = 0;
out.max_scan_reporting_threshold = cap.maxReportingThreshold;
ans.value = true;
}
);
return ans.value;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Holds the current background scan state, to implement pause and restart
*/
@VisibleForTesting
class CurrentBackgroundScan {
public int cmdId;
public StaBackgroundScanParameters param;
public WifiNative.ScanEventHandler eventHandler = null;
public boolean paused = false;
public WifiScanner.ScanData[] latestScanResults = null;
CurrentBackgroundScan(int id, WifiNative.ScanSettings settings) {
cmdId = id;
param = new StaBackgroundScanParameters();
param.basePeriodInMs = settings.base_period_ms;
param.maxApPerScan = settings.max_ap_per_scan;
param.reportThresholdPercent = settings.report_threshold_percent;
param.reportThresholdNumScans = settings.report_threshold_num_scans;
if (settings.buckets != null) {
for (WifiNative.BucketSettings bs : settings.buckets) {
param.buckets.add(makeStaBackgroundScanBucketParametersFromBucketSettings(bs));
}
}
}
}
/**
* Makes the Hal flavor of WifiNative.BucketSettings
*
* @param bs WifiNative.BucketSettings
* @return Hal flavor of bs
* @throws IllegalArgumentException if band value is not recognized
*/
private StaBackgroundScanBucketParameters
makeStaBackgroundScanBucketParametersFromBucketSettings(WifiNative.BucketSettings bs) {
StaBackgroundScanBucketParameters pa = new StaBackgroundScanBucketParameters();
pa.bucketIdx = bs.bucket;
pa.band = makeWifiBandFromFrameworkBand(bs.band);
if (bs.channels != null) {
for (WifiNative.ChannelSettings cs : bs.channels) {
pa.frequencies.add(cs.frequency);
}
}
pa.periodInMs = bs.period_ms;
pa.eventReportScheme = makeReportSchemeFromBucketSettingsReportEvents(bs.report_events);
pa.exponentialMaxPeriodInMs = bs.max_period_ms;
// Although HAL API allows configurable base value for the truncated
// exponential back off scan. Native API and above support only
// truncated binary exponential back off scan.
// Hard code value of base to 2 here.
pa.exponentialBase = 2;
pa.exponentialStepCount = bs.step_count;
return pa;
}
/**
* Makes the Hal flavor of WifiScanner's band indication
*
* Note: This method is only used by background scan which does not
* support 6GHz, hence band combinations including 6GHz are considered invalid
*
* @param frameworkBand one of WifiScanner.WIFI_BAND_*
* @return A WifiBand value
* @throws IllegalArgumentException if frameworkBand is not recognized
*/
private int makeWifiBandFromFrameworkBand(int frameworkBand) {
switch (frameworkBand) {
case WifiScanner.WIFI_BAND_UNSPECIFIED:
return WifiBand.BAND_UNSPECIFIED;
case WifiScanner.WIFI_BAND_24_GHZ:
return WifiBand.BAND_24GHZ;
case WifiScanner.WIFI_BAND_5_GHZ:
return WifiBand.BAND_5GHZ;
case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
return WifiBand.BAND_5GHZ_DFS;
case WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS:
return WifiBand.BAND_5GHZ_WITH_DFS;
case WifiScanner.WIFI_BAND_BOTH:
return WifiBand.BAND_24GHZ_5GHZ;
case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS;
default:
throw new IllegalArgumentException("bad band " + frameworkBand);
}
}
/**
* Makes the Hal flavor of WifiScanner's report event mask
*
* @param reportUnderscoreEvents is logical OR of WifiScanner.REPORT_EVENT_* values
* @return Corresponding StaBackgroundScanBucketEventReportSchemeMask value
* @throws IllegalArgumentException if a mask bit is not recognized
*/
private int makeReportSchemeFromBucketSettingsReportEvents(int reportUnderscoreEvents) {
int ans = 0;
BitMask in = new BitMask(reportUnderscoreEvents);
if (in.testAndClear(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN)) {
ans |= StaBackgroundScanBucketEventReportSchemeMask.EACH_SCAN;
}
if (in.testAndClear(WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)) {
ans |= StaBackgroundScanBucketEventReportSchemeMask.FULL_RESULTS;
}
if (in.testAndClear(WifiScanner.REPORT_EVENT_NO_BATCH)) {
ans |= StaBackgroundScanBucketEventReportSchemeMask.NO_BATCH;
}
if (in.value != 0) throw new IllegalArgumentException("bad " + reportUnderscoreEvents);
return ans;
}
private int mLastScanCmdId; // For assigning cmdIds to scans
@VisibleForTesting
CurrentBackgroundScan mScan = null;
/**
* Starts a background scan
*
* Any ongoing scan will be stopped first
*
* @param ifaceName Name of the interface.
* @param settings to control the scan
* @param eventHandler to call with the results
* @return true for success
*/
public boolean startBgScan(@NonNull String ifaceName,
WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
WifiStatus status;
if (eventHandler == null) return boolResult(false);
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
if (mScan != null && !mScan.paused) {
ok(iface.stopBackgroundScan(mScan.cmdId));
mScan = null;
}
mLastScanCmdId = (mLastScanCmdId % 9) + 1; // cycle through non-zero single digits
CurrentBackgroundScan scan = new CurrentBackgroundScan(mLastScanCmdId, settings);
status = iface.startBackgroundScan(scan.cmdId, scan.param);
if (!ok(status)) return false;
scan.eventHandler = eventHandler;
mScan = scan;
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Stops any ongoing backgound scan
*
* @param ifaceName Name of the interface.
*/
public void stopBgScan(@NonNull String ifaceName) {
WifiStatus status;
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return;
try {
if (mScan != null) {
ok(iface.stopBackgroundScan(mScan.cmdId));
mScan = null;
}
} catch (RemoteException e) {
handleRemoteException(e);
}
}
}
/**
* Pauses an ongoing backgound scan
*
* @param ifaceName Name of the interface.
*/
public void pauseBgScan(@NonNull String ifaceName) {
WifiStatus status;
synchronized (sLock) {
try {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return;
if (mScan != null && !mScan.paused) {
status = iface.stopBackgroundScan(mScan.cmdId);
if (!ok(status)) return;
mScan.paused = true;
}
} catch (RemoteException e) {
handleRemoteException(e);
}
}
}
/**
* Restarts a paused background scan
*
* @param ifaceName Name of the interface.
*/
public void restartBgScan(@NonNull String ifaceName) {
WifiStatus status;
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return;
try {
if (mScan != null && mScan.paused) {
status = iface.startBackgroundScan(mScan.cmdId, mScan.param);
if (!ok(status)) return;
mScan.paused = false;
}
} catch (RemoteException e) {
handleRemoteException(e);
}
}
}
/**
* Gets the latest scan results received from the HIDL interface callback.
* TODO(b/35754840): This hop to fetch scan results after callback is unnecessary. Refactor
* WifiScanner to use the scan results from the callback.
*
* @param ifaceName Name of the interface.
*/
public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return null;
if (mScan == null) return null;
return mScan.latestScanResults;
}
}
/**
* Get the link layer statistics
*
* Note - we always enable link layer stats on a STA interface.
*
* @param ifaceName Name of the interface.
* @return the statistics, or null if unable to do so
*/
public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) {
if (getWifiStaIfaceForV1_3Mockable(ifaceName) != null) {
return getWifiLinkLayerStats_1_3_Internal(ifaceName);
}
return getWifiLinkLayerStats_internal(ifaceName);
}
private WifiLinkLayerStats getWifiLinkLayerStats_internal(@NonNull String ifaceName) {
class AnswerBox {
public StaLinkLayerStats value = null;
}
AnswerBox answer = new AnswerBox();
synchronized (sLock) {
try {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return null;
iface.getLinkLayerStats((status, stats) -> {
if (!ok(status)) return;
answer.value = stats;
});
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats(answer.value);
return stats;
}
private WifiLinkLayerStats getWifiLinkLayerStats_1_3_Internal(@NonNull String ifaceName) {
class AnswerBox {
public android.hardware.wifi.V1_3.StaLinkLayerStats value = null;
}
AnswerBox answer = new AnswerBox();
synchronized (sLock) {
try {
android.hardware.wifi.V1_3.IWifiStaIface iface =
getWifiStaIfaceForV1_3Mockable(ifaceName);
if (iface == null) return null;
iface.getLinkLayerStats_1_3((status, stats) -> {
if (!ok(status)) return;
answer.value = stats;
});
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats_1_3(answer.value);
return stats;
}
/**
* Makes the framework version of link layer stats from the hal version.
*/
@VisibleForTesting
static WifiLinkLayerStats frameworkFromHalLinkLayerStats(StaLinkLayerStats stats) {
if (stats == null) return null;
WifiLinkLayerStats out = new WifiLinkLayerStats();
setIfaceStats(out, stats.iface);
setRadioStats(out, stats.radios);
setTimeStamp(out, stats.timeStampInMs);
out.version = WifiLinkLayerStats.V1_0;
return out;
}
/**
* Makes the framework version of link layer stats from the hal version.
*/
@VisibleForTesting
static WifiLinkLayerStats frameworkFromHalLinkLayerStats_1_3(
android.hardware.wifi.V1_3.StaLinkLayerStats stats) {
if (stats == null) return null;
WifiLinkLayerStats out = new WifiLinkLayerStats();
setIfaceStats(out, stats.iface);
setRadioStats_1_3(out, stats.radios);
setTimeStamp(out, stats.timeStampInMs);
out.version = WifiLinkLayerStats.V1_3;
return out;
}
private static void setIfaceStats(WifiLinkLayerStats stats, StaLinkLayerIfaceStats iface) {
if (iface == null) return;
stats.beacon_rx = iface.beaconRx;
stats.rssi_mgmt = iface.avgRssiMgmt;
// Statistics are broken out by Wireless Multimedia Extensions categories
// WME Best Effort Access Category
stats.rxmpdu_be = iface.wmeBePktStats.rxMpdu;
stats.txmpdu_be = iface.wmeBePktStats.txMpdu;
stats.lostmpdu_be = iface.wmeBePktStats.lostMpdu;
stats.retries_be = iface.wmeBePktStats.retries;
// WME Background Access Category
stats.rxmpdu_bk = iface.wmeBkPktStats.rxMpdu;
stats.txmpdu_bk = iface.wmeBkPktStats.txMpdu;
stats.lostmpdu_bk = iface.wmeBkPktStats.lostMpdu;
stats.retries_bk = iface.wmeBkPktStats.retries;
// WME Video Access Category
stats.rxmpdu_vi = iface.wmeViPktStats.rxMpdu;
stats.txmpdu_vi = iface.wmeViPktStats.txMpdu;
stats.lostmpdu_vi = iface.wmeViPktStats.lostMpdu;
stats.retries_vi = iface.wmeViPktStats.retries;
// WME Voice Access Category
stats.rxmpdu_vo = iface.wmeVoPktStats.rxMpdu;
stats.txmpdu_vo = iface.wmeVoPktStats.txMpdu;
stats.lostmpdu_vo = iface.wmeVoPktStats.lostMpdu;
stats.retries_vo = iface.wmeVoPktStats.retries;
}
private static void setRadioStats(WifiLinkLayerStats stats,
List<StaLinkLayerRadioStats> radios) {
if (radios == null) return;
// NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
if (radios.size() > 0) {
StaLinkLayerRadioStats radioStats = radios.get(0);
stats.on_time = radioStats.onTimeInMs;
stats.tx_time = radioStats.txTimeInMs;
stats.tx_time_per_level = new int[radioStats.txTimeInMsPerLevel.size()];
for (int i = 0; i < stats.tx_time_per_level.length; i++) {
stats.tx_time_per_level[i] = radioStats.txTimeInMsPerLevel.get(i);
}
stats.rx_time = radioStats.rxTimeInMs;
stats.on_time_scan = radioStats.onTimeInMsForScan;
}
}
private static void setRadioStats_1_3(WifiLinkLayerStats stats,
List<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> radios) {
if (radios == null) return;
// NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
if (radios.size() > 0) {
android.hardware.wifi.V1_3.StaLinkLayerRadioStats radioStats = radios.get(0);
stats.on_time = radioStats.V1_0.onTimeInMs;
stats.tx_time = radioStats.V1_0.txTimeInMs;
stats.tx_time_per_level = new int[radioStats.V1_0.txTimeInMsPerLevel.size()];
for (int i = 0; i < stats.tx_time_per_level.length; i++) {
stats.tx_time_per_level[i] = radioStats.V1_0.txTimeInMsPerLevel.get(i);
}
stats.rx_time = radioStats.V1_0.rxTimeInMs;
stats.on_time_scan = radioStats.V1_0.onTimeInMsForScan;
stats.on_time_nan_scan = radioStats.onTimeInMsForNanScan;
stats.on_time_background_scan = radioStats.onTimeInMsForBgScan;
stats.on_time_roam_scan = radioStats.onTimeInMsForRoamScan;
stats.on_time_pno_scan = radioStats.onTimeInMsForPnoScan;
stats.on_time_hs20_scan = radioStats.onTimeInMsForHs20Scan;
/* Copy list of channel stats */
for (int i = 0; i < radioStats.channelStats.size(); i++) {
android.hardware.wifi.V1_3.WifiChannelStats channelStats =
radioStats.channelStats.get(i);
ChannelStats channelStatsEntry = new ChannelStats();
channelStatsEntry.frequency = channelStats.channel.centerFreq;
channelStatsEntry.radioOnTimeMs = channelStats.onTimeInMs;
channelStatsEntry.ccaBusyTimeMs = channelStats.ccaBusyTimeInMs;
stats.channelStatsMap.put(channelStats.channel.centerFreq, channelStatsEntry);
}
}
}
private static void setTimeStamp(WifiLinkLayerStats stats, long timeStampInMs) {
stats.timeStampInMs = timeStampInMs;
}
@VisibleForTesting
boolean mLinkLayerStatsDebug = false; // Passed to Hal
/**
* Enables the linkLayerStats in the Hal.
*
* This is called unconditionally whenever we create a STA interface.
*
* @param iface Iface object.
*/
private void enableLinkLayerStats(IWifiStaIface iface) {
synchronized (sLock) {
try {
WifiStatus status;
status = iface.enableLinkLayerStatsCollection(mLinkLayerStatsDebug);
if (!ok(status)) {
mLog.err("unable to enable link layer stats collection").flush();
}
} catch (RemoteException e) {
handleRemoteException(e);
}
}
}
/**
* Translation table used by getSupportedFeatureSet for translating IWifiChip caps for V1.1
*/
private static final long[][] sChipFeatureCapabilityTranslation = {
{WifiManager.WIFI_FEATURE_TX_POWER_LIMIT,
android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT
},
{WifiManager.WIFI_FEATURE_D2D_RTT,
android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
},
{WifiManager.WIFI_FEATURE_D2AP_RTT,
android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT
}
};
/**
* Translation table used by getSupportedFeatureSet for translating IWifiChip caps for
* additional capabilities introduced in V1.3
*/
private static final long[][] sChipFeatureCapabilityTranslation13 = {
{WifiManager.WIFI_FEATURE_LOW_LATENCY,
android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.SET_LATENCY_MODE
},
{WifiManager.WIFI_FEATURE_P2P_RAND_MAC,
android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.P2P_RAND_MAC
}
};
/**
* Feature bit mask translation for Chip V1.1
*
* @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
* @return bitmask defined by WifiManager.WIFI_FEATURE_*
*/
@VisibleForTesting
int wifiFeatureMaskFromChipCapabilities(int capabilities) {
int features = 0;
for (int i = 0; i < sChipFeatureCapabilityTranslation.length; i++) {
if ((capabilities & sChipFeatureCapabilityTranslation[i][1]) != 0) {
features |= sChipFeatureCapabilityTranslation[i][0];
}
}
return features;
}
/**
* Feature bit mask translation for Chip V1.3
*
* @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
* @return bitmask defined by WifiManager.WIFI_FEATURE_*
*/
@VisibleForTesting
long wifiFeatureMaskFromChipCapabilities_1_3(int capabilities) {
// First collect features from previous versions
long features = wifiFeatureMaskFromChipCapabilities(capabilities);
// Next collect features for V1_3 version
for (int i = 0; i < sChipFeatureCapabilityTranslation13.length; i++) {
if ((capabilities & sChipFeatureCapabilityTranslation13[i][1]) != 0) {
features |= sChipFeatureCapabilityTranslation13[i][0];
}
}
return features;
}
/**
* Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps
*/
private static final long[][] sStaFeatureCapabilityTranslation = {
{WifiManager.WIFI_FEATURE_PASSPOINT,
IWifiStaIface.StaIfaceCapabilityMask.HOTSPOT
},
{WifiManager.WIFI_FEATURE_SCANNER,
IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN,
},
{WifiManager.WIFI_FEATURE_PNO,
IWifiStaIface.StaIfaceCapabilityMask.PNO
},
{WifiManager.WIFI_FEATURE_TDLS,
IWifiStaIface.StaIfaceCapabilityMask.TDLS
},
{WifiManager.WIFI_FEATURE_TDLS_OFFCHANNEL,
IWifiStaIface.StaIfaceCapabilityMask.TDLS_OFFCHANNEL
},
{WifiManager.WIFI_FEATURE_LINK_LAYER_STATS,
IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
},
{WifiManager.WIFI_FEATURE_RSSI_MONITOR,
IWifiStaIface.StaIfaceCapabilityMask.RSSI_MONITOR
},
{WifiManager.WIFI_FEATURE_MKEEP_ALIVE,
IWifiStaIface.StaIfaceCapabilityMask.KEEP_ALIVE
},
{WifiManager.WIFI_FEATURE_CONFIG_NDO,
IWifiStaIface.StaIfaceCapabilityMask.ND_OFFLOAD
},
{WifiManager.WIFI_FEATURE_CONTROL_ROAMING,
IWifiStaIface.StaIfaceCapabilityMask.CONTROL_ROAMING
},
{WifiManager.WIFI_FEATURE_IE_WHITELIST,
IWifiStaIface.StaIfaceCapabilityMask.PROBE_IE_WHITELIST
},
{WifiManager.WIFI_FEATURE_SCAN_RAND,
IWifiStaIface.StaIfaceCapabilityMask.SCAN_RAND
}
};
/**
* Feature bit mask translation for STAs
*
* @param capabilities bitmask defined IWifiStaIface.StaIfaceCapabilityMask
* @return bitmask defined by WifiManager.WIFI_FEATURE_*
*/
@VisibleForTesting
long wifiFeatureMaskFromStaCapabilities(int capabilities) {
long features = 0;
for (int i = 0; i < sStaFeatureCapabilityTranslation.length; i++) {
if ((capabilities & sStaFeatureCapabilityTranslation[i][1]) != 0) {
features |= sStaFeatureCapabilityTranslation[i][0];
}
}
return features;
}
/**
* Get the supported features
*
* The result may differ depending on the mode (STA or AP)
*
* @param ifaceName Name of the interface.
* @return bitmask defined by WifiManager.WIFI_FEATURE_*
*/
public long getSupportedFeatureSet(@NonNull String ifaceName) {
long featureSet = 0;
if (!mHalDeviceManager.isStarted()) {
return featureSet; // TODO: can't get capabilities with Wi-Fi down
}
try {
final MutableLong feat = new MutableLong(0);
synchronized (sLock) {
android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
if (iWifiChipV13 != null) {
iWifiChipV13.getCapabilities_1_3((status, capabilities) -> {
if (!ok(status)) return;
feat.value = wifiFeatureMaskFromChipCapabilities_1_3(capabilities);
});
} else if (mIWifiChip != null) {
mIWifiChip.getCapabilities((status, capabilities) -> {
if (!ok(status)) return;
feat.value = wifiFeatureMaskFromChipCapabilities(capabilities);
});
}
IWifiStaIface iface = getStaIface(ifaceName);
if (iface != null) {
iface.getCapabilities((status, capabilities) -> {
if (!ok(status)) return;
feat.value |= wifiFeatureMaskFromStaCapabilities(capabilities);
});
}
}
featureSet = feat.value;
} catch (RemoteException e) {
handleRemoteException(e);
return 0;
}
Set<Integer> supportedIfaceTypes = mHalDeviceManager.getSupportedIfaceTypes();
if (supportedIfaceTypes.contains(IfaceType.STA)) {
featureSet |= WifiManager.WIFI_FEATURE_INFRA;
}
if (supportedIfaceTypes.contains(IfaceType.AP)) {
featureSet |= WifiManager.WIFI_FEATURE_MOBILE_HOTSPOT;
}
if (supportedIfaceTypes.contains(IfaceType.P2P)) {
featureSet |= WifiManager.WIFI_FEATURE_P2P;
}
if (supportedIfaceTypes.contains(IfaceType.NAN)) {
featureSet |= WifiManager.WIFI_FEATURE_AWARE;
}
return featureSet;
}
/**
* Set Mac address on the given interface
*
* @param ifaceName Name of the interface
* @param mac MAC address to change into
* @return true for success
*/
public boolean setMacAddress(@NonNull String ifaceName, @NonNull MacAddress mac) {
byte[] macByteArray = mac.toByteArray();
synchronized (sLock) {
try {
android.hardware.wifi.V1_2.IWifiStaIface sta12 =
getWifiStaIfaceForV1_2Mockable(ifaceName);
if (sta12 != null) {
return ok(sta12.setMacAddress(macByteArray));
}
android.hardware.wifi.V1_4.IWifiApIface ap14 =
getWifiApIfaceForV1_4Mockable(ifaceName);
if (ap14 != null) {
return ok(ap14.setMacAddress(macByteArray));
}
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
return boolResult(false);
}
/**
* Returns true if Hal version supports setMacAddress, otherwise false.
*
* @param ifaceName Name of the interface
*/
public boolean isSetMacAddressSupported(@NonNull String ifaceName) {
synchronized (sLock) {
android.hardware.wifi.V1_2.IWifiStaIface sta12 =
getWifiStaIfaceForV1_2Mockable(ifaceName);
if (sta12 != null) {
return true;
}
android.hardware.wifi.V1_4.IWifiApIface ap14 =
getWifiApIfaceForV1_4Mockable(ifaceName);
if (ap14 != null) {
return true;
}
}
return false;
}
/**
* Get factory MAC address of the given interface
*
* @param ifaceName Name of the interface
* @return factory MAC address of the interface or null.
*/
public MacAddress getFactoryMacAddress(@NonNull String ifaceName) {
class AnswerBox {
public MacAddress mac = null;
}
synchronized (sLock) {
try {
AnswerBox box = new AnswerBox();
android.hardware.wifi.V1_3.IWifiStaIface sta13 =
getWifiStaIfaceForV1_3Mockable(ifaceName);
if (sta13 != null) {
sta13.getFactoryMacAddress((status, macBytes) -> {
if (!ok(status)) return;
box.mac = MacAddress.fromBytes(macBytes);
});
return box.mac;
}
android.hardware.wifi.V1_4.IWifiApIface ap14 =
getWifiApIfaceForV1_4Mockable(ifaceName);
if (ap14 != null) {
ap14.getFactoryMacAddress((status, macBytes) -> {
if (!ok(status)) return;
box.mac = MacAddress.fromBytes(macBytes);
});
return box.mac;
}
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
return null;
}
/**
* Get the APF (Android Packet Filter) capabilities of the device
*
* @param ifaceName Name of the interface.
* @return APF capabilities object.
*/
public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) {
class AnswerBox {
public ApfCapabilities value = sNoApfCapabilities;
}
synchronized (sLock) {
try {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return sNoApfCapabilities;
AnswerBox box = new AnswerBox();
iface.getApfPacketFilterCapabilities((status, capabilities) -> {
if (!ok(status)) return;
box.value = new ApfCapabilities(
/* apfVersionSupported */ capabilities.version,
/* maximumApfProgramSize */ capabilities.maxLength,
/* apfPacketFormat */ android.system.OsConstants.ARPHRD_ETHER);
});
return box.value;
} catch (RemoteException e) {
handleRemoteException(e);
return sNoApfCapabilities;
}
}
}
private static final ApfCapabilities sNoApfCapabilities = new ApfCapabilities(0, 0, 0);
/**
* Installs an APF program on this iface, replacing any existing program.
*
* @param ifaceName Name of the interface.
* @param filter is the android packet filter program
* @return true for success
*/
public boolean installPacketFilter(@NonNull String ifaceName, byte[] filter) {
int cmdId = 0; // We only aspire to support one program at a time
if (filter == null) return boolResult(false);
// Copy the program before taking the lock.
ArrayList<Byte> program = NativeUtil.byteArrayToArrayList(filter);
enter("filter length %").c(filter.length).flush();
synchronized (sLock) {
try {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
WifiStatus status = iface.installApfPacketFilter(cmdId, program);
if (!ok(status)) return false;
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Reads the APF program and data buffer on this iface.
*
* @param ifaceName Name of the interface
* @return the buffer returned by the driver, or null in case of an error
*/
public byte[] readPacketFilter(@NonNull String ifaceName) {
class AnswerBox {
public byte[] data = null;
}
AnswerBox answer = new AnswerBox();
enter("").flush();
// TODO: Must also take the wakelock here to prevent going to sleep with APF disabled.
synchronized (sLock) {
try {
android.hardware.wifi.V1_2.IWifiStaIface ifaceV12 =
getWifiStaIfaceForV1_2Mockable(ifaceName);
if (ifaceV12 == null) return byteArrayResult(null);
ifaceV12.readApfPacketFilterData((status, dataByteArray) -> {
if (!ok(status)) return;
answer.data = NativeUtil.byteArrayFromArrayList(dataByteArray);
});
return byteArrayResult(answer.data);
} catch (RemoteException e) {
handleRemoteException(e);
return byteArrayResult(null);
}
}
}
/**
* Set country code for this AP iface.
*
* @param ifaceName Name of the interface.
* @param countryCode - two-letter country code (as ISO 3166)
* @return true for success
*/
public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
if (countryCode == null) return boolResult(false);
if (countryCode.length() != 2) return boolResult(false);
byte[] code;
try {
code = NativeUtil.stringToByteArray(countryCode);
} catch (IllegalArgumentException e) {
return boolResult(false);
}
synchronized (sLock) {
try {
IWifiApIface iface = getApIface(ifaceName);
if (iface == null) return boolResult(false);
WifiStatus status = iface.setCountryCode(code);
if (!ok(status)) return false;
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
private WifiNative.WifiLoggerEventHandler mLogEventHandler = null;
/**
* Registers the logger callback and enables alerts.
* Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked.
*/
public boolean setLoggingEventHandler(WifiNative.WifiLoggerEventHandler handler) {
if (handler == null) return boolResult(false);
synchronized (sLock) {
if (mIWifiChip == null) return boolResult(false);
if (mLogEventHandler != null) return boolResult(false);
try {
WifiStatus status = mIWifiChip.enableDebugErrorAlerts(true);
if (!ok(status)) return false;
mLogEventHandler = handler;
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Stops all logging and resets the logger callback.
* This stops both the alerts and ring buffer data collection.
* Existing log handler is cleared.
*/
public boolean resetLogHandler() {
synchronized (sLock) {
mLogEventHandler = null;
if (mIWifiChip == null) return boolResult(false);
try {
WifiStatus status = mIWifiChip.enableDebugErrorAlerts(false);
if (!ok(status)) return false;
status = mIWifiChip.stopLoggingToDebugRingBuffer();
if (!ok(status)) return false;
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Control debug data collection
*
* @param verboseLevel 0 to 3, inclusive. 0 stops logging.
* @param flags Ignored.
* @param maxIntervalInSec Maximum interval between reports; ignore if 0.
* @param minDataSizeInBytes Minimum data size in buffer for report; ignore if 0.
* @param ringName Name of the ring for which data collection is to start.
* @return true for success
*/
public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxIntervalInSec,
int minDataSizeInBytes, String ringName) {
enter("verboseLevel=%, flags=%, maxIntervalInSec=%, minDataSizeInBytes=%, ringName=%")
.c(verboseLevel).c(flags).c(maxIntervalInSec).c(minDataSizeInBytes).c(ringName)
.flush();
synchronized (sLock) {
if (mIWifiChip == null) return boolResult(false);
try {
// note - flags are not used
WifiStatus status = mIWifiChip.startLoggingToDebugRingBuffer(
ringName,
verboseLevel,
maxIntervalInSec,
minDataSizeInBytes
);
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Pointlessly fail
*
* @return -1
*/
public int getSupportedLoggerFeatureSet() {
return -1;
}
private String mDriverDescription; // Cached value filled by requestChipDebugInfo()
/**
* Vendor-provided wifi driver version string
*/
public String getDriverVersion() {
synchronized (sLock) {
if (mDriverDescription == null) requestChipDebugInfo();
return mDriverDescription;
}
}
private String mFirmwareDescription; // Cached value filled by requestChipDebugInfo()
/**
* Vendor-provided wifi firmware version string
*/
public String getFirmwareVersion() {
synchronized (sLock) {
if (mFirmwareDescription == null) requestChipDebugInfo();
return mFirmwareDescription;
}
}
/**
* Refreshes our idea of the driver and firmware versions
*/
private void requestChipDebugInfo() {
mDriverDescription = null;
mFirmwareDescription = null;
try {
if (mIWifiChip == null) return;
mIWifiChip.requestChipDebugInfo((status, chipDebugInfo) -> {
if (!ok(status)) return;
mDriverDescription = chipDebugInfo.driverDescription;
mFirmwareDescription = chipDebugInfo.firmwareDescription;
});
} catch (RemoteException e) {
handleRemoteException(e);
return;
}
mLog.info("Driver: % Firmware: %")
.c(mDriverDescription)
.c(mFirmwareDescription)
.flush();
}
/**
* Creates RingBufferStatus from the Hal version
*/
private static WifiNative.RingBufferStatus ringBufferStatus(WifiDebugRingBufferStatus h) {
WifiNative.RingBufferStatus ans = new WifiNative.RingBufferStatus();
ans.name = h.ringName;
ans.flag = frameworkRingBufferFlagsFromHal(h.flags);
ans.ringBufferId = h.ringId;
ans.ringBufferByteSize = h.sizeInBytes;
ans.verboseLevel = h.verboseLevel;
// Remaining fields are unavailable
// writtenBytes;
// readBytes;
// writtenRecords;
return ans;
}
/**
* Translates a hal wifiDebugRingBufferFlag to the WifiNative version
*/
private static int frameworkRingBufferFlagsFromHal(int wifiDebugRingBufferFlag) {
BitMask checkoff = new BitMask(wifiDebugRingBufferFlag);
int flags = 0;
if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_BINARY_ENTRIES)) {
flags |= WifiNative.RingBufferStatus.HAS_BINARY_ENTRIES;
}
if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_ASCII_ENTRIES)) {
flags |= WifiNative.RingBufferStatus.HAS_ASCII_ENTRIES;
}
if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_PER_PACKET_ENTRIES)) {
flags |= WifiNative.RingBufferStatus.HAS_PER_PACKET_ENTRIES;
}
if (checkoff.value != 0) {
throw new IllegalArgumentException("Unknown WifiDebugRingBufferFlag " + checkoff.value);
}
return flags;
}
/**
* Creates array of RingBufferStatus from the Hal version
*/
private static WifiNative.RingBufferStatus[] makeRingBufferStatusArray(
ArrayList<WifiDebugRingBufferStatus> ringBuffers) {
WifiNative.RingBufferStatus[] ans = new WifiNative.RingBufferStatus[ringBuffers.size()];
int i = 0;
for (WifiDebugRingBufferStatus b : ringBuffers) {
ans[i++] = ringBufferStatus(b);
}
return ans;
}
/**
* API to get the status of all ring buffers supported by driver
*/
public WifiNative.RingBufferStatus[] getRingBufferStatus() {
class AnswerBox {
public WifiNative.RingBufferStatus[] value = null;
}
AnswerBox ans = new AnswerBox();
synchronized (sLock) {
if (mIWifiChip == null) return null;
try {
mIWifiChip.getDebugRingBuffersStatus((status, ringBuffers) -> {
if (!ok(status)) return;
ans.value = makeRingBufferStatusArray(ringBuffers);
});
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
return ans.value;
}
/**
* Indicates to driver that all the data has to be uploaded urgently
*/
public boolean getRingBufferData(String ringName) {
enter("ringName %").c(ringName).flush();
synchronized (sLock) {
if (mIWifiChip == null) return boolResult(false);
try {
WifiStatus status = mIWifiChip.forceDumpToDebugRingBuffer(ringName);
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* request hal to flush ring buffers to files
*/
public boolean flushRingBufferData() {
synchronized (sLock) {
if (mIWifiChip == null) return boolResult(false);
android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
if (iWifiChipV13 != null) {
try {
WifiStatus status = iWifiChipV13.flushRingBufferToFile();
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
return false;
}
}
/**
* Request vendor debug info from the firmware
*/
public byte[] getFwMemoryDump() {
class AnswerBox {
public byte[] value;
}
AnswerBox ans = new AnswerBox();
synchronized (sLock) {
if (mIWifiChip == null) return (null);
try {
mIWifiChip.requestFirmwareDebugDump((status, blob) -> {
if (!ok(status)) return;
ans.value = NativeUtil.byteArrayFromArrayList(blob);
});
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
return ans.value;
}
/**
* Request vendor debug info from the driver
*/
public byte[] getDriverStateDump() {
class AnswerBox {
public byte[] value;
}
AnswerBox ans = new AnswerBox();
synchronized (sLock) {
if (mIWifiChip == null) return (null);
try {
mIWifiChip.requestDriverDebugDump((status, blob) -> {
if (!ok(status)) return;
ans.value = NativeUtil.byteArrayFromArrayList(blob);
});
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
return ans.value;
}
/**
* Start packet fate monitoring
* <p>
* Once started, monitoring remains active until HAL is unloaded.
*
* @param ifaceName Name of the interface.
* @return true for success
*/
public boolean startPktFateMonitoring(@NonNull String ifaceName) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
WifiStatus status = iface.startDebugPacketFateMonitoring();
return ok(status);
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
private byte halToFrameworkPktFateFrameType(int type) {
switch (type) {
case WifiDebugPacketFateFrameType.UNKNOWN:
return WifiLoggerHal.FRAME_TYPE_UNKNOWN;
case WifiDebugPacketFateFrameType.ETHERNET_II:
return WifiLoggerHal.FRAME_TYPE_ETHERNET_II;
case WifiDebugPacketFateFrameType.MGMT_80211:
return WifiLoggerHal.FRAME_TYPE_80211_MGMT;
default:
throw new IllegalArgumentException("bad " + type);
}
}
private byte halToFrameworkRxPktFate(int type) {
switch (type) {
case WifiDebugRxPacketFate.SUCCESS:
return WifiLoggerHal.RX_PKT_FATE_SUCCESS;
case WifiDebugRxPacketFate.FW_QUEUED:
return WifiLoggerHal.RX_PKT_FATE_FW_QUEUED;
case WifiDebugRxPacketFate.FW_DROP_FILTER:
return WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER;
case WifiDebugRxPacketFate.FW_DROP_INVALID:
return WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID;
case WifiDebugRxPacketFate.FW_DROP_NOBUFS:
return WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS;
case WifiDebugRxPacketFate.FW_DROP_OTHER:
return WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER;
case WifiDebugRxPacketFate.DRV_QUEUED:
return WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED;
case WifiDebugRxPacketFate.DRV_DROP_FILTER:
return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER;
case WifiDebugRxPacketFate.DRV_DROP_INVALID:
return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID;
case WifiDebugRxPacketFate.DRV_DROP_NOBUFS:
return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS;
case WifiDebugRxPacketFate.DRV_DROP_OTHER:
return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER;
default:
throw new IllegalArgumentException("bad " + type);
}
}
private byte halToFrameworkTxPktFate(int type) {
switch (type) {
case WifiDebugTxPacketFate.ACKED:
return WifiLoggerHal.TX_PKT_FATE_ACKED;
case WifiDebugTxPacketFate.SENT:
return WifiLoggerHal.TX_PKT_FATE_SENT;
case WifiDebugTxPacketFate.FW_QUEUED:
return WifiLoggerHal.TX_PKT_FATE_FW_QUEUED;
case WifiDebugTxPacketFate.FW_DROP_INVALID:
return WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID;
case WifiDebugTxPacketFate.FW_DROP_NOBUFS:
return WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS;
case WifiDebugTxPacketFate.FW_DROP_OTHER:
return WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER;
case WifiDebugTxPacketFate.DRV_QUEUED:
return WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED;
case WifiDebugTxPacketFate.DRV_DROP_INVALID:
return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID;
case WifiDebugTxPacketFate.DRV_DROP_NOBUFS:
return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS;
case WifiDebugTxPacketFate.DRV_DROP_OTHER:
return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER;
default:
throw new IllegalArgumentException("bad " + type);
}
}
/**
* Retrieve fates of outbound packets
* <p>
* Reports the outbound frames for the most recent association (space allowing).
*
* @param ifaceName Name of the interface.
* @param reportBufs
* @return true for success
*/
public boolean getTxPktFates(@NonNull String ifaceName, WifiNative.TxFateReport[] reportBufs) {
if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
MutableBoolean ok = new MutableBoolean(false);
iface.getDebugTxPacketFates((status, fates) -> {
if (!ok(status)) return;
int i = 0;
for (WifiDebugTxPacketFateReport fate : fates) {
if (i >= reportBufs.length) break;
byte code = halToFrameworkTxPktFate(fate.fate);
long us = fate.frameInfo.driverTimestampUsec;
byte type =
halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
byte[] frame =
NativeUtil.byteArrayFromArrayList(
fate.frameInfo.frameContent);
reportBufs[i++] =
new WifiNative.TxFateReport(code, us, type, frame);
}
ok.value = true;
}
);
return ok.value;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Retrieve fates of inbound packets
* <p>
* Reports the inbound frames for the most recent association (space allowing).
*
* @param ifaceName Name of the interface.
* @param reportBufs
* @return true for success
*/
public boolean getRxPktFates(@NonNull String ifaceName, WifiNative.RxFateReport[] reportBufs) {
if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
MutableBoolean ok = new MutableBoolean(false);
iface.getDebugRxPacketFates((status, fates) -> {
if (!ok(status)) return;
int i = 0;
for (WifiDebugRxPacketFateReport fate : fates) {
if (i >= reportBufs.length) break;
byte code = halToFrameworkRxPktFate(fate.fate);
long us = fate.frameInfo.driverTimestampUsec;
byte type =
halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
byte[] frame =
NativeUtil.byteArrayFromArrayList(
fate.frameInfo.frameContent);
reportBufs[i++] =
new WifiNative.RxFateReport(code, us, type, frame);
}
ok.value = true;
}
);
return ok.value;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Start sending the specified keep alive packets periodically.
*
* @param ifaceName Name of the interface.
* @param slot
* @param srcMac
* @param dstMac
* @param keepAlivePacket
* @param protocol
* @param periodInMs
* @return 0 for success, -1 for error
*/
public int startSendingOffloadedPacket(
@NonNull String ifaceName, int slot, byte[] srcMac, byte[] dstMac,
byte[] packet, int protocol, int periodInMs) {
enter("slot=% periodInMs=%").c(slot).c(periodInMs).flush();
ArrayList<Byte> data = NativeUtil.byteArrayToArrayList(packet);
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return -1;
try {
WifiStatus status = iface.startSendingKeepAlivePackets(
slot,
data,
(short) protocol,
srcMac,
dstMac,
periodInMs);
if (!ok(status)) return -1;
return 0;
} catch (RemoteException e) {
handleRemoteException(e);
return -1;
}
}
}
/**
* Stop sending the specified keep alive packets.
*
* @param ifaceName Name of the interface.
* @param slot id - same as startSendingOffloadedPacket call.
* @return 0 for success, -1 for error
*/
public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) {
enter("slot=%").c(slot).flush();
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return -1;
try {
WifiStatus status = iface.stopSendingKeepAlivePackets(slot);
if (!ok(status)) return -1;
return 0;
} catch (RemoteException e) {
handleRemoteException(e);
return -1;
}
}
}
/**
* A fixed cmdId for our RssiMonitoring (we only do one at a time)
*/
@VisibleForTesting
static final int sRssiMonCmdId = 7551;
/**
* Our client's handler
*/
private WifiNative.WifiRssiEventHandler mWifiRssiEventHandler;
/**
* Start RSSI monitoring on the currently connected access point.
*
* @param ifaceName Name of the interface.
* @param maxRssi Maximum RSSI threshold.
* @param minRssi Minimum RSSI threshold.
* @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi
* @return 0 for success, -1 for failure
*/
public int startRssiMonitoring(@NonNull String ifaceName, byte maxRssi, byte minRssi,
WifiNative.WifiRssiEventHandler rssiEventHandler) {
enter("maxRssi=% minRssi=%").c(maxRssi).c(minRssi).flush();
if (maxRssi <= minRssi) return -1;
if (rssiEventHandler == null) return -1;
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return -1;
try {
iface.stopRssiMonitoring(sRssiMonCmdId);
WifiStatus status;
status = iface.startRssiMonitoring(sRssiMonCmdId, maxRssi, minRssi);
if (!ok(status)) return -1;
mWifiRssiEventHandler = rssiEventHandler;
return 0;
} catch (RemoteException e) {
handleRemoteException(e);
return -1;
}
}
}
/**
* Stop RSSI monitoring
*
* @param ifaceName Name of the interface.
* @return 0 for success, -1 for failure
*/
public int stopRssiMonitoring(@NonNull String ifaceName) {
synchronized (sLock) {
mWifiRssiEventHandler = null;
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return -1;
try {
WifiStatus status = iface.stopRssiMonitoring(sRssiMonCmdId);
if (!ok(status)) return -1;
return 0;
} catch (RemoteException e) {
handleRemoteException(e);
return -1;
}
}
}
//TODO - belongs in NativeUtil
private static int[] intsFromArrayList(ArrayList<Integer> a) {
if (a == null) return null;
int[] b = new int[a.size()];
int i = 0;
for (Integer e : a) b[i++] = e;
return b;
}
/**
* Translates from Hal version of wake reason stats to the framework version of same
*
* @param h - Hal version of wake reason stats
* @return framework version of same
*/
private static WlanWakeReasonAndCounts halToFrameworkWakeReasons(
WifiDebugHostWakeReasonStats h) {
if (h == null) return null;
WlanWakeReasonAndCounts ans = new WlanWakeReasonAndCounts();
ans.totalCmdEventWake = h.totalCmdEventWakeCnt;
ans.totalDriverFwLocalWake = h.totalDriverFwLocalWakeCnt;
ans.totalRxDataWake = h.totalRxPacketWakeCnt;
ans.rxUnicast = h.rxPktWakeDetails.rxUnicastCnt;
ans.rxMulticast = h.rxPktWakeDetails.rxMulticastCnt;
ans.rxBroadcast = h.rxPktWakeDetails.rxBroadcastCnt;
ans.icmp = h.rxIcmpPkWakeDetails.icmpPkt;
ans.icmp6 = h.rxIcmpPkWakeDetails.icmp6Pkt;
ans.icmp6Ra = h.rxIcmpPkWakeDetails.icmp6Ra;
ans.icmp6Na = h.rxIcmpPkWakeDetails.icmp6Na;
ans.icmp6Ns = h.rxIcmpPkWakeDetails.icmp6Ns;
ans.ipv4RxMulticast = h.rxMulticastPkWakeDetails.ipv4RxMulticastAddrCnt;
ans.ipv6Multicast = h.rxMulticastPkWakeDetails.ipv6RxMulticastAddrCnt;
ans.otherRxMulticast = h.rxMulticastPkWakeDetails.otherRxMulticastAddrCnt;
ans.cmdEventWakeCntArray = intsFromArrayList(h.cmdEventWakeCntPerType);
ans.driverFWLocalWakeCntArray = intsFromArrayList(h.driverFwLocalWakeCntPerType);
return ans;
}
/**
* Fetch the host wakeup reasons stats from wlan driver.
*
* @return the |WlanWakeReasonAndCounts| from the wlan driver, or null on failure.
*/
public WlanWakeReasonAndCounts getWlanWakeReasonCount() {
class AnswerBox {
public WifiDebugHostWakeReasonStats value = null;
}
AnswerBox ans = new AnswerBox();
synchronized (sLock) {
if (mIWifiChip == null) return null;
try {
mIWifiChip.getDebugHostWakeReasonStats((status, stats) -> {
if (ok(status)) {
ans.value = stats;
}
});
return halToFrameworkWakeReasons(ans.value);
} catch (RemoteException e) {
handleRemoteException(e);
return null;
}
}
}
/**
* Enable/Disable Neighbour discovery offload functionality in the firmware.
*
* @param ifaceName Name of the interface.
* @param enabled true to enable, false to disable.
* @return true for success, false for failure
*/
public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) {
enter("enabled=%").c(enabled).flush();
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
WifiStatus status = iface.enableNdOffload(enabled);
if (!ok(status)) return false;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
return true;
}
// Firmware roaming control.
/**
* Query the firmware roaming capabilities.
*
* @param ifaceName Name of the interface.
* @param capabilities object to be filled in
* @return true for success; false for failure
*/
public boolean getRoamingCapabilities(@NonNull String ifaceName,
WifiNative.RoamingCapabilities capabilities) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
MutableBoolean ok = new MutableBoolean(false);
WifiNative.RoamingCapabilities out = capabilities;
iface.getRoamingCapabilities((status, cap) -> {
if (!ok(status)) return;
out.maxBlacklistSize = cap.maxBlacklistSize;
out.maxWhitelistSize = cap.maxWhitelistSize;
ok.value = true;
});
return ok.value;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
}
/**
* Enable/disable firmware roaming.
*
* @param ifaceName Name of the interface.
* @param state the intended roaming state
* @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE,
* or SET_FIRMWARE_ROAMING_BUSY
*/
public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
try {
byte val;
switch (state) {
case WifiNative.DISABLE_FIRMWARE_ROAMING:
val = StaRoamingState.DISABLED;
break;
case WifiNative.ENABLE_FIRMWARE_ROAMING:
val = StaRoamingState.ENABLED;
break;
default:
mLog.err("enableFirmwareRoaming invalid argument %").c(state).flush();
return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
}
WifiStatus status = iface.setRoamingState(val);
if (ok(status)) {
return WifiNative.SET_FIRMWARE_ROAMING_SUCCESS;
} else if (status.code == WifiStatusCode.ERROR_BUSY) {
return WifiNative.SET_FIRMWARE_ROAMING_BUSY;
} else {
return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
}
} catch (RemoteException e) {
handleRemoteException(e);
return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
}
}
}
/**
* Set firmware roaming configurations.
*
* @param ifaceName Name of the interface.
* @param config new roaming configuration object
* @return true for success; false for failure
*/
public boolean configureRoaming(@NonNull String ifaceName, WifiNative.RoamingConfig config) {
synchronized (sLock) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return boolResult(false);
try {
StaRoamingConfig roamingConfig = new StaRoamingConfig();
// parse the blacklist BSSIDs if any
if (config.blacklistBssids != null) {
for (String bssid : config.blacklistBssids) {
byte[] mac = NativeUtil.macAddressToByteArray(bssid);
roamingConfig.bssidBlacklist.add(mac);
}
}
// parse the whitelist SSIDs if any
if (config.whitelistSsids != null) {
for (String ssidStr : config.whitelistSsids) {
byte[] ssid = NativeUtil.byteArrayFromArrayList(
NativeUtil.decodeSsid(ssidStr));
roamingConfig.ssidWhitelist.add(ssid);
}
}
WifiStatus status = iface.configureRoaming(roamingConfig);
if (!ok(status)) return false;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
} catch (IllegalArgumentException e) {
mLog.err("Illegal argument for roaming configuration").c(e.toString()).flush();
return false;
}
return true;
}
}
/**
* Method to mock out the V1_1 IWifiChip retrieval in unit tests.
*
* @return 1.1 IWifiChip object if the device is running the 1.1 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
if (mIWifiChip == null) return null;
return android.hardware.wifi.V1_1.IWifiChip.castFrom(mIWifiChip);
}
/**
* Method to mock out the V1_2 IWifiChip retrieval in unit tests.
*
* @return 1.2 IWifiChip object if the device is running the 1.2 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_2.IWifiChip getWifiChipForV1_2Mockable() {
if (mIWifiChip == null) return null;
return android.hardware.wifi.V1_2.IWifiChip.castFrom(mIWifiChip);
}
/**
* Method to mock out the V1_3 IWifiChip retrieval in unit tests.
*
* @return 1.3 IWifiChip object if the device is running the 1.3 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_3.IWifiChip getWifiChipForV1_3Mockable() {
if (mIWifiChip == null) return null;
return android.hardware.wifi.V1_3.IWifiChip.castFrom(mIWifiChip);
}
/**
* Method to mock out the V1_4 IWifiChip retrieval in unit tests.
*
* @return 1.4 IWifiChip object if the device is running the 1.4 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_4.IWifiChip getWifiChipForV1_4Mockable() {
if (mIWifiChip == null) return null;
return android.hardware.wifi.V1_4.IWifiChip.castFrom(mIWifiChip);
}
/**
* Method to mock out the V1_2 IWifiStaIface retrieval in unit tests.
*
* @param ifaceName Name of the interface
* @return 1.2 IWifiStaIface object if the device is running the 1.2 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_2.IWifiStaIface getWifiStaIfaceForV1_2Mockable(
@NonNull String ifaceName) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return null;
return android.hardware.wifi.V1_2.IWifiStaIface.castFrom(iface);
}
/**
* Method to mock out the V1_3 IWifiStaIface retrieval in unit tests.
*
* @param ifaceName Name of the interface
* @return 1.3 IWifiStaIface object if the device is running the 1.3 wifi hal service, null
* otherwise.
*/
protected android.hardware.wifi.V1_3.IWifiStaIface getWifiStaIfaceForV1_3Mockable(
@NonNull String ifaceName) {
IWifiStaIface iface = getStaIface(ifaceName);
if (iface == null) return null;
return android.hardware.wifi.V1_3.IWifiStaIface.castFrom(iface);
}
protected android.hardware.wifi.V1_4.IWifiApIface getWifiApIfaceForV1_4Mockable(
String ifaceName) {
IWifiApIface iface = getApIface(ifaceName);
if (iface == null) return null;
return android.hardware.wifi.V1_4.IWifiApIface.castFrom(iface);
}
/**
* sarPowerBackoffRequired_1_1()
* This method checks if we need to backoff wifi Tx power due to SAR requirements.
* It handles the case when the device is running the V1_1 version of WifiChip HAL
* In that HAL version, it is required to perform wifi Tx power backoff only if
* a voice call is ongoing.
*/
private boolean sarPowerBackoffRequired_1_1(SarInfo sarInfo) {
/* As long as no voice call is active (in case voice call is supported),
* no backoff is needed */
if (sarInfo.sarVoiceCallSupported) {
return (sarInfo.isVoiceCall || sarInfo.isEarPieceActive);
} else {
return false;
}
}
/**
* frameworkToHalTxPowerScenario_1_1()
* This method maps the information inside the SarInfo instance into a SAR scenario
* when device is running the V1_1 version of WifiChip HAL.
* In this HAL version, only one scenario is defined which is for VOICE_CALL (if voice call is
* supported).
* Otherwise, an exception is thrown.
*/
private int frameworkToHalTxPowerScenario_1_1(SarInfo sarInfo) {
if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) {
return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL;
} else {
throw new IllegalArgumentException("bad scenario: voice call not active/supported");
}
}
/**
* sarPowerBackoffRequired_1_2()
* This method checks if we need to backoff wifi Tx power due to SAR requirements.
* It handles the case when the device is running the V1_2 version of WifiChip HAL
*/
private boolean sarPowerBackoffRequired_1_2(SarInfo sarInfo) {
if (sarInfo.sarSapSupported && sarInfo.isWifiSapEnabled) {
return true;
}
if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) {
return true;
}
return false;
}
/**
* frameworkToHalTxPowerScenario_1_2()
* This method maps the information inside the SarInfo instance into a SAR scenario
* when device is running the V1_2 version of WifiChip HAL.
* If SAR SoftAP input is supported,
* we make these assumptions:
* - All voice calls are treated as if device is near the head.
* - SoftAP scenario is treated as if device is near the body.
* In case SoftAP is not supported, then we should revert to the V1_1 HAL
* behavior, and the only valid scenario would be when a voice call is ongoing.
*/
private int frameworkToHalTxPowerScenario_1_2(SarInfo sarInfo) {
if (sarInfo.sarSapSupported && sarInfo.sarVoiceCallSupported) {
if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) {
return android.hardware.wifi.V1_2.IWifiChip
.TxPowerScenario.ON_HEAD_CELL_ON;
} else if (sarInfo.isWifiSapEnabled) {
return android.hardware.wifi.V1_2.IWifiChip
.TxPowerScenario.ON_BODY_CELL_ON;
} else {
throw new IllegalArgumentException("bad scenario: no voice call/softAP active");
}
} else if (sarInfo.sarVoiceCallSupported) {
/* SAR SoftAP input not supported, act like V1_1 */
if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) {
return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL;
} else {
throw new IllegalArgumentException("bad scenario: voice call not active");
}
} else {
throw new IllegalArgumentException("Invalid case: voice call not supported");
}
}
/**
* Select one of the pre-configured TX power level scenarios or reset it back to normal.
* Primarily used for meeting SAR requirements during voice calls.
*
* Note: If it was found out that the scenario to be reported is the same as last reported one,
* then exit with success.
* This is to handle the case when some HAL versions deal with different inputs equally,
* in that case, we should not call the hal unless there is a change in scenario.
* Note: It is assumed that this method is only called if SAR is enabled. The logic of whether
* to call it or not resides in SarManager class.
*
* @param sarInfo The collection of inputs to select the SAR scenario.
* @return true for success; false for failure or if the HAL version does not support this API.
*/
public boolean selectTxPowerScenario(SarInfo sarInfo) {
synchronized (sLock) {
// First attempt to get a V_1_2 instance of the Wifi HAL.
android.hardware.wifi.V1_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable();
if (iWifiChipV12 != null) {
return selectTxPowerScenario_1_2(iWifiChipV12, sarInfo);
}
// Now attempt to get a V_1_1 instance of the Wifi HAL.
android.hardware.wifi.V1_1.IWifiChip iWifiChipV11 = getWifiChipForV1_1Mockable();
if (iWifiChipV11 != null) {
return selectTxPowerScenario_1_1(iWifiChipV11, sarInfo);
}
// HAL version does not support SAR
return false;
}
}
private boolean selectTxPowerScenario_1_1(
android.hardware.wifi.V1_1.IWifiChip iWifiChip, SarInfo sarInfo) {
WifiStatus status;
try {
if (sarPowerBackoffRequired_1_1(sarInfo)) {
// Power backoff is needed, so calculate the required scenario,
// and attempt to set it.
int halScenario = frameworkToHalTxPowerScenario_1_1(sarInfo);
if (sarInfo.setSarScenarioNeeded(halScenario)) {
status = iWifiChip.selectTxPowerScenario(halScenario);
if (ok(status)) {
mLog.d("Setting SAR scenario to " + halScenario);
return true;
} else {
mLog.e("Failed to set SAR scenario to " + halScenario);
return false;
}
}
// Reaching here means setting SAR scenario would be redundant,
// do nothing and return with success.
return true;
}
// We don't need to perform power backoff, so attempt to reset SAR scenario.
if (sarInfo.resetSarScenarioNeeded()) {
status = iWifiChip.resetTxPowerScenario();
if (ok(status)) {
mLog.d("Resetting SAR scenario");
return true;
} else {
mLog.e("Failed to reset SAR scenario");
return false;
}
}
// Resetting SAR scenario would be redundant,
// do nothing and return with success.
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
} catch (IllegalArgumentException e) {
mLog.err("Illegal argument for selectTxPowerScenario_1_1()").c(e.toString()).flush();
return false;
}
}
private boolean selectTxPowerScenario_1_2(
android.hardware.wifi.V1_2.IWifiChip iWifiChip, SarInfo sarInfo) {
WifiStatus status;
try {
if (sarPowerBackoffRequired_1_2(sarInfo)) {
// Power backoff is needed, so calculate the required scenario,
// and attempt to set it.
int halScenario = frameworkToHalTxPowerScenario_1_2(sarInfo);
if (sarInfo.setSarScenarioNeeded(halScenario)) {
status = iWifiChip.selectTxPowerScenario_1_2(halScenario);
if (ok(status)) {
mLog.d("Setting SAR scenario to " + halScenario);
return true;
} else {
mLog.e("Failed to set SAR scenario to " + halScenario);
return false;
}
}
// Reaching here means setting SAR scenario would be redundant,
// do nothing and return with success.
return true;
}
// We don't need to perform power backoff, so attempt to reset SAR scenario.
if (sarInfo.resetSarScenarioNeeded()) {
status = iWifiChip.resetTxPowerScenario();
if (ok(status)) {
mLog.d("Resetting SAR scenario");
return true;
} else {
mLog.e("Failed to reset SAR scenario");
return false;
}
}
// Resetting SAR scenario would be redundant,
// do nothing and return with success.
return true;
} catch (RemoteException e) {
handleRemoteException(e);
return false;
} catch (IllegalArgumentException e) {
mLog.err("Illegal argument for selectTxPowerScenario_1_2()").c(e.toString()).flush();
return false;
}
}
/**
* Enable/Disable low-latency mode
*
* @param enabled true to enable low-latency mode, false to disable it
*/
public boolean setLowLatencyMode(boolean enabled) {
synchronized (sLock) {
android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
if (iWifiChipV13 != null) {
try {
int mode;
if (enabled) {
mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.LOW;
} else {
mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.NORMAL;
}
WifiStatus status = iWifiChipV13.setLatencyMode(mode);
if (ok(status)) {
mVerboseLog.d("Setting low-latency mode to " + enabled);
return true;
} else {
mLog.e("Failed to set low-latency mode to " + enabled);
return false;
}
} catch (RemoteException e) {
handleRemoteException(e);
return false;
}
}
// HAL version does not support this api
return false;
}
}
/**
* Returns whether STA/AP concurrency is supported or not.
*/
public boolean isStaApConcurrencySupported() {
synchronized (sLock) {
return mHalDeviceManager.canSupportIfaceCombo(new SparseArray<Integer>() {{
put(IfaceType.STA, 1);
put(IfaceType.AP, 1);
}});
}
}
// This creates a blob of IE elements from the array received.
// TODO: This ugly conversion can be removed if we put IE elements in ScanResult.
private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList<WifiInformationElement> ies) {
if (ies == null || ies.isEmpty()) return new byte[0];
ArrayList<Byte> ieBlob = new ArrayList<>();
for (WifiInformationElement ie : ies) {
ieBlob.add(ie.id);
ieBlob.addAll(ie.data);
}
return NativeUtil.byteArrayFromArrayList(ieBlob);
}
// This is only filling up the fields of Scan Result used by Gscan clients.
private static ScanResult hidlToFrameworkScanResult(StaScanResult scanResult) {
if (scanResult == null) return null;
ScanResult frameworkScanResult = new ScanResult();
frameworkScanResult.SSID = NativeUtil.encodeSsid(scanResult.ssid);
frameworkScanResult.wifiSsid =
WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(scanResult.ssid));
frameworkScanResult.BSSID = NativeUtil.macAddressFromByteArray(scanResult.bssid);
frameworkScanResult.level = scanResult.rssi;
frameworkScanResult.frequency = scanResult.frequency;
frameworkScanResult.timestamp = scanResult.timeStampInUs;
return frameworkScanResult;
}
private static ScanResult[] hidlToFrameworkScanResults(ArrayList<StaScanResult> scanResults) {
if (scanResults == null || scanResults.isEmpty()) return new ScanResult[0];
ScanResult[] frameworkScanResults = new ScanResult[scanResults.size()];
int i = 0;
for (StaScanResult scanResult : scanResults) {
frameworkScanResults[i++] = hidlToFrameworkScanResult(scanResult);
}
return frameworkScanResults;
}
/**
* This just returns whether the scan was interrupted or not.
*/
private static int hidlToFrameworkScanDataFlags(int flag) {
if (flag == StaScanDataFlagMask.INTERRUPTED) {
return 1;
} else {
return 0;
}
}
private static WifiScanner.ScanData[] hidlToFrameworkScanDatas(
int cmdId, ArrayList<StaScanData> scanDatas) {
if (scanDatas == null || scanDatas.isEmpty()) return new WifiScanner.ScanData[0];
WifiScanner.ScanData[] frameworkScanDatas = new WifiScanner.ScanData[scanDatas.size()];
int i = 0;
for (StaScanData scanData : scanDatas) {
int flags = hidlToFrameworkScanDataFlags(scanData.flags);
ScanResult[] frameworkScanResults = hidlToFrameworkScanResults(scanData.results);
frameworkScanDatas[i++] =
new WifiScanner.ScanData(cmdId, flags, scanData.bucketsScanned,
WifiScanner.WIFI_BAND_UNSPECIFIED, frameworkScanResults);
}
return frameworkScanDatas;
}
/**
* Callback for events on the STA interface.
*/
private class StaIfaceEventCallback extends IWifiStaIfaceEventCallback.Stub {
@Override
public void onBackgroundScanFailure(int cmdId) {
mVerboseLog.d("onBackgroundScanFailure " + cmdId);
WifiNative.ScanEventHandler eventHandler;
synchronized (sLock) {
if (mScan == null || cmdId != mScan.cmdId) return;
eventHandler = mScan.eventHandler;
}
eventHandler.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
}
@Override
public void onBackgroundFullScanResult(
int cmdId, int bucketsScanned, StaScanResult result) {
mVerboseLog.d("onBackgroundFullScanResult " + cmdId);
WifiNative.ScanEventHandler eventHandler;
synchronized (sLock) {
if (mScan == null || cmdId != mScan.cmdId) return;
eventHandler = mScan.eventHandler;
}
eventHandler.onFullScanResult(hidlToFrameworkScanResult(result), bucketsScanned);
}
@Override
public void onBackgroundScanResults(int cmdId, ArrayList<StaScanData> scanDatas) {
mVerboseLog.d("onBackgroundScanResults " + cmdId);
WifiNative.ScanEventHandler eventHandler;
// WifiScanner currently uses the results callback to fetch the scan results.
// So, simulate that by sending out the notification and then caching the results
// locally. This will then be returned to WifiScanner via getScanResults.
synchronized (sLock) {
if (mScan == null || cmdId != mScan.cmdId) return;
eventHandler = mScan.eventHandler;
mScan.latestScanResults = hidlToFrameworkScanDatas(cmdId, scanDatas);
}
eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
}
@Override
public void onRssiThresholdBreached(int cmdId, byte[/* 6 */] currBssid, int currRssi) {
mVerboseLog.d("onRssiThresholdBreached " + cmdId + "currRssi " + currRssi);
WifiNative.WifiRssiEventHandler eventHandler;
synchronized (sLock) {
if (mWifiRssiEventHandler == null || cmdId != sRssiMonCmdId) return;
eventHandler = mWifiRssiEventHandler;
}
eventHandler.onRssiThresholdBreached((byte) currRssi);
}
}
/**
* Callback for events on the chip.
*/
private class ChipEventCallback extends IWifiChipEventCallback.Stub {
@Override
public void onChipReconfigured(int modeId) {
mVerboseLog.d("onChipReconfigured " + modeId);
}
@Override
public void onChipReconfigureFailure(WifiStatus status) {
mVerboseLog.d("onChipReconfigureFailure " + status);
}
public void onIfaceAdded(int type, String name) {
mVerboseLog.d("onIfaceAdded " + type + ", name: " + name);
}
@Override
public void onIfaceRemoved(int type, String name) {
mVerboseLog.d("onIfaceRemoved " + type + ", name: " + name);
}
@Override
public void onDebugRingBufferDataAvailable(
WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
//TODO(b/35875078) Reinstate logging when execessive callbacks are fixed
// mVerboseLog.d("onDebugRingBufferDataAvailable " + status);
mHalEventHandler.post(() -> {
WifiNative.WifiLoggerEventHandler eventHandler;
synchronized (sLock) {
if (mLogEventHandler == null || status == null || data == null) return;
eventHandler = mLogEventHandler;
}
// Because |sLock| has been released, there is a chance that we'll execute
// a spurious callback (after someone has called resetLogHandler()).
//
// However, the alternative risks deadlock. Consider:
// [T1.1] WifiDiagnostics.captureBugReport()
// [T1.2] -- acquire WifiDiagnostics object's intrinsic lock
// [T1.3] -> WifiVendorHal.getRingBufferData()
// [T1.4] -- acquire WifiVendorHal.sLock
// [T2.1] <lambda>()
// [T2.2] -- acquire WifiVendorHal.sLock
// [T2.3] -> WifiDiagnostics.onRingBufferData()
// [T2.4] -- acquire WifiDiagnostics object's intrinsic lock
//
// The problem here is that the two threads acquire the locks in opposite order.
// If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
// will be deadlocked.
int sizeBefore = data.size();
boolean conversionFailure = false;
try {
eventHandler.onRingBufferData(
ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
int sizeAfter = data.size();
if (sizeAfter != sizeBefore) {
conversionFailure = true;
}
} catch (ArrayIndexOutOfBoundsException e) {
conversionFailure = true;
}
if (conversionFailure) {
Log.wtf("WifiVendorHal", "Conversion failure detected in "
+ "onDebugRingBufferDataAvailable. "
+ "The input ArrayList |data| is potentially corrupted. "
+ "Starting size=" + sizeBefore + ", "
+ "final size=" + data.size());
}
});
}
@Override
public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
mLog.w("onDebugErrorAlert " + errorCode);
mHalEventHandler.post(() -> {
WifiNative.WifiLoggerEventHandler eventHandler;
synchronized (sLock) {
if (mLogEventHandler == null || debugData == null) return;
eventHandler = mLogEventHandler;
}
// See comment in onDebugRingBufferDataAvailable(), for an explanation
// of why this callback is invoked without |sLock| held.
eventHandler.onWifiAlert(
errorCode, NativeUtil.byteArrayFromArrayList(debugData));
});
}
}
private boolean areSameIfaceNames(List<IfaceInfo> ifaceList1, List<IfaceInfo> ifaceList2) {
List<String> ifaceNamesList1 = ifaceList1
.stream()
.map(i -> i.name)
.collect(Collectors.toList());
List<String> ifaceNamesList2 = ifaceList2
.stream()
.map(i -> i.name)
.collect(Collectors.toList());
return ifaceNamesList1.containsAll(ifaceNamesList2);
}
/**
* Callback for events on the 1.2 chip.
*/
private class ChipEventCallbackV12 extends
android.hardware.wifi.V1_2.IWifiChipEventCallback.Stub {
@Override
public void onChipReconfigured(int modeId) {
mIWifiChipEventCallback.onChipReconfigured(modeId);
}
@Override
public void onChipReconfigureFailure(WifiStatus status) {
mIWifiChipEventCallback.onChipReconfigureFailure(status);
}
public void onIfaceAdded(int type, String name) {
mIWifiChipEventCallback.onIfaceAdded(type, name);
}
@Override
public void onIfaceRemoved(int type, String name) {
mIWifiChipEventCallback.onIfaceRemoved(type, name);
}
@Override
public void onDebugRingBufferDataAvailable(
WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
mIWifiChipEventCallback.onDebugRingBufferDataAvailable(status, data);
}
@Override
public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
mIWifiChipEventCallback.onDebugErrorAlert(errorCode, debugData);
}
@Override
public void onRadioModeChange(ArrayList<RadioModeInfo> radioModeInfoList) {
mVerboseLog.d("onRadioModeChange " + radioModeInfoList);
WifiNative.VendorHalRadioModeChangeEventHandler handler;
synchronized (sLock) {
if (mRadioModeChangeEventHandler == null || radioModeInfoList == null) return;
handler = mRadioModeChangeEventHandler;
}
// Should only contain 1 or 2 radio infos.
if (radioModeInfoList.size() == 0 || radioModeInfoList.size() > 2) {
mLog.e("Unexpected number of radio info in list " + radioModeInfoList.size());
return;
}
RadioModeInfo radioModeInfo0 = radioModeInfoList.get(0);
RadioModeInfo radioModeInfo1 =
radioModeInfoList.size() == 2 ? radioModeInfoList.get(1) : null;
// Number of ifaces on each radio should be equal.
if (radioModeInfo1 != null
&& radioModeInfo0.ifaceInfos.size() != radioModeInfo1.ifaceInfos.size()) {
mLog.e("Unexpected number of iface info in list "
+ radioModeInfo0.ifaceInfos.size() + ", "
+ radioModeInfo1.ifaceInfos.size());
return;
}
int numIfacesOnEachRadio = radioModeInfo0.ifaceInfos.size();
// Only 1 or 2 ifaces should be present on each radio.
if (numIfacesOnEachRadio == 0 || numIfacesOnEachRadio > 2) {
mLog.e("Unexpected number of iface info in list " + numIfacesOnEachRadio);
return;
}
Runnable runnable = null;
// 2 ifaces simultaneous on 2 radios.
if (radioModeInfoList.size() == 2 && numIfacesOnEachRadio == 1) {
// Iface on radio0 should be different from the iface on radio1 for DBS & SBS.
if (areSameIfaceNames(radioModeInfo0.ifaceInfos, radioModeInfo1.ifaceInfos)) {
mLog.e("Unexpected for both radio infos to have same iface");
return;
}
if (radioModeInfo0.bandInfo != radioModeInfo1.bandInfo) {
runnable = () -> {
handler.onDbs();
};
} else {
runnable = () -> {
handler.onSbs(radioModeInfo0.bandInfo);
};
}
// 2 ifaces time sharing on 1 radio.
} else if (radioModeInfoList.size() == 1 && numIfacesOnEachRadio == 2) {
IfaceInfo ifaceInfo0 = radioModeInfo0.ifaceInfos.get(0);
IfaceInfo ifaceInfo1 = radioModeInfo0.ifaceInfos.get(1);
if (ifaceInfo0.channel != ifaceInfo1.channel) {
runnable = () -> {
handler.onMcc(radioModeInfo0.bandInfo);
};
} else {
runnable = () -> {
handler.onScc(radioModeInfo0.bandInfo);
};
}
} else {
// Not concurrency scenario, uninteresting...
}
if (runnable != null) mHalEventHandler.post(runnable);
}
}
/**
* Callback for events on the 1.4 chip.
*/
private class ChipEventCallbackV14 extends
android.hardware.wifi.V1_4.IWifiChipEventCallback.Stub {
@Override
public void onChipReconfigured(int modeId) {
mIWifiChipEventCallback.onChipReconfigured(modeId);
}
@Override
public void onChipReconfigureFailure(WifiStatus status) {
mIWifiChipEventCallback.onChipReconfigureFailure(status);
}
public void onIfaceAdded(int type, String name) {
mIWifiChipEventCallback.onIfaceAdded(type, name);
}
@Override
public void onIfaceRemoved(int type, String name) {
mIWifiChipEventCallback.onIfaceRemoved(type, name);
}
@Override
public void onDebugRingBufferDataAvailable(
WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
mIWifiChipEventCallback.onDebugRingBufferDataAvailable(status, data);
}
@Override
public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
mIWifiChipEventCallback.onDebugErrorAlert(errorCode, debugData);
}
@Override
public void onRadioModeChange(
ArrayList<android.hardware.wifi.V1_2.IWifiChipEventCallback.RadioModeInfo>
radioModeInfoList) {
mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfoList);
}
@Override
public void onRadioModeChange_1_4(ArrayList<RadioModeInfo> radioModeInfoList) {
mVerboseLog.d("onRadioModeChange_1_4 " + radioModeInfoList);
WifiNative.VendorHalRadioModeChangeEventHandler handler;
synchronized (sLock) {
if (mRadioModeChangeEventHandler == null || radioModeInfoList == null) return;
handler = mRadioModeChangeEventHandler;
}
// Should only contain 1 or 2 radio infos.
if (radioModeInfoList.size() == 0 || radioModeInfoList.size() > 2) {
mLog.e("Unexpected number of radio info in list " + radioModeInfoList.size());
return;
}
RadioModeInfo radioModeInfo0 = radioModeInfoList.get(0);
RadioModeInfo radioModeInfo1 =
radioModeInfoList.size() == 2 ? radioModeInfoList.get(1) : null;
// Number of ifaces on each radio should be equal.
if (radioModeInfo1 != null
&& radioModeInfo0.ifaceInfos.size() != radioModeInfo1.ifaceInfos.size()) {
mLog.e("Unexpected number of iface info in list "
+ radioModeInfo0.ifaceInfos.size() + ", "
+ radioModeInfo1.ifaceInfos.size());
return;
}
int numIfacesOnEachRadio = radioModeInfo0.ifaceInfos.size();
// Only 1 or 2 ifaces should be present on each radio.
if (numIfacesOnEachRadio == 0 || numIfacesOnEachRadio > 2) {
mLog.e("Unexpected number of iface info in list " + numIfacesOnEachRadio);
return;
}
Runnable runnable = null;
// 2 ifaces simultaneous on 2 radios.
if (radioModeInfoList.size() == 2 && numIfacesOnEachRadio == 1) {
// Iface on radio0 should be different from the iface on radio1 for DBS & SBS.
if (areSameIfaceNames(radioModeInfo0.ifaceInfos, radioModeInfo1.ifaceInfos)) {
mLog.e("Unexpected for both radio infos to have same iface");
return;
}
if (radioModeInfo0.bandInfo != radioModeInfo1.bandInfo) {
runnable = () -> {
handler.onDbs();
};
} else {
runnable = () -> {
handler.onSbs(radioModeInfo0.bandInfo);
};
}
// 2 ifaces time sharing on 1 radio.
} else if (radioModeInfoList.size() == 1 && numIfacesOnEachRadio == 2) {
IfaceInfo ifaceInfo0 = radioModeInfo0.ifaceInfos.get(0);
IfaceInfo ifaceInfo1 = radioModeInfo0.ifaceInfos.get(1);
if (ifaceInfo0.channel != ifaceInfo1.channel) {
runnable = () -> {
handler.onMcc(radioModeInfo0.bandInfo);
};
} else {
runnable = () -> {
handler.onScc(radioModeInfo0.bandInfo);
};
}
} else {
// Not concurrency scenario, uninteresting...
}
if (runnable != null) mHalEventHandler.post(runnable);
}
}
/**
* Hal Device Manager callbacks.
*/
public class HalDeviceManagerStatusListener implements HalDeviceManager.ManagerStatusListener {
@Override
public void onStatusChanged() {
boolean isReady = mHalDeviceManager.isReady();
boolean isStarted = mHalDeviceManager.isStarted();
mVerboseLog.i("Device Manager onStatusChanged. isReady(): " + isReady
+ ", isStarted(): " + isStarted);
if (!isReady) {
// Probably something unpleasant, e.g. the server died
WifiNative.VendorHalDeathEventHandler handler;
synchronized (sLock) {
clearState();
handler = mDeathEventHandler;
}
if (handler != null) {
handler.onDeath();
}
}
if (isStarted) {
synchronized (sLock) {
if (mStaIfaceAvailableForRequestListener != null) {
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
IfaceType.STA, mStaIfaceAvailableForRequestListener,
mHalEventHandler);
}
if (mApIfaceAvailableForRequestListener != null) {
mHalDeviceManager.registerInterfaceAvailableForRequestListener(
IfaceType.AP, mApIfaceAvailableForRequestListener,
mHalEventHandler);
}
}
}
}
}
}