974 lines
37 KiB
Java
974 lines
37 KiB
Java
/*
|
|
* Copyright (C) 2021 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.content.Context;
|
|
import android.hardware.wifi.hostapd.ApInfo;
|
|
import android.hardware.wifi.hostapd.BandMask;
|
|
import android.hardware.wifi.hostapd.ChannelBandwidth;
|
|
import android.hardware.wifi.hostapd.ChannelParams;
|
|
import android.hardware.wifi.hostapd.ClientInfo;
|
|
import android.hardware.wifi.hostapd.DebugLevel;
|
|
import android.hardware.wifi.hostapd.EncryptionType;
|
|
import android.hardware.wifi.hostapd.FrequencyRange;
|
|
import android.hardware.wifi.hostapd.Generation;
|
|
import android.hardware.wifi.hostapd.HwModeParams;
|
|
import android.hardware.wifi.hostapd.IHostapd;
|
|
import android.hardware.wifi.hostapd.IHostapdCallback;
|
|
import android.hardware.wifi.hostapd.Ieee80211ReasonCode;
|
|
import android.hardware.wifi.hostapd.IfaceParams;
|
|
import android.hardware.wifi.hostapd.NetworkParams;
|
|
import android.net.MacAddress;
|
|
import android.net.wifi.ScanResult;
|
|
import android.net.wifi.SoftApConfiguration;
|
|
import android.net.wifi.SoftApConfiguration.BandType;
|
|
import android.net.wifi.SoftApInfo;
|
|
import android.net.wifi.WifiAnnotations;
|
|
import android.net.wifi.WifiManager;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.IBinder.DeathRecipient;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.os.ServiceSpecificException;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.modules.utils.build.SdkLevel;
|
|
import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
|
|
import com.android.server.wifi.util.ApConfigUtil;
|
|
import com.android.server.wifi.util.NativeUtil;
|
|
import com.android.wifi.resources.R;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import javax.annotation.concurrent.ThreadSafe;
|
|
|
|
/**
|
|
* To maintain thread-safety, the locking protocol is that every non-static method (regardless of
|
|
* access level) acquires mLock.
|
|
*/
|
|
@ThreadSafe
|
|
/** The implementation of IHostapdHal which based on Stable AIDL interface */
|
|
public class HostapdHalAidlImp implements IHostapdHal {
|
|
private static final String TAG = "HostapdHalAidlImp";
|
|
private static final String HAL_INSTANCE_NAME = IHostapd.DESCRIPTOR + "/default";
|
|
@VisibleForTesting
|
|
public static final long WAIT_FOR_DEATH_TIMEOUT_MS = 50L;
|
|
|
|
private final Object mLock = new Object();
|
|
private boolean mVerboseLoggingEnabled = false;
|
|
private boolean mVerboseHalLoggingEnabled = false;
|
|
private final Context mContext;
|
|
private final Handler mEventHandler;
|
|
|
|
// Hostapd HAL interface objects
|
|
private IHostapd mIHostapd;
|
|
private HashMap<String, Runnable> mSoftApFailureListeners = new HashMap<>();
|
|
private WifiNative.SoftApHalCallback mSoftApEventCallback;
|
|
private Set<String> mActiveInstances = new HashSet<>();
|
|
private HostapdDeathEventHandler mDeathEventHandler;
|
|
private boolean mServiceDeclared = false;
|
|
private CountDownLatch mWaitForDeathLatch;
|
|
|
|
/**
|
|
* Default death recipient. Called any time the service dies.
|
|
*/
|
|
private class HostapdDeathRecipient implements DeathRecipient {
|
|
private final IBinder mWho;
|
|
@Override
|
|
/* Do nothing as we override the default function binderDied(IBinder who). */
|
|
public void binderDied() {
|
|
synchronized (mLock) {
|
|
Log.w(TAG, "IHostapd/IHostapd died. who " + mWho + " service "
|
|
+ getServiceBinderMockable());
|
|
if (mWho == getServiceBinderMockable()) {
|
|
if (mWaitForDeathLatch != null) {
|
|
mWaitForDeathLatch.countDown();
|
|
}
|
|
mEventHandler.post(() -> {
|
|
synchronized (mLock) {
|
|
Log.w(TAG, "Handle IHostapd/IHostapd died.");
|
|
hostapdServiceDiedHandler(mWho);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
HostapdDeathRecipient(IBinder who) {
|
|
mWho = who;
|
|
}
|
|
}
|
|
|
|
public HostapdHalAidlImp(@NonNull Context context, @NonNull Handler handler) {
|
|
mContext = context;
|
|
mEventHandler = handler;
|
|
Log.d(TAG, "init HostapdHalAidlImp");
|
|
}
|
|
|
|
/**
|
|
* Enable/Disable verbose logging.
|
|
*
|
|
* @param enable true to enable, false to disable.
|
|
*/
|
|
@Override
|
|
public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) {
|
|
synchronized (mLock) {
|
|
mVerboseLoggingEnabled = verboseEnabled;
|
|
mVerboseHalLoggingEnabled = halVerboseEnabled;
|
|
setDebugParams();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not the hostapd supports getting the AP info from the callback.
|
|
*/
|
|
@Override
|
|
public boolean isApInfoCallbackSupported() {
|
|
// Supported in the AIDL implementation
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the IHostapd service is declared, and therefore should be available.
|
|
* @return true if the IHostapd service is declared
|
|
*/
|
|
@Override
|
|
public boolean initialize() {
|
|
synchronized (mLock) {
|
|
if (mVerboseLoggingEnabled) {
|
|
Log.i(TAG, "Checking if IHostapd service is declared.");
|
|
}
|
|
mServiceDeclared = serviceDeclared();
|
|
return mServiceDeclared;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register for callbacks with the hostapd service. On service-side event,
|
|
* the hostapd service will trigger our IHostapdCallback implementation, which
|
|
* in turn calls the proper SoftApHalCallback registered with us by WifiNative.
|
|
*/
|
|
private boolean registerCallback(IHostapdCallback callback) {
|
|
synchronized (mLock) {
|
|
String methodStr = "registerCallback";
|
|
try {
|
|
mIHostapd.registerCallback(callback);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
} catch (ServiceSpecificException e) {
|
|
handleServiceSpecificException(e, methodStr);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the provided callback handler for SoftAp events.
|
|
* <p>
|
|
* Note that only one callback can be registered at a time - any registration overrides previous
|
|
* registrations.
|
|
*
|
|
* @param ifaceName Name of the interface.
|
|
* @param listener Callback listener for AP events.
|
|
* @return true on success, false on failure.
|
|
*/
|
|
@Override
|
|
public boolean registerApCallback(@NonNull String ifaceName,
|
|
@NonNull WifiNative.SoftApHalCallback callback) {
|
|
// TODO(b/195980798) : Create a hashmap to associate the listener with the ifaceName
|
|
synchronized (mLock) {
|
|
if (callback == null) {
|
|
Log.e(TAG, "registerApCallback called with a null callback");
|
|
return false;
|
|
}
|
|
mSoftApEventCallback = callback;
|
|
Log.i(TAG, "registerApCallback Successful in " + ifaceName);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add and start a new access point.
|
|
*
|
|
* @param ifaceName Name of the interface.
|
|
* @param config Configuration to use for the AP.
|
|
* @param isMetered Indicates if the network is metered or not.
|
|
* @param onFailureListener A runnable to be triggered on failure.
|
|
* @return true on success, false otherwise.
|
|
*/
|
|
@Override
|
|
public boolean addAccessPoint(@NonNull String ifaceName, @NonNull SoftApConfiguration config,
|
|
boolean isMetered, Runnable onFailureListener) {
|
|
synchronized (mLock) {
|
|
final String methodStr = "addAccessPoint";
|
|
Log.d(TAG, methodStr + ": " + ifaceName);
|
|
if (!checkHostapdAndLogFailure(methodStr)) {
|
|
return false;
|
|
}
|
|
try {
|
|
IfaceParams ifaceParams = prepareIfaceParams(ifaceName, config);
|
|
NetworkParams nwParams = prepareNetworkParams(isMetered, config);
|
|
if (ifaceParams == null || nwParams == null) {
|
|
Log.e(TAG, "addAccessPoint parameters could not be prepared.");
|
|
return false;
|
|
}
|
|
mIHostapd.addAccessPoint(ifaceParams, nwParams);
|
|
mSoftApFailureListeners.put(ifaceName, onFailureListener);
|
|
return true;
|
|
} catch (IllegalArgumentException e) {
|
|
Log.e(TAG, "Unrecognized apBand: " + config.getBand());
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
} catch (ServiceSpecificException e) {
|
|
handleServiceSpecificException(e, methodStr);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a previously started access point.
|
|
*
|
|
* @param ifaceName Name of the interface.
|
|
* @return true on success, false otherwise.
|
|
*/
|
|
@Override
|
|
public boolean removeAccessPoint(@NonNull String ifaceName) {
|
|
synchronized (mLock) {
|
|
final String methodStr = "removeAccessPoint";
|
|
if (!checkHostapdAndLogFailure(methodStr)) {
|
|
return false;
|
|
}
|
|
try {
|
|
mSoftApFailureListeners.remove(ifaceName);
|
|
mSoftApEventCallback = null;
|
|
mIHostapd.removeAccessPoint(ifaceName);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
} catch (ServiceSpecificException e) {
|
|
handleServiceSpecificException(e, methodStr);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a previously connected client.
|
|
*
|
|
* @param ifaceName Name of the interface.
|
|
* @param client Mac Address of the client.
|
|
* @param reasonCode One of disconnect reason code which defined in {@link WifiManager}.
|
|
* @return true on success, false otherwise.
|
|
*/
|
|
@Override
|
|
public boolean forceClientDisconnect(@NonNull String ifaceName,
|
|
@NonNull MacAddress client, int reasonCode) {
|
|
synchronized (mLock) {
|
|
final String methodStr = "forceClientDisconnect";
|
|
try {
|
|
if (!checkHostapdAndLogFailure(methodStr)) {
|
|
return false;
|
|
}
|
|
byte[] clientMacByteArray = client.toByteArray();
|
|
int disconnectReason;
|
|
switch (reasonCode) {
|
|
case WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER:
|
|
disconnectReason = Ieee80211ReasonCode.WLAN_REASON_PREV_AUTH_NOT_VALID;
|
|
break;
|
|
case WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS:
|
|
disconnectReason = Ieee80211ReasonCode.WLAN_REASON_DISASSOC_AP_BUSY;
|
|
break;
|
|
case WifiManager.SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED:
|
|
disconnectReason = Ieee80211ReasonCode.WLAN_REASON_UNSPECIFIED;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException(
|
|
"Unknown disconnect reason code:" + reasonCode);
|
|
}
|
|
mIHostapd.forceClientDisconnect(ifaceName, clientMacByteArray, disconnectReason);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
} catch (ServiceSpecificException e) {
|
|
handleServiceSpecificException(e, methodStr);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a death notification for hostapd.
|
|
* @return Returns true on success.
|
|
*/
|
|
@Override
|
|
public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) {
|
|
synchronized (mLock) {
|
|
if (mDeathEventHandler != null) {
|
|
Log.e(TAG, "Death handler already present");
|
|
}
|
|
mDeathEventHandler = handler;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deregisters a death notification for hostapd.
|
|
* @return Returns true on success.
|
|
*/
|
|
@Override
|
|
public boolean deregisterDeathHandler() {
|
|
synchronized (mLock) {
|
|
if (mDeathEventHandler == null) {
|
|
Log.e(TAG, "No Death handler present");
|
|
return false;
|
|
}
|
|
mDeathEventHandler = null;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle hostapd death.
|
|
*/
|
|
private void hostapdServiceDiedHandler(IBinder who) {
|
|
synchronized (mLock) {
|
|
if (who != getServiceBinderMockable()) {
|
|
Log.w(TAG, "Ignoring stale death recipient notification");
|
|
return;
|
|
}
|
|
mIHostapd = null;
|
|
if (mDeathEventHandler != null) {
|
|
mDeathEventHandler.onDeath();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class HostapdCallback extends IHostapdCallback.Stub {
|
|
@Override
|
|
public void onFailure(String ifaceName, String instanceName) {
|
|
Log.w(TAG, "Failure on iface " + ifaceName + ", instance: " + instanceName);
|
|
Runnable onFailureListener = mSoftApFailureListeners.get(ifaceName);
|
|
if (onFailureListener != null) {
|
|
mActiveInstances.remove(instanceName);
|
|
if (mActiveInstances.size() == 0) {
|
|
onFailureListener.run();
|
|
} else if (mSoftApEventCallback != null) {
|
|
mSoftApEventCallback.onInstanceFailure(instanceName);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onApInstanceInfoChanged(ApInfo info) {
|
|
Log.v(TAG, "onApInstanceInfoChanged on " + info.ifaceName + " / "
|
|
+ info.apIfaceInstance);
|
|
try {
|
|
if (mSoftApEventCallback != null) {
|
|
mSoftApEventCallback.onInfoChanged(info.apIfaceInstance, info.freqMhz,
|
|
mapHalChannelBandwidthToSoftApInfo(info.channelBandwidth),
|
|
mapHalGenerationToWifiStandard(info.generation),
|
|
MacAddress.fromBytes(info.apIfaceInstanceMacAddress));
|
|
}
|
|
mActiveInstances.add(info.apIfaceInstance);
|
|
} catch (IllegalArgumentException iae) {
|
|
Log.e(TAG, " Invalid apIfaceInstanceMacAddress, " + iae);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onConnectedClientsChanged(ClientInfo info) {
|
|
try {
|
|
Log.d(TAG, "onConnectedClientsChanged on " + info.ifaceName
|
|
+ " / " + info.apIfaceInstance
|
|
+ " and Mac is " + MacAddress.fromBytes(info.clientAddress).toString()
|
|
+ " isConnected: " + info.isConnected);
|
|
if (mSoftApEventCallback != null) {
|
|
mSoftApEventCallback.onConnectedClientsChanged(info.apIfaceInstance,
|
|
MacAddress.fromBytes(info.clientAddress), info.isConnected);
|
|
}
|
|
} catch (IllegalArgumentException iae) {
|
|
Log.e(TAG, " Invalid clientAddress, " + iae);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getInterfaceHash() {
|
|
return IHostapdCallback.HASH;
|
|
}
|
|
|
|
@Override
|
|
public int getInterfaceVersion() {
|
|
return IHostapdCallback.VERSION;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Signals whether Initialization started and found the declared service
|
|
*/
|
|
@Override
|
|
public boolean isInitializationStarted() {
|
|
synchronized (mLock) {
|
|
return mServiceDeclared;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Signals whether Initialization completed successfully.
|
|
*/
|
|
@Override
|
|
public boolean isInitializationComplete() {
|
|
synchronized (mLock) {
|
|
return mIHostapd != null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates whether the AIDL service is declared
|
|
*/
|
|
public static boolean serviceDeclared() {
|
|
// Service Manager API ServiceManager#isDeclared supported after T.
|
|
if (!SdkLevel.isAtLeastT()) {
|
|
return false;
|
|
}
|
|
return ServiceManager.isDeclared(HAL_INSTANCE_NAME);
|
|
}
|
|
|
|
/**
|
|
* Wrapper functions created to be mockable in unit tests
|
|
*/
|
|
@VisibleForTesting
|
|
protected IBinder getServiceBinderMockable() {
|
|
synchronized (mLock) {
|
|
if (mIHostapd == null) return null;
|
|
return mIHostapd.asBinder();
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
protected IHostapd getHostapdMockable() {
|
|
synchronized (mLock) {
|
|
return IHostapd.Stub.asInterface(
|
|
ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start hostapd daemon
|
|
*
|
|
* @return true when succeed, otherwise false.
|
|
*/
|
|
@Override
|
|
public boolean startDaemon() {
|
|
synchronized (mLock) {
|
|
final String methodStr = "startDaemon";
|
|
mIHostapd = getHostapdMockable();
|
|
if (mIHostapd == null) {
|
|
Log.e(TAG, "Service hostapd wasn't found.");
|
|
return false;
|
|
}
|
|
Log.i(TAG, "Obtained IHostApd binder.");
|
|
|
|
try {
|
|
IBinder serviceBinder = getServiceBinderMockable();
|
|
if (serviceBinder == null) return false;
|
|
mWaitForDeathLatch = null;
|
|
serviceBinder.linkToDeath(new HostapdDeathRecipient(serviceBinder), /* flags= */ 0);
|
|
setDebugParams();
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
return false;
|
|
}
|
|
|
|
if (!registerCallback(new HostapdCallback())) {
|
|
Log.e(TAG, "Failed to register callback, stopping hostapd AIDL startup");
|
|
mIHostapd = null;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminate the hostapd daemon & wait for it's death.
|
|
*/
|
|
@Override
|
|
public void terminate() {
|
|
synchronized (mLock) {
|
|
final String methodStr = "terminate";
|
|
if (!checkHostapdAndLogFailure(methodStr)) {
|
|
return;
|
|
}
|
|
Log.i(TAG, "Terminate HostApd Service.");
|
|
try {
|
|
mWaitForDeathLatch = new CountDownLatch(1);
|
|
mIHostapd.terminate();
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
}
|
|
}
|
|
|
|
// Now wait for death listener callback to confirm that it's dead.
|
|
try {
|
|
if (!mWaitForDeathLatch.await(WAIT_FOR_DEATH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
|
Log.w(TAG, "Timed out waiting for confirmation of hostapd death");
|
|
} else {
|
|
Log.d(TAG, "Got service death confirmation");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
Log.w(TAG, "Failed to wait for hostapd death");
|
|
}
|
|
}
|
|
|
|
private void handleRemoteException(RemoteException e, String methodStr) {
|
|
synchronized (mLock) {
|
|
hostapdServiceDiedHandler(getServiceBinderMockable());
|
|
Log.e(TAG, "IHostapd." + methodStr + " failed with exception", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the debug log level for hostapd.
|
|
*
|
|
* @return true if request is sent successfully, false otherwise.
|
|
*/
|
|
private boolean setDebugParams() {
|
|
synchronized (mLock) {
|
|
final String methodStr = "setDebugParams";
|
|
if (!checkHostapdAndLogFailure(methodStr)) {
|
|
return false;
|
|
}
|
|
try {
|
|
mIHostapd.setDebugParams(mVerboseHalLoggingEnabled
|
|
? DebugLevel.DEBUG : DebugLevel.INFO);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
handleRemoteException(e, methodStr);
|
|
} catch (ServiceSpecificException e) {
|
|
handleServiceSpecificException(e, methodStr);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static int getEncryptionType(SoftApConfiguration localConfig) {
|
|
int encryptionType;
|
|
switch (localConfig.getSecurityType()) {
|
|
case SoftApConfiguration.SECURITY_TYPE_OPEN:
|
|
encryptionType = EncryptionType.NONE;
|
|
break;
|
|
case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK:
|
|
encryptionType = EncryptionType.WPA2;
|
|
break;
|
|
case SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION:
|
|
encryptionType = EncryptionType.WPA3_SAE_TRANSITION;
|
|
break;
|
|
case SoftApConfiguration.SECURITY_TYPE_WPA3_SAE:
|
|
encryptionType = EncryptionType.WPA3_SAE;
|
|
break;
|
|
case SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION:
|
|
encryptionType = EncryptionType.WPA3_OWE_TRANSITION;
|
|
break;
|
|
case SoftApConfiguration.SECURITY_TYPE_WPA3_OWE:
|
|
encryptionType = EncryptionType.WPA3_OWE;
|
|
break;
|
|
default:
|
|
// We really shouldn't default to None, but this was how NetworkManagementService
|
|
// used to do this.
|
|
encryptionType = EncryptionType.NONE;
|
|
break;
|
|
}
|
|
return encryptionType;
|
|
}
|
|
|
|
private static int getHalBandMask(int apBand) throws IllegalArgumentException {
|
|
int bandMask = 0;
|
|
|
|
if (!ApConfigUtil.isBandValid(apBand)) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_2GHZ)) {
|
|
bandMask |= BandMask.BAND_2_GHZ;
|
|
}
|
|
if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_5GHZ)) {
|
|
bandMask |= BandMask.BAND_5_GHZ;
|
|
}
|
|
if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_6GHZ)) {
|
|
bandMask |= BandMask.BAND_6_GHZ;
|
|
}
|
|
if (ApConfigUtil.containsBand(apBand, SoftApConfiguration.BAND_60GHZ)) {
|
|
bandMask |= BandMask.BAND_60_GHZ;
|
|
}
|
|
|
|
return bandMask;
|
|
}
|
|
|
|
/**
|
|
* Prepare the acsChannelFreqRangesMhz in ChannelParams.
|
|
*/
|
|
private void prepareAcsChannelFreqRangesMhz(ChannelParams channelParams,
|
|
@BandType int band, SoftApConfiguration config) {
|
|
List<FrequencyRange> ranges = new ArrayList<>();
|
|
if ((band & SoftApConfiguration.BAND_2GHZ) != 0) {
|
|
ranges.addAll(toAcsFreqRanges(SoftApConfiguration.BAND_2GHZ, config));
|
|
}
|
|
if ((band & SoftApConfiguration.BAND_5GHZ) != 0) {
|
|
ranges.addAll(toAcsFreqRanges(SoftApConfiguration.BAND_5GHZ, config));
|
|
}
|
|
if ((band & SoftApConfiguration.BAND_6GHZ) != 0) {
|
|
ranges.addAll(toAcsFreqRanges(SoftApConfiguration.BAND_6GHZ, config));
|
|
}
|
|
channelParams.acsChannelFreqRangesMhz = ranges.toArray(
|
|
new FrequencyRange[ranges.size()]);
|
|
}
|
|
|
|
/**
|
|
* Convert OEM and SoftApConfiguration channel restrictions to a list of FreqRanges
|
|
*/
|
|
private List<FrequencyRange> toAcsFreqRanges(@BandType int band, SoftApConfiguration config) {
|
|
List<Integer> allowedChannelList;
|
|
List<FrequencyRange> frequencyRanges = new ArrayList<>();
|
|
|
|
if (!ApConfigUtil.isBandValid(band) || ApConfigUtil.isMultiband(band)) {
|
|
Log.e(TAG, "Invalid band : " + band);
|
|
return frequencyRanges;
|
|
}
|
|
|
|
String oemConfig;
|
|
switch (band) {
|
|
case SoftApConfiguration.BAND_2GHZ:
|
|
oemConfig = mContext.getResources().getString(
|
|
R.string.config_wifiSoftap2gChannelList);
|
|
break;
|
|
case SoftApConfiguration.BAND_5GHZ:
|
|
oemConfig = mContext.getResources().getString(
|
|
R.string.config_wifiSoftap5gChannelList);
|
|
break;
|
|
case SoftApConfiguration.BAND_6GHZ:
|
|
oemConfig = mContext.getResources().getString(
|
|
R.string.config_wifiSoftap6gChannelList);
|
|
break;
|
|
default:
|
|
return frequencyRanges;
|
|
}
|
|
|
|
allowedChannelList = ApConfigUtil.collectAllowedAcsChannels(band, oemConfig,
|
|
SdkLevel.isAtLeastT()
|
|
? config.getAllowedAcsChannels(band) : new int[] {});
|
|
if (allowedChannelList.isEmpty()) {
|
|
Log.e(TAG, "Empty list of allowed channels");
|
|
return frequencyRanges;
|
|
}
|
|
Collections.sort(allowedChannelList);
|
|
|
|
// Convert the sorted list to a set of frequency ranges
|
|
boolean rangeStarted = false;
|
|
int prevChannel = -1;
|
|
FrequencyRange freqRange = null;
|
|
for (int channel : allowedChannelList) {
|
|
// Continuation of an existing frequency range
|
|
if (rangeStarted) {
|
|
if (channel == prevChannel + 1) {
|
|
prevChannel = channel;
|
|
continue;
|
|
}
|
|
|
|
// End of the existing frequency range
|
|
freqRange.endMhz = ApConfigUtil.convertChannelToFrequency(prevChannel, band);
|
|
frequencyRanges.add(freqRange);
|
|
// We will continue to start a new frequency range
|
|
}
|
|
|
|
// Beginning of a new frequency range
|
|
freqRange = new FrequencyRange();
|
|
freqRange.startMhz = ApConfigUtil.convertChannelToFrequency(channel, band);
|
|
rangeStarted = true;
|
|
prevChannel = channel;
|
|
}
|
|
|
|
// End the last range
|
|
freqRange.endMhz = ApConfigUtil.convertChannelToFrequency(prevChannel, band);
|
|
frequencyRanges.add(freqRange);
|
|
|
|
return frequencyRanges;
|
|
}
|
|
|
|
/**
|
|
* Map hal bandwidth to SoftApInfo.
|
|
*
|
|
* @param bandwidth The channel bandwidth of the AP which is defined in the HAL.
|
|
* @return The channel bandwidth in the SoftApinfo.
|
|
*/
|
|
@VisibleForTesting
|
|
public int mapHalChannelBandwidthToSoftApInfo(int channelBandwidth) {
|
|
switch (channelBandwidth) {
|
|
case ChannelBandwidth.BANDWIDTH_20_NOHT:
|
|
return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
|
|
case ChannelBandwidth.BANDWIDTH_20:
|
|
return SoftApInfo.CHANNEL_WIDTH_20MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_40:
|
|
return SoftApInfo.CHANNEL_WIDTH_40MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_80:
|
|
return SoftApInfo.CHANNEL_WIDTH_80MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_80P80:
|
|
return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_160:
|
|
return SoftApInfo.CHANNEL_WIDTH_160MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_320:
|
|
return SoftApInfo.CHANNEL_WIDTH_320MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_2160:
|
|
return SoftApInfo.CHANNEL_WIDTH_2160MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_4320:
|
|
return SoftApInfo.CHANNEL_WIDTH_4320MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_6480:
|
|
return SoftApInfo.CHANNEL_WIDTH_6480MHZ;
|
|
case ChannelBandwidth.BANDWIDTH_8640:
|
|
return SoftApInfo.CHANNEL_WIDTH_8640MHZ;
|
|
default:
|
|
return SoftApInfo.CHANNEL_WIDTH_INVALID;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map SoftApInfo bandwidth to hal.
|
|
*
|
|
* @param channelBandwidth The channel bandwidth as defined in SoftApInfo
|
|
* @return The channel bandwidth as defined in hal
|
|
*/
|
|
@VisibleForTesting
|
|
public int mapSoftApInfoBandwidthToHal(@WifiAnnotations.Bandwidth int channelBandwidth) {
|
|
switch (channelBandwidth) {
|
|
case SoftApInfo.CHANNEL_WIDTH_AUTO:
|
|
return ChannelBandwidth.BANDWIDTH_AUTO;
|
|
case SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT:
|
|
return ChannelBandwidth.BANDWIDTH_20_NOHT;
|
|
case SoftApInfo.CHANNEL_WIDTH_20MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_20;
|
|
case SoftApInfo.CHANNEL_WIDTH_40MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_40;
|
|
case SoftApInfo.CHANNEL_WIDTH_80MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_80;
|
|
case SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_80P80;
|
|
case SoftApInfo.CHANNEL_WIDTH_160MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_160;
|
|
case SoftApInfo.CHANNEL_WIDTH_320MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_320;
|
|
case SoftApInfo.CHANNEL_WIDTH_2160MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_2160;
|
|
case SoftApInfo.CHANNEL_WIDTH_4320MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_4320;
|
|
case SoftApInfo.CHANNEL_WIDTH_6480MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_6480;
|
|
case SoftApInfo.CHANNEL_WIDTH_8640MHZ:
|
|
return ChannelBandwidth.BANDWIDTH_8640;
|
|
default:
|
|
return ChannelBandwidth.BANDWIDTH_INVALID;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map hal generation to wifi standard.
|
|
*
|
|
* @param generation The operation mode of the AP which is defined in HAL.
|
|
* @return The wifi standard in the ScanResult.
|
|
*/
|
|
@VisibleForTesting
|
|
public int mapHalGenerationToWifiStandard(int generation) {
|
|
switch (generation) {
|
|
case Generation.WIFI_STANDARD_LEGACY:
|
|
return ScanResult.WIFI_STANDARD_LEGACY;
|
|
case Generation.WIFI_STANDARD_11N:
|
|
return ScanResult.WIFI_STANDARD_11N;
|
|
case Generation.WIFI_STANDARD_11AC:
|
|
return ScanResult.WIFI_STANDARD_11AC;
|
|
case Generation.WIFI_STANDARD_11AX:
|
|
return ScanResult.WIFI_STANDARD_11AX;
|
|
case Generation.WIFI_STANDARD_11BE:
|
|
return ScanResult.WIFI_STANDARD_11BE;
|
|
case Generation.WIFI_STANDARD_11AD:
|
|
return ScanResult.WIFI_STANDARD_11AD;
|
|
default:
|
|
return ScanResult.WIFI_STANDARD_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
private NetworkParams prepareNetworkParams(boolean isMetered,
|
|
SoftApConfiguration config) {
|
|
NetworkParams nwParams = new NetworkParams();
|
|
ArrayList<Byte> ssid = NativeUtil.byteArrayToArrayList(config.getWifiSsid().getBytes());
|
|
nwParams.ssid = new byte[ssid.size()];
|
|
for (int i = 0; i < ssid.size(); i++) {
|
|
nwParams.ssid[i] = ssid.get(i);
|
|
}
|
|
|
|
final List<ScanResult.InformationElement> elements = config.getVendorElementsInternal();
|
|
int totalLen = 0;
|
|
for (ScanResult.InformationElement e : elements) {
|
|
totalLen += 2 + e.bytes.length; // 1 byte ID + 1 byte payload len + payload
|
|
}
|
|
nwParams.vendorElements = new byte[totalLen];
|
|
int i = 0;
|
|
for (ScanResult.InformationElement e : elements) {
|
|
nwParams.vendorElements[i++] = (byte) e.id;
|
|
nwParams.vendorElements[i++] = (byte) e.bytes.length;
|
|
for (int j = 0; j < e.bytes.length; j++) {
|
|
nwParams.vendorElements[i++] = e.bytes[j];
|
|
}
|
|
}
|
|
|
|
nwParams.isMetered = isMetered;
|
|
nwParams.isHidden = config.isHiddenSsid();
|
|
nwParams.encryptionType = getEncryptionType(config);
|
|
nwParams.passphrase = (config.getPassphrase() != null)
|
|
? config.getPassphrase() : "";
|
|
|
|
if (nwParams.ssid == null || nwParams.passphrase == null) {
|
|
return null;
|
|
}
|
|
return nwParams;
|
|
}
|
|
|
|
private IfaceParams prepareIfaceParams(String ifaceName, SoftApConfiguration config)
|
|
throws IllegalArgumentException {
|
|
IfaceParams ifaceParams = new IfaceParams();
|
|
ifaceParams.name = ifaceName;
|
|
ifaceParams.hwModeParams = prepareHwModeParams(config);
|
|
ifaceParams.channelParams = prepareChannelParamsList(config);
|
|
if (ifaceParams.name == null || ifaceParams.hwModeParams == null
|
|
|| ifaceParams.channelParams == null) {
|
|
return null;
|
|
}
|
|
return ifaceParams;
|
|
}
|
|
|
|
private HwModeParams prepareHwModeParams(SoftApConfiguration config) {
|
|
HwModeParams hwModeParams = new HwModeParams();
|
|
hwModeParams.enable80211N = true;
|
|
hwModeParams.enable80211AC = mContext.getResources().getBoolean(
|
|
R.bool.config_wifi_softap_ieee80211ac_supported);
|
|
hwModeParams.enable80211AX = ApConfigUtil.isIeee80211axSupported(mContext);
|
|
//Update 80211ax support with the configuration.
|
|
hwModeParams.enable80211AX &= config.isIeee80211axEnabledInternal();
|
|
hwModeParams.enable6GhzBand = ApConfigUtil.isBandSupported(
|
|
SoftApConfiguration.BAND_6GHZ, mContext);
|
|
hwModeParams.enableHeSingleUserBeamformer = mContext.getResources().getBoolean(
|
|
R.bool.config_wifiSoftapHeSuBeamformerSupported);
|
|
hwModeParams.enableHeSingleUserBeamformee = mContext.getResources().getBoolean(
|
|
R.bool.config_wifiSoftapHeSuBeamformeeSupported);
|
|
hwModeParams.enableHeMultiUserBeamformer = mContext.getResources().getBoolean(
|
|
R.bool.config_wifiSoftapHeMuBeamformerSupported);
|
|
hwModeParams.enableHeTargetWakeTime = mContext.getResources().getBoolean(
|
|
R.bool.config_wifiSoftapHeTwtSupported);
|
|
hwModeParams.enable80211BE = ApConfigUtil.isIeee80211beSupported(mContext);
|
|
//Update 80211be support with the configuration.
|
|
hwModeParams.enable80211BE &= config.isIeee80211beEnabledInternal();
|
|
|
|
if (SdkLevel.isAtLeastT()) {
|
|
hwModeParams.maximumChannelBandwidth =
|
|
mapSoftApInfoBandwidthToHal(config.getMaxChannelBandwidth());
|
|
} else {
|
|
hwModeParams.maximumChannelBandwidth = ChannelBandwidth.BANDWIDTH_AUTO;
|
|
}
|
|
return hwModeParams;
|
|
}
|
|
|
|
private ChannelParams[] prepareChannelParamsList(SoftApConfiguration config)
|
|
throws IllegalArgumentException {
|
|
int nChannels = 1;
|
|
boolean repeatBand = false;
|
|
if (SdkLevel.isAtLeastS()) {
|
|
nChannels = config.getChannels().size();
|
|
}
|
|
if (config.getSecurityType()
|
|
== SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION) {
|
|
nChannels = 2;
|
|
repeatBand = true;
|
|
}
|
|
ChannelParams[] channelParamsList = new ChannelParams[nChannels];
|
|
for (int i = 0; i < nChannels; i++) {
|
|
int band = config.getBand();
|
|
int channel = config.getChannel();
|
|
if (SdkLevel.isAtLeastS() && !repeatBand) {
|
|
band = config.getChannels().keyAt(i);
|
|
channel = config.getChannels().valueAt(i);
|
|
}
|
|
channelParamsList[i] = new ChannelParams();
|
|
channelParamsList[i].channel = channel;
|
|
channelParamsList[i].enableAcs = ApConfigUtil.isAcsSupported(mContext)
|
|
&& channel == 0;
|
|
channelParamsList[i].bandMask = getHalBandMask(band);
|
|
channelParamsList[i].acsChannelFreqRangesMhz = new FrequencyRange[0];
|
|
if (channelParamsList[i].enableAcs) {
|
|
channelParamsList[i].acsShouldExcludeDfs = !mContext.getResources()
|
|
.getBoolean(R.bool.config_wifiSoftapAcsIncludeDfs);
|
|
if (ApConfigUtil.isSendFreqRangesNeeded(band, mContext, config)) {
|
|
prepareAcsChannelFreqRangesMhz(channelParamsList[i], band, config);
|
|
}
|
|
}
|
|
if (channelParamsList[i].acsChannelFreqRangesMhz == null) {
|
|
return null;
|
|
}
|
|
}
|
|
return channelParamsList;
|
|
}
|
|
|
|
/**
|
|
* Returns false if Hostapd is null, and logs failure to call methodStr
|
|
*/
|
|
private boolean checkHostapdAndLogFailure(String methodStr) {
|
|
synchronized (mLock) {
|
|
if (mIHostapd == null) {
|
|
Log.e(TAG, "Can't call " + methodStr + ", IHostapd is null");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logs failure for a service specific exception. Error codes are defined in HostapdStatusCode
|
|
*/
|
|
private void handleServiceSpecificException(
|
|
ServiceSpecificException exception, String methodStr) {
|
|
synchronized (mLock) {
|
|
Log.e(TAG, "IHostapd." + methodStr + " failed: " + exception.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dump information about the AIDL implementation.
|
|
*
|
|
* TODO (b/202302891) Log version information once we freeze the AIDL interface
|
|
*/
|
|
public void dump(PrintWriter pw) {
|
|
pw.println("AIDL interface version: 1 (initial)");
|
|
}
|
|
}
|