/*
|
* 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.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.Looper;
|
import android.os.RemoteException;
|
import android.text.TextUtils;
|
import android.util.Log;
|
import android.util.MutableBoolean;
|
import android.util.MutableLong;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.HexDump;
|
import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
|
import com.android.server.wifi.WifiLinkLayerStats.ChannelStats;
|
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 final HalDeviceManager mHalDeviceManager;
|
private final HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
|
private final IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
|
private final ChipEventCallback mIWifiChipEventCallback;
|
private final ChipEventCallbackV12 mIWifiChipEventCallbackV12;
|
|
// 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 Looper mLooper;
|
private final Handler mHalEventHandler;
|
|
public WifiVendorHal(HalDeviceManager halDeviceManager,
|
Looper looper) {
|
mHalDeviceManager = halDeviceManager;
|
mLooper = looper;
|
mHalEventHandler = new Handler(looper);
|
mHalDeviceManagerStatusCallbacks = new HalDeviceManagerStatusListener();
|
mIWifiStaIfaceEventCallback = new StaIfaceEventCallback();
|
mIWifiChipEventCallback = new ChipEventCallback();
|
mIWifiChipEventCallbackV12 = new ChipEventCallbackV12();
|
}
|
|
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(false, 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;
|
}
|
}
|
|
/** 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 lowPrioritySta The requested STA has a low request priority (lower probability of
|
* getting created, higher probability of getting destroyed).
|
* @param destroyedListener Listener to be invoked when the interface is destroyed.
|
* @return iface name on success, null otherwise.
|
*/
|
public String createStaIface(boolean lowPrioritySta,
|
InterfaceDestroyedListener destroyedListener) {
|
synchronized (sLock) {
|
IWifiStaIface iface = mHalDeviceManager.createStaIface(lowPrioritySta,
|
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_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable();
|
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
|
*
|
* @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 int[][] 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 int[][] sStaFeatureCapabilityTranslation = {
|
{WifiManager.WIFI_FEATURE_INFRA_5G,
|
IWifiStaIface.StaIfaceCapabilityMask.STA_5G
|
},
|
{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
|
int wifiFeatureMaskFromStaCapabilities(int capabilities) {
|
int 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 the MAC OUI during scanning.
|
* <p>
|
* An OUI {Organizationally Unique Identifier} is a 24-bit number that
|
* uniquely identifies a vendor or manufacturer.
|
*
|
* @param ifaceName Name of the interface.
|
* @param oui
|
* @return true for success
|
*/
|
public boolean setScanningMacOui(@NonNull String ifaceName, byte[] oui) {
|
if (oui == null) return boolResult(false);
|
if (oui.length != 3) return boolResult(false);
|
synchronized (sLock) {
|
try {
|
IWifiStaIface iface = getStaIface(ifaceName);
|
if (iface == null) return boolResult(false);
|
WifiStatus status = iface.setScanningMacOui(oui);
|
if (!ok(status)) return false;
|
return true;
|
} catch (RemoteException e) {
|
handleRemoteException(e);
|
return false;
|
}
|
}
|
}
|
|
/**
|
* 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 ifaceV12 =
|
getWifiStaIfaceForV1_2Mockable(ifaceName);
|
if (ifaceV12 == null) return boolResult(false);
|
WifiStatus status = ifaceV12.setMacAddress(macByteArray);
|
if (!ok(status)) return false;
|
return true;
|
} catch (RemoteException e) {
|
handleRemoteException(e);
|
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 {
|
android.hardware.wifi.V1_3.IWifiStaIface ifaceV13 =
|
getWifiStaIfaceForV1_3Mockable(ifaceName);
|
if (ifaceV13 == null) return null;
|
AnswerBox box = new AnswerBox();
|
ifaceV13.getFactoryMacAddress((status, macBytes) -> {
|
if (!ok(status)) return;
|
box.mac = MacAddress.fromBytes(macBytes);
|
});
|
return box.mac;
|
} catch (RemoteException e) {
|
handleRemoteException(e);
|
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_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);
|
}
|
|
/**
|
* 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
|
* In that HAL version, behavior depends on if SAR sensor input is considered in this device.
|
* If it is, then whenever the device is near the user body/hand/head, back-off is required.
|
* Otherwise, we should revert to the V1_1 HAL behavior which is only to perform backoff when
|
* a voice call is ongoing.
|
*/
|
private boolean sarPowerBackoffRequired_1_2(SarInfo sarInfo) {
|
/* If SAR sensor is supported, output only dependent on device proximity */
|
if (sarInfo.sarSensorSupported) {
|
return (sarInfo.sensorState != SarInfo.SAR_SENSOR_FREE_SPACE);
|
}
|
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.
|
* In this HAL version, behavior depends on if SAR sensor input is considered in this device.
|
* If it is, then based on regulatory compliance requirements,
|
* - There is no need to treat NEAR_HAND different from NEAR_BODY, both can be considered
|
* near the user body.
|
* - Running in softAP mode can be treated the same way as running a voice call from tx power
|
* backoff perspective.
|
* If SAR sensor input is not supported in this device, but SoftAP is,
|
* 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 neither SAR sensor, nor SoftAP is 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.sarSensorSupported) {
|
switch(sarInfo.sensorState) {
|
case SarInfo.SAR_SENSOR_NEAR_BODY:
|
case SarInfo.SAR_SENSOR_NEAR_HAND:
|
if (sarInfo.isVoiceCall || sarInfo.isWifiSapEnabled) {
|
return android.hardware.wifi.V1_2.IWifiChip
|
.TxPowerScenario.ON_BODY_CELL_ON;
|
} else {
|
return android.hardware.wifi.V1_2.IWifiChip
|
.TxPowerScenario.ON_BODY_CELL_OFF;
|
}
|
|
case SarInfo.SAR_SENSOR_NEAR_HEAD:
|
if (sarInfo.isVoiceCall || sarInfo.isWifiSapEnabled) {
|
return android.hardware.wifi.V1_2.IWifiChip
|
.TxPowerScenario.ON_HEAD_CELL_ON;
|
} else {
|
return android.hardware.wifi.V1_2.IWifiChip
|
.TxPowerScenario.ON_HEAD_CELL_OFF;
|
}
|
|
default:
|
throw new IllegalArgumentException("bad scenario: Invalid sensor state");
|
}
|
} else 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 Sensors and SoftAP 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.
|
* Note: This method is called whether SAR sensor is supported or not. The passed SarInfo object
|
* contains a flag to indicate the SAR sensor support.
|
*
|
* @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;
|
}
|
}
|
|
// 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));
|
});
|
}
|
}
|
|
/**
|
* 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);
|
}
|
|
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);
|
}
|
|
private boolean areSameIfaces(List<IfaceInfo> ifaceList1, List<IfaceInfo> ifaceList2) {
|
return ifaceList1.containsAll(ifaceList2);
|
}
|
|
@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;
|
}
|
// 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) {
|
handler.onDbs();
|
} else {
|
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) {
|
handler.onMcc(radioModeInfo0.bandInfo);
|
} else {
|
handler.onScc(radioModeInfo0.bandInfo);
|
}
|
} else {
|
// Not concurrency scenario, uninteresting...
|
}
|
}
|
}
|
|
/**
|
* 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();
|
}
|
}
|
}
|
}
|
}
|