blob: 969d02e99168ffad4797e86923371cb8a6a7818f [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import static com.android.server.wifi.HalDeviceManagerUtil.jsonToStaticChipInfo;
import static com.android.server.wifi.HalDeviceManagerUtil.staticChipInfoToJson;
import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_STATIC_CHIP_INFO;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.hardware.wifi.V1_0.IWifi;
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.IWifiEventCallback;
import android.hardware.wifi.V1_0.IWifiIface;
import android.hardware.wifi.V1_0.IWifiNanIface;
import android.hardware.wifi.V1_0.IWifiP2pIface;
import android.hardware.wifi.V1_0.IWifiRttController;
import android.hardware.wifi.V1_0.IWifiStaIface;
import android.hardware.wifi.V1_0.IfaceType;
import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
import android.hardware.wifi.V1_0.WifiStatus;
import android.hardware.wifi.V1_0.WifiStatusCode;
import android.hardware.wifi.V1_5.WifiBand;
import android.hardware.wifi.V1_6.IWifiChip.ChipConcurrencyCombination;
import android.hardware.wifi.V1_6.IWifiChip.ChipConcurrencyCombinationLimit;
import android.hardware.wifi.V1_6.IfaceConcurrencyType;
import android.hardware.wifi.V1_6.WifiRadioCombination;
import android.hardware.wifi.V1_6.WifiRadioCombinationMatrix;
import android.hardware.wifi.V1_6.WifiRadioConfiguration;
import android.hidl.manager.V1_0.IServiceNotification;
import android.hidl.manager.V1_2.IServiceManager;
import android.net.wifi.WifiContext;
import android.os.Handler;
import android.os.IHwBinder.DeathRecipient;
import android.os.RemoteException;
import android.os.WorkSource;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.HalDeviceManagerUtil.StaticChipInfo;
import com.android.server.wifi.util.GeneralUtil.Mutable;
import com.android.server.wifi.util.WorkSourceHelper;
import com.android.wifi.resources.R;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Handles device management through the HAL (HIDL) interface.
*/
public class HalDeviceManager {
private static final String TAG = "HalDevMgr";
private static final boolean VDBG = false;
private boolean mDbg = false;
public static final long CHIP_CAPABILITY_ANY = 0L;
private static final long CHIP_CAPABILITY_UNINITIALIZED = -1L;
private static final int DBS_24G_5G_MASK =
(1 << WifiBand.BAND_24GHZ) | (1 << WifiBand.BAND_5GHZ);
private static final int DBS_5G_6G_MASK =
(1 << WifiBand.BAND_5GHZ) | (1 << WifiBand.BAND_6GHZ);
private static final int START_HAL_RETRY_INTERVAL_MS = 20;
// Number of attempts a start() is re-tried. A value of 0 means no retries after a single
// attempt.
@VisibleForTesting
public static final int START_HAL_RETRY_TIMES = 3;
private final Clock mClock;
private final WifiInjector mWifiInjector;
private final Handler mEventHandler;
private WifiDeathRecipient mIWifiDeathRecipient;
private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
private boolean mIsBridgedSoftApSupported;
private boolean mIsStaWithBridgedSoftApConcurrencySupported;
private ArrayMap<IWifiIface, SoftApManager> mSoftApManagers = new ArrayMap<>();
// cache the value for supporting vendor HAL or not
private boolean mIsVendorHalSupported = false;
/**
* Public API for querying interfaces from the HalDeviceManager.
*/
public static final int HDM_CREATE_IFACE_STA = 0;
public static final int HDM_CREATE_IFACE_AP = 1;
public static final int HDM_CREATE_IFACE_AP_BRIDGE = 2;
public static final int HDM_CREATE_IFACE_P2P = 3;
public static final int HDM_CREATE_IFACE_NAN = 4;
@IntDef(flag = false, prefix = { "HDM_CREATE_IFACE_TYPE_" }, value = {
HDM_CREATE_IFACE_STA,
HDM_CREATE_IFACE_AP,
HDM_CREATE_IFACE_AP_BRIDGE,
HDM_CREATE_IFACE_P2P,
HDM_CREATE_IFACE_NAN,
})
public @interface HdmIfaceTypeForCreation {};
public static final SparseIntArray HAL_IFACE_MAP = new SparseIntArray() {{
put(HDM_CREATE_IFACE_STA, IfaceType.STA);
put(HDM_CREATE_IFACE_AP, IfaceType.AP);
put(HDM_CREATE_IFACE_AP_BRIDGE, IfaceType.AP);
put(HDM_CREATE_IFACE_P2P, IfaceType.P2P);
put(HDM_CREATE_IFACE_NAN, IfaceType.NAN);
}};
public static final SparseIntArray REVERSE_HAL_IFACE_MAP = new SparseIntArray() {{
put(IfaceType.STA, HDM_CREATE_IFACE_STA);
put(IfaceType.AP, HDM_CREATE_IFACE_AP);
put(IfaceType.P2P, HDM_CREATE_IFACE_P2P);
put(IfaceType.NAN, HDM_CREATE_IFACE_NAN);
}};
public static final SparseIntArray CONCURRENCY_TYPE_TO_CREATE_TYPE_MAP = new SparseIntArray() {{
put(android.hardware.wifi.V1_6.IfaceConcurrencyType.STA, HDM_CREATE_IFACE_STA);
put(android.hardware.wifi.V1_6.IfaceConcurrencyType.AP, HDM_CREATE_IFACE_AP);
put(android.hardware.wifi.V1_6.IfaceConcurrencyType.AP_BRIDGED,
HDM_CREATE_IFACE_AP_BRIDGE);
put(android.hardware.wifi.V1_6.IfaceConcurrencyType.P2P, HDM_CREATE_IFACE_P2P);
put(android.hardware.wifi.V1_6.IfaceConcurrencyType.NAN, HDM_CREATE_IFACE_NAN);
}};
public static final SparseIntArray IFACE_TYPE_TO_CONCURRENCY_TYPE_MAP = new SparseIntArray() {{
put(IfaceType.STA, android.hardware.wifi.V1_6.IfaceConcurrencyType.STA);
put(IfaceType.AP, android.hardware.wifi.V1_6.IfaceConcurrencyType.AP);
put(IfaceType.P2P, android.hardware.wifi.V1_6.IfaceConcurrencyType.P2P);
put(IfaceType.NAN, android.hardware.wifi.V1_6.IfaceConcurrencyType.NAN);
}};
// public API
public HalDeviceManager(WifiContext context, Clock clock, WifiInjector wifiInjector,
Handler handler) {
Resources res = context.getResources();
mIsBridgedSoftApSupported = res.getBoolean(R.bool.config_wifiBridgedSoftApSupported);
mIsStaWithBridgedSoftApConcurrencySupported =
res.getBoolean(R.bool.config_wifiStaWithBridgedSoftApConcurrencySupported);
mClock = clock;
mWifiInjector = wifiInjector;
mEventHandler = handler;
mIWifiDeathRecipient = new WifiDeathRecipient();
mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
}
/**
* Enables verbose logging.
*/
public void enableVerboseLogging(boolean verboseEnabled) {
mDbg = verboseEnabled;
if (VDBG) {
mDbg = true; // just override
}
}
/**
* Actually starts the HalDeviceManager: separate from constructor since may want to phase
* at a later time.
*
* TODO: if decide that no need for separating construction from initialization (e.g. both are
* done at injector) then move to constructor.
*/
public void initialize() {
initializeInternal();
}
/**
* Register a ManagerStatusListener to get information about the status of the manager. Use the
* isReady() and isStarted() methods to check status immediately after registration and when
* triggered.
*
* It is safe to re-register the same callback object - duplicates are detected and only a
* single copy kept.
*
* @param listener ManagerStatusListener listener object.
* @param handler Handler on which to dispatch listener. Null implies the listener will be
* invoked synchronously from the context of the client which triggered the
* state change.
*/
public void registerStatusListener(@NonNull ManagerStatusListener listener,
@Nullable Handler handler) {
synchronized (mLock) {
if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
Log.w(TAG, "registerStatusListener: duplicate registration ignored");
}
}
}
/**
* Returns whether the vendor HAL is supported on this device or not.
*/
public boolean isSupported() {
return mIsVendorHalSupported;
}
/**
* Returns the current status of the HalDeviceManager: whether or not it is ready to execute
* commands. A return of 'false' indicates that the HAL service (IWifi) is not available. Use
* the registerStatusListener() to listener for status changes.
*/
public boolean isReady() {
return mIsReady;
}
/**
* Returns the current status of Wi-Fi: started (true) or stopped (false).
*
* Note: direct call to HIDL.
*/
public boolean isStarted() {
return isWifiStarted();
}
/**
* Attempts to start Wi-Fi (using HIDL). Returns the success (true) or failure (false) or
* the start operation. Will also dispatch any registered ManagerStatusCallback.onStart() on
* success.
*
* Note: direct call to HIDL.
*/
public boolean start() {
return startWifi();
}
/**
* Stops Wi-Fi. Will also dispatch any registeredManagerStatusCallback.onStop().
*
* Note: direct call to HIDL - failure is not-expected.
*/
public void stop() {
synchronized (mLock) { // prevents race condition
stopWifi();
mWifi = null;
}
}
/**
* HAL device manager status change listener.
*/
public interface ManagerStatusListener {
/**
* Indicates that the status of the HalDeviceManager has changed. Use isReady() and
* isStarted() to obtain status information.
*/
void onStatusChanged();
}
/**
* Return the set of supported interface types across all Wi-Fi chips on the device.
*
* @return A set of IfaceTypes constants (possibly empty, e.g. on error).
*/
public Set<Integer> getSupportedIfaceTypes() {
return getSupportedIfaceTypesInternal(null);
}
/**
* Return the set of supported interface types for the specified Wi-Fi chip.
*
* @return A set of IfaceTypes constants (possibly empty, e.g. on error).
*/
public Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
return getSupportedIfaceTypesInternal(chip);
}
// interface-specific behavior
/**
* Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
* needed and permitted by priority.
*
* @param requiredChipCapabilities The bitmask of Capabilities which are required.
* See IWifiChip.hal for documentation.
* @param destroyedListener Optional (nullable) listener to call when the allocated interface
* is removed. Will only be registered and used if an interface is
* created successfully.
* @param handler Handler on which to dispatch listener. Must be non Null if destroyedListener
* is set. If the this handler is running on the same thread as the client which
* triggered the iface destruction, the listener will be invoked synchronously
* from that context of the client.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return A newly created interface - or null if the interface could not be created.
*/
public IWifiStaIface createStaIface(
long requiredChipCapabilities,
@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
@NonNull WorkSource requestorWs) {
return (IWifiStaIface) createIface(HDM_CREATE_IFACE_STA, requiredChipCapabilities,
destroyedListener, handler, requestorWs);
}
/**
* Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
* needed and permitted by priority.
*
* @param destroyedListener Optional (nullable) listener to call when the allocated interface
* is removed. Will only be registered and used if an interface is
* created successfully.
* @param handler Handler on which to dispatch listener. Must be non Null if destroyedListener
* is set. If the handler is running on the same thread as the client which
* triggered the iface destruction, the listener will be invoked synchronously
* from that context of the client.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return A newly created interface - or null if the interface could not be created.
*/
public IWifiStaIface createStaIface(
@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
@NonNull WorkSource requestorWs) {
return (IWifiStaIface) createStaIface(CHIP_CAPABILITY_ANY,
destroyedListener, handler, requestorWs);
}
/**
* Create AP interface if possible (see createStaIface doc).
*/
public IWifiApIface createApIface(
long requiredChipCapabilities,
@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler,
@NonNull WorkSource requestorWs, boolean isBridged,
@NonNull SoftApManager softApManager) {
if (softApManager == null) {
Log.e(TAG, "Cannot create AP Iface with null SoftApManager");
return null;
}
IWifiApIface apIface = (IWifiApIface) createIface(isBridged ? HDM_CREATE_IFACE_AP_BRIDGE
: HDM_CREATE_IFACE_AP, requiredChipCapabilities, destroyedListener,
handler, requestorWs);
if (apIface != null) {
mSoftApManagers.put(apIface, softApManager);
}
return apIface;
}
/**
* Create P2P interface if possible (see createStaIface doc).
*/
public IWifiP2pIface createP2pIface(
long requiredChipCapabilities,
@Nullable InterfaceDestroyedListener destroyedListener,
@Nullable Handler handler, @NonNull WorkSource requestorWs) {
return (IWifiP2pIface) createIface(HDM_CREATE_IFACE_P2P, requiredChipCapabilities,
destroyedListener, handler, requestorWs);
}
/**
* Create P2P interface if possible (see createStaIface doc).
*/
public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
@Nullable Handler handler, @NonNull WorkSource requestorWs) {
return (IWifiP2pIface) createP2pIface(CHIP_CAPABILITY_ANY,
destroyedListener, handler, requestorWs);
}
/**
* Create NAN interface if possible (see createStaIface doc).
*/
public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
@Nullable Handler handler, @NonNull WorkSource requestorWs) {
return (IWifiNanIface) createIface(HDM_CREATE_IFACE_NAN, CHIP_CAPABILITY_ANY,
destroyedListener, handler, requestorWs);
}
/**
* Removes (releases/destroys) the given interface. Will trigger any registered
* InterfaceDestroyedListeners.
*/
public boolean removeIface(IWifiIface iface) {
boolean success = removeIfaceInternal(iface, /* validateRttController */true);
return success;
}
private InterfaceCacheEntry getInterfaceCacheEntry(IWifiIface iface) {
String name = getName(iface);
int type = getType(iface);
if (VDBG) Log.d(TAG, "getInterfaceCacheEntry: iface(name)=" + name);
synchronized (mLock) {
InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type));
if (cacheEntry == null) {
Log.e(TAG, "getInterfaceCacheEntry: no entry for iface(name)=" + name);
return null;
}
return cacheEntry;
}
}
/**
* Returns the IWifiChip corresponding to the specified interface (or null on error).
*
* Note: clients must not perform chip mode changes or interface management (create/delete)
* operations on IWifiChip directly. However, they can use the IWifiChip interface to perform
* other functions - e.g. calling the debug/trace methods.
*/
public IWifiChip getChip(IWifiIface iface) {
synchronized (mLock) {
InterfaceCacheEntry cacheEntry = getInterfaceCacheEntry(iface);
return (cacheEntry == null) ? null : cacheEntry.chip;
}
}
private WifiChipInfo getChipInfo(IWifiIface iface) {
synchronized (mLock) {
InterfaceCacheEntry cacheEntry = getInterfaceCacheEntry(iface);
if (null == cacheEntry) return null;
WifiChipInfo[] chipInfos = getAllChipInfoCached();
if (null == chipInfos) return null;
for (WifiChipInfo info: chipInfos) {
if (info.chipId == cacheEntry.chipId) {
return info;
}
}
return null;
}
}
private boolean isDbsSupported(IWifiIface iface, int dbsMask) {
synchronized (mLock) {
WifiChipInfo info = getChipInfo(iface);
if (null == info) return false;
// If there is no radio combination information, cache it.
if (null == info.radioCombinationMatrix) {
IWifiChip chip = getChip(iface);
if (null == chip) return false;
info.radioCombinationMatrix = getChipSupportedRadioCombinationsMatrix(chip);
info.radioCombinationLookupTable = convertRadioCombinationMatrixToLookupTable(
info.radioCombinationMatrix);
if (mDbg) {
Log.d(TAG, "radioCombinationMatrix=" + info.radioCombinationMatrix
+ "radioCombinationLookupTable=" + info.radioCombinationLookupTable);
}
}
return info.radioCombinationLookupTable.get(dbsMask);
}
}
/**
* Indicate whether or not 2.4GHz/5GHz DBS is supported.
*
* @param iface The interface on the chip.
* @return true if supported; false, otherwise;
*/
public boolean is24g5gDbsSupported(IWifiIface iface) {
return isDbsSupported(iface, DBS_24G_5G_MASK);
}
/**
* Indicate whether or not 5GHz/6GHz DBS is supported.
*
* @param iface The interface on the chip.
* @return true if supported; false, otherwise;
*/
public boolean is5g6gDbsSupported(IWifiIface iface) {
return isDbsSupported(iface, DBS_5G_6G_MASK);
}
/**
* Replace the requestorWs info for the associated info.
*
* When a new iface is requested via
* {@link #createIface(int, InterfaceDestroyedListener, Handler, WorkSource)}, the clients
* pass in a worksource which includes all the apps that triggered the iface creation. However,
* this list of apps can change during the lifetime of the iface (as new apps request the same
* iface or existing apps release their request for the iface). This API can be invoked multiple
* times to replace the entire requestor info for the provided iface.
*
* Note: This is is wholesale replace of the requestor info. The corresponding client is
* responsible for individual add/remove of apps in the WorkSource passed in.
*/
public boolean replaceRequestorWs(@NonNull IWifiIface iface,
@NonNull WorkSource newRequestorWs) {
String name = getName(iface);
int type = getType(iface);
if (VDBG) {
Log.d(TAG, "replaceRequestorWs: iface(name)=" + name + ", newRequestorWs="
+ newRequestorWs);
}
synchronized (mLock) {
InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type));
if (cacheEntry == null) {
Log.e(TAG, "replaceRequestorWs: no entry for iface(name)=" + name);
return false;
}
cacheEntry.requestorWsHelper = mWifiInjector.makeWsHelper(newRequestorWs);
return true;
}
}
/**
* Register a SubsystemRestartListener to listen to the subsystem restart event from HAL.
* Use the action() to forward the event to SelfRecovery when receiving the event from HAL.
*
* @param listener SubsystemRestartListener listener object.
* @param handler Handler on which to dispatch listener. Null implies the listener will be
* invoked synchronously from the context of the client which triggered the
* state change.
*/
public void registerSubsystemRestartListener(@NonNull SubsystemRestartListener listener,
@Nullable Handler handler) {
if (listener == null) {
Log.wtf(TAG, "registerSubsystemRestartListener with nulls!? listener=" + listener);
return;
}
if (!mSubsystemRestartListener.add(new SubsystemRestartListenerProxy(listener, handler))) {
Log.w(TAG, "registerSubsystemRestartListener: duplicate registration ignored");
}
}
/**
* Register a callback object for RTT life-cycle events. The callback object registration
* indicates that an RTT controller should be created whenever possible. The callback object
* will be called with a new RTT controller whenever it is created (or at registration time
* if an RTT controller already exists). The callback object will also be triggered whenever
* an existing RTT controller is destroyed (the previous copies must be discarded by the
* recipient).
*
* @param callback InterfaceRttControllerLifecycleCallback object.
* @param handler Handler on which to dispatch callback
*/
public void registerRttControllerLifecycleCallback(
@NonNull InterfaceRttControllerLifecycleCallback callback, @NonNull Handler handler) {
if (VDBG) {
Log.d(TAG, "registerRttControllerLifecycleCallback: callback=" + callback + ", handler="
+ handler);
}
if (callback == null || handler == null) {
Log.wtf(TAG, "registerRttControllerLifecycleCallback with nulls!? callback=" + callback
+ ", handler=" + handler);
return;
}
synchronized (mLock) {
InterfaceRttControllerLifecycleCallbackProxy proxy =
new InterfaceRttControllerLifecycleCallbackProxy(callback, handler);
if (!mRttControllerLifecycleCallbacks.add(proxy)) {
Log.d(TAG,
"registerRttControllerLifecycleCallback: registering an existing callback="
+ callback);
return;
}
if (mIWifiRttController == null) {
mIWifiRttController = createRttControllerIfPossible();
}
if (mIWifiRttController != null) {
proxy.onNewRttController(mIWifiRttController);
}
}
}
/**
* Return the name of the input interface or null on error.
*/
public static String getName(IWifiIface iface) {
if (iface == null) {
return "<null>";
}
Mutable<String> nameResp = new Mutable<>();
try {
iface.getName((WifiStatus status, String name) -> {
if (status.code == WifiStatusCode.SUCCESS) {
nameResp.value = name;
} else {
Log.e(TAG, "Error on getName: " + statusString(status));
}
});
} catch (RemoteException e) {
Log.e(TAG, "Exception on getName: " + e);
}
return nameResp.value;
}
/**
* Called when subsystem restart
*/
public interface SubsystemRestartListener {
/**
* Called for subsystem restart event from the HAL.
* It will trigger recovery mechanism in framework.
*/
void onSubsystemRestart();
}
/**
* Called when interface is destroyed.
*/
public interface InterfaceDestroyedListener {
/**
* Called for every interface on which registered when destroyed - whether
* destroyed by releaseIface() or through chip mode change or through Wi-Fi
* going down.
*
* Can be registered when the interface is requested with createXxxIface() - will
* only be valid if the interface creation was successful - i.e. a non-null was returned.
*
* @param ifaceName Name of the interface that was destroyed.
*/
void onDestroyed(@NonNull String ifaceName);
}
/**
* Called on RTT controller lifecycle events. RTT controller is a singleton which will be
* created when possible (after first lifecycle registration) and destroyed if necessary.
*
* Determination of availability is determined by the HAL. Creation attempts (if requested
* by registration of interface) will be done on any mode changes.
*/
public interface InterfaceRttControllerLifecycleCallback {
/**
* Called when an RTT controller was created (or for newly registered listeners - if it
* was already available). The controller provided by this callback may be destroyed by
* the HAL at which point the {@link #onRttControllerDestroyed()} will be called.
*
* Note: this callback can be triggered to replace an existing controller (instead of
* calling the Destroyed callback in between).
*
* @param controller The RTT controller object.
*/
void onNewRttController(@NonNull IWifiRttController controller);
/**
* Called when the previously provided RTT controller is destroyed. Clients must discard
* their copy. A new copy may be provided later by
* {@link #onNewRttController(IWifiRttController)}.
*/
void onRttControllerDestroyed();
}
/**
* Returns whether the provided @HdmIfaceTypeForCreation combo can be supported by the device.
* Note: This only returns an answer based on the create type combination exposed by the HAL.
* The actual iface creation/deletion rules depend on the iface priorities set in
* {@link #allowedToDeleteIfaceTypeForRequestedType(int, WorkSource, int, WifiIfaceInfo[][])}
*
* @param createTypeCombo SparseArray keyed in by @HdmIfaceTypeForCreation to number of ifaces
* needed.
* @return true if the device supports the provided combo, false otherwise.
*/
public boolean canDeviceSupportCreateTypeCombo(SparseArray<Integer> createTypeCombo) {
if (VDBG) {
Log.d(TAG, "canDeviceSupportCreateTypeCombo: createTypeCombo=" + createTypeCombo);
}
synchronized (mLock) {
int[] requestedCombo = new int[CREATE_TYPES_BY_PRIORITY.length];
for (int createType : CREATE_TYPES_BY_PRIORITY) {
requestedCombo[createType] = createTypeCombo.get(createType, 0);
}
for (StaticChipInfo staticChipInfo : getStaticChipInfos()) {
SparseArray<List<int[][]>> expandedCreateTypeCombosPerChipModeId =
getExpandedCreateTypeCombosPerChipModeId(
staticChipInfo.getAvailableModes());
for (int i = 0; i < expandedCreateTypeCombosPerChipModeId.size(); i++) {
int chipModeId = expandedCreateTypeCombosPerChipModeId.keyAt(i);
for (int[][] expandedCreateTypeCombo
: expandedCreateTypeCombosPerChipModeId.get(chipModeId)) {
for (int[] supportedCombo : expandedCreateTypeCombo) {
if (canCreateTypeComboSupportRequestedCreateTypeCombo(
supportedCombo, requestedCombo)) {
if (VDBG) {
Log.d(TAG, "Device can support createTypeCombo="
+ createTypeCombo);
}
return true;
}
}
}
}
}
if (VDBG) {
Log.d(TAG, "Device cannot support createTypeCombo=" + createTypeCombo);
}
return false;
}
}
/**
* Returns whether the provided Iface can be requested by specifier requestor.
*
* @param createIfaceType Type of iface requested.
* @param requiredChipCapabilities The bitmask of Capabilities which are required.
* See IWifiChip.hal for documentation.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return true if the device supports the provided combo, false otherwise.
*/
public boolean isItPossibleToCreateIface(@HdmIfaceTypeForCreation int createIfaceType,
long requiredChipCapabilities, WorkSource requestorWs) {
if (VDBG) {
Log.d(TAG, "isItPossibleToCreateIface: createIfaceType=" + createIfaceType
+ ", requiredChipCapabilities=" + requiredChipCapabilities);
}
return reportImpactToCreateIface(createIfaceType, true, requiredChipCapabilities,
requestorWs) != null;
}
/**
* Returns whether the provided Iface can be requested by specifier requestor.
*
* @param createIfaceType Type of iface requested.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return true if the device supports the provided combo, false otherwise.
*/
public boolean isItPossibleToCreateIface(
@HdmIfaceTypeForCreation int createIfaceType, WorkSource requestorWs) {
return isItPossibleToCreateIface(
createIfaceType, CHIP_CAPABILITY_ANY, requestorWs);
}
/**
* Returns the details of what it would take to create the provided Iface requested by the
* specified requestor. The details are the list of other interfaces which would have to be
* destroyed.
*
* Return types imply:
* - null: interface cannot be created
* - empty list: interface can be crated w/o destroying any other interafces
* - otherwise: a list of interfaces to be destroyed
*
* @param createIfaceType Type of iface requested.
* @param queryForNewInterface True: request another interface of the specified type, False: if
* there's already an interface of the specified type then no need
* for further operation.
* @param requiredChipCapabilities The bitmask of Capabilities which are required.
* See IWifiChip.hal for documentation.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return the list of interfaces that would have to be destroyed and their worksource. The
* interface type is described using @HdmIfaceTypeForCreation.
*/
public List<Pair<Integer, WorkSource>> reportImpactToCreateIface(
@HdmIfaceTypeForCreation int createIfaceType, boolean queryForNewInterface,
long requiredChipCapabilities, WorkSource requestorWs) {
if (VDBG) {
Log.d(TAG, "reportImpactToCreateIface: ifaceType=" + createIfaceType
+ ", requiredChipCapabilities=" + requiredChipCapabilities
+ ", requestorWs=" + requestorWs);
}
IfaceCreationData creationData;
synchronized (mLock) {
if (mWifi == null) {
Log.e(TAG, "reportImpactToCreateIface: null IWifi -- ifaceType=" + createIfaceType);
return null;
}
WifiChipInfo[] chipInfos = getAllChipInfo();
if (chipInfos == null) {
Log.e(TAG, "createIface: no chip info found");
stopWifi(); // major error: shutting down
return null;
}
if (!validateInterfaceCacheAndRetrieveRequestorWs(chipInfos)) {
Log.e(TAG, "createIface: local cache is invalid!");
stopWifi(); // major error: shutting down
return null;
}
if (!queryForNewInterface) {
for (WifiChipInfo chipInfo: chipInfos) {
if (chipInfo.ifaces[createIfaceType].length != 0) {
return Collections.emptyList(); // approve w/o deleting any interfaces
}
}
}
creationData = getBestIfaceCreationProposal(chipInfos, createIfaceType,
requiredChipCapabilities, requestorWs);
}
if (creationData == null) {
return null; // impossible to create requested interface
}
List<Pair<Integer, WorkSource>> details = new ArrayList<>();
boolean isModeConfigNeeded = !creationData.chipInfo.currentModeIdValid
|| creationData.chipInfo.currentModeId != creationData.chipModeId;
if (!isModeConfigNeeded && (creationData.interfacesToBeRemovedFirst == null
|| creationData.interfacesToBeRemovedFirst.isEmpty())) {
// can create interface w/o deleting any other interfaces
return details;
}
if (isModeConfigNeeded) {
if (VDBG) {
Log.d(TAG, "isItPossibleToCreateIfaceDetails: mode change from - "
+ creationData.chipInfo.currentModeId + ", to - "
+ creationData.chipModeId);
}
for (WifiIfaceInfo[] ifaceInfos: creationData.chipInfo.ifaces) {
for (WifiIfaceInfo ifaceInfo : ifaceInfos) {
details.add(Pair.create(ifaceInfo.createType,
ifaceInfo.requestorWsHelper.getWorkSource()));
}
}
} else {
for (WifiIfaceInfo ifaceInfo : creationData.interfacesToBeRemovedFirst) {
details.add(Pair.create(ifaceInfo.createType,
ifaceInfo.requestorWsHelper.getWorkSource()));
}
}
return details;
}
/**
* See {@link #reportImpactToCreateIface(int, boolean, long, WorkSource)}.
*
* @param ifaceType Type of iface requested.
* @param queryForNewInterface True: request another interface of the specified type, False: if
* there's already an interface of the specified type then no need
* for further operation.
* @param requestorWs Requestor worksource. This will be used to determine priority of this
* interface using rules based on the requestor app's context.
* @return the list of interfaces that would have to be destroyed and their worksource.
*/
public List<Pair<Integer, WorkSource>> reportImpactToCreateIface(
@HdmIfaceTypeForCreation int ifaceType, boolean queryForNewInterface,
WorkSource requestorWs) {
return reportImpactToCreateIface(ifaceType, queryForNewInterface, CHIP_CAPABILITY_ANY,
requestorWs);
}
// internal state
/* This "PRIORITY" is not for deciding interface elimination (that is controlled by
* allowedToDeleteIfaceTypeForRequestedType. This priority is used for:
* - Comparing 2 configuration options
* - Order of dispatch of available for request listeners
*/
private static final int[] IFACE_TYPES_BY_PRIORITY =
{IfaceType.AP, IfaceType.STA, IfaceType.P2P, IfaceType.NAN};
private static final int[] CREATE_TYPES_BY_PRIORITY =
{HDM_CREATE_IFACE_AP, HDM_CREATE_IFACE_AP_BRIDGE, HDM_CREATE_IFACE_STA,
HDM_CREATE_IFACE_P2P, HDM_CREATE_IFACE_NAN};
private final Object mLock = new Object();
private IServiceManager mServiceManager;
private IWifi mWifi;
private IWifiRttController mIWifiRttController;
private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
private final WifiEventCallbackV15 mWifiEventCallbackV15 = new WifiEventCallbackV15();
private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
private final Set<InterfaceRttControllerLifecycleCallbackProxy>
mRttControllerLifecycleCallbacks = new HashSet<>();
private final SparseArray<IWifiChipEventCallback.Stub> mDebugCallbacks = new SparseArray<>();
private boolean mIsReady;
private final Set<SubsystemRestartListenerProxy> mSubsystemRestartListener = new HashSet<>();
/*
* This is the only place where we cache HIDL information in this manager. Necessary since
* we need to keep a list of registered destroyed listeners. Will be validated regularly
* in getAllChipInfoAndValidateCache().
*/
private final Map<Pair<String, Integer>, InterfaceCacheEntry> mInterfaceInfoCache =
new HashMap<>();
private class InterfaceCacheEntry {
public IWifiChip chip;
public int chipId;
public String name;
public int type;
public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
public long creationTime;
public WorkSourceHelper requestorWsHelper;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{name=").append(name).append(", type=").append(type)
.append(", destroyedListeners.size()=").append(destroyedListeners.size())
.append(", RequestorWs=").append(requestorWsHelper)
.append(", creationTime=").append(creationTime).append("}");
return sb.toString();
}
}
private class WifiIfaceInfo {
public String name;
public IWifiIface iface;
public @HdmIfaceTypeForCreation int createType;
public WorkSourceHelper requestorWsHelper;
@Override
public String toString() {
return "{name=" + name + ", iface=" + iface + ", requestorWs=" + requestorWsHelper
+ " }";
}
}
private class WifiChipInfo {
public IWifiChip chip;
public int chipId = -1;
public ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode> availableModes;
public boolean currentModeIdValid = false;
public int currentModeId = -1;
// Arrays of WifiIfaceInfo indexed by @HdmIfaceTypeForCreation, in order of creation as
// returned by IWifiChip.getXxxIfaceNames.
public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[CREATE_TYPES_BY_PRIORITY.length][];
public long chipCapabilities;
public WifiRadioCombinationMatrix radioCombinationMatrix = null;
public SparseBooleanArray radioCombinationLookupTable = new SparseBooleanArray();
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{chipId=").append(chipId).append(", availableModes=").append(availableModes)
.append(", currentModeIdValid=").append(currentModeIdValid)
.append(", currentModeId=").append(currentModeId)
.append(", chipCapabilities=").append(chipCapabilities);
for (int type: IFACE_TYPES_BY_PRIORITY) {
sb.append(", ifaces[" + type + "].length=").append(ifaces[type].length);
}
sb.append("}");
return sb.toString();
}
}
/**
* Wrapper function to access the HIDL services. Created to be mockable in unit-tests.
*/
protected IWifi getWifiServiceMockable() {
try {
return IWifi.getService(true /* retry */);
} catch (RemoteException e) {
Log.e(TAG, "Exception getting IWifi service: " + e);
return null;
}
}
protected android.hardware.wifi.V1_5.IWifi getWifiServiceForV1_5Mockable(IWifi iWifi) {
if (null == iWifi) return null;
return android.hardware.wifi.V1_5.IWifi.castFrom(iWifi);
}
protected IServiceManager getServiceManagerMockable() {
try {
return IServiceManager.getService();
} catch (RemoteException e) {
Log.e(TAG, "Exception getting IServiceManager: " + e);
return null;
}
}
protected android.hardware.wifi.V1_5.IWifiChip getWifiChipForV1_5Mockable(IWifiChip chip) {
if (null == chip) return null;
return android.hardware.wifi.V1_5.IWifiChip.castFrom(chip);
}
protected android.hardware.wifi.V1_6.IWifiChip getWifiChipForV1_6Mockable(IWifiChip chip) {
if (null == chip) return null;
return android.hardware.wifi.V1_6.IWifiChip.castFrom(chip);
}
protected android.hardware.wifi.V1_5.IWifiApIface getIWifiApIfaceForV1_5Mockable(
IWifiApIface iface) {
if (null == iface) return null;
return android.hardware.wifi.V1_5.IWifiApIface.castFrom(iface);
}
protected boolean isBridgedSoftApSupportedMockable() {
return mIsBridgedSoftApSupported;
}
protected boolean isStaWithBridgedSoftApConcurrencySupportedMockable() {
return mIsStaWithBridgedSoftApConcurrencySupported;
}
// internal implementation
private void initializeInternal() {
initIServiceManagerIfNecessary();
if (mIsVendorHalSupported) {
initIWifiIfNecessary();
}
}
private void teardownInternal() {
managerStatusListenerDispatch();
dispatchAllDestroyedListeners();
mIWifiRttController = null;
dispatchRttControllerLifecycleOnDestroyed();
mRttControllerLifecycleCallbacks.clear();
}
private class ServiceManagerDeathRecipient implements DeathRecipient {
@Override
public void serviceDied(long cookie) {
mEventHandler.post(() -> {
Log.wtf(TAG, "IServiceManager died: cookie=" + cookie);
synchronized (mLock) {
mServiceManager = null;
// theoretically can call initServiceManager again here - but
// there's no point since most likely system is going to reboot
}
});
}
}
private final IServiceNotification mServiceNotificationCallback =
new IServiceNotification.Stub() {
@Override
public void onRegistration(String fqName, String name,
boolean preexisting) {
Log.d(TAG, "IWifi registration notification: fqName=" + fqName
+ ", name=" + name + ", preexisting=" + preexisting);
synchronized (mLock) {
initIWifiIfNecessary();
}
}
};
/**
* Failures of IServiceManager are most likely system breaking in any case. Behavior here
* will be to WTF and continue.
*/
private void initIServiceManagerIfNecessary() {
if (mDbg) Log.d(TAG, "initIServiceManagerIfNecessary");
synchronized (mLock) {
if (mServiceManager != null) {
return;
}
mServiceManager = getServiceManagerMockable();
if (mServiceManager == null) {
Log.wtf(TAG, "Failed to get IServiceManager instance");
} else {
try {
if (!mServiceManager.linkToDeath(
mServiceManagerDeathRecipient, /* don't care */ 0)) {
Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
mServiceManager = null;
return;
}
if (!mServiceManager.registerForNotifications(IWifi.kInterfaceName, "",
mServiceNotificationCallback)) {
Log.wtf(TAG, "Failed to register a listener for IWifi service");
mServiceManager = null;
}
} catch (RemoteException e) {
Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
mServiceManager = null;
}
// Cache the result for the supporting vendor hal or not
mIsVendorHalSupported = isSupportedInternal();
}
}
}
/**
* Uses the IServiceManager to query if the vendor HAL is present in the VINTF for the device
* or not.
* @return true if supported, false otherwise.
*/
private boolean isSupportedInternal() {
if (VDBG) Log.d(TAG, "isSupportedInternal");
synchronized (mLock) {
if (mServiceManager == null) {
Log.e(TAG, "isSupported: called but mServiceManager is null!?");
return false;
}
try {
List<String> wifiServices =
mServiceManager.listManifestByInterface(IWifi.kInterfaceName);
return !wifiServices.isEmpty();
} catch (RemoteException e) {
Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
return false;
}
}
}
private class WifiDeathRecipient implements DeathRecipient {
@Override
public void serviceDied(long cookie) {
mEventHandler.post(() -> {
Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie);
synchronized (mLock) { // prevents race condition with surrounding method
mWifi = null;
mIsReady = false;
teardownInternal();
// don't restart: wait for registration notification
}
});
}
}
/**
* Register the wifi HAL event callback. Reset the Wifi HAL interface when it fails.
* @return true if success.
*/
private boolean registerWifiHalEventCallback() {
try {
if (mWifi == null) {
Log.e(TAG, "registerWifiHalEventCallback called but mWifi is null!?");
return false;
}
WifiStatus status;
android.hardware.wifi.V1_5.IWifi iWifiV15 = getWifiServiceForV1_5Mockable(mWifi);
if (iWifiV15 != null) {
status = iWifiV15.registerEventCallback_1_5(mWifiEventCallbackV15);
} else {
status = mWifi.registerEventCallback(mWifiEventCallback);
}
if (status.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status));
mWifi = null;
}
return status.code == WifiStatusCode.SUCCESS;
} catch (RemoteException e) {
Log.e(TAG, "Exception while operating on IWifi: " + e);
}
return false;
}
/**
* Initialize IWifi and register death listener and event callback.
*
* - It is possible that IWifi is not ready - we have a listener on IServiceManager for it.
* - It is not expected that any of the registrations will fail. Possible indication that
* service died after we obtained a handle to it.
*
* Here and elsewhere we assume that death listener will do the right thing!
*/
private void initIWifiIfNecessary() {
if (mDbg) Log.d(TAG, "initIWifiIfNecessary");
synchronized (mLock) {
if (mWifi != null) {
return;
}
try {
mWifi = getWifiServiceMockable();
if (mWifi == null) {
Log.e(TAG, "IWifi not (yet) available - but have a listener for it ...");
return;
}
if (!mWifi.linkToDeath(mIWifiDeathRecipient, /* don't care */ 0)) {
Log.e(TAG, "Error on linkToDeath on IWifi - will retry later");
return;
}
// Stopping wifi just in case. This would also trigger the status callback.
// Stopping wifi invalidated the registered the event callback, register after the
// wifi stop.
stopWifi();
if (!registerWifiHalEventCallback()) {
return;
}
mIsReady = true;
} catch (RemoteException e) {
Log.e(TAG, "Exception while operating on IWifi: " + e);
}
}
}
/**
* Registers event listeners on all IWifiChips after a successful start: DEBUG only!
*
* We don't need the listeners since any callbacks are just confirmation of status codes we
* obtain directly from mode changes or interface creation/deletion.
*
* Relies (to the degree we care) on the service removing all listeners when Wi-Fi is stopped.
*/
private void initIWifiChipDebugListeners() {
if (VDBG) Log.d(TAG, "initIWifiChipDebugListeners");
if (!VDBG) {
return;
}
synchronized (mLock) {
try {
Mutable<Boolean> statusOk = new Mutable<>(false);
Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
// get all chip IDs
mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
statusOk.value = false;
if (status.code == WifiStatusCode.SUCCESS) {
if (chipIds == null) {
Log.wtf(TAG, "getChipIds failed, chipIds is null");
return;
}
statusOk.value = true;
}
if (statusOk.value) {
chipIdsResp.value = chipIds;
} else {
Log.e(TAG, "getChipIds failed: " + statusString(status));
}
});
if (!statusOk.value) {
return;
}
Log.d(TAG, "getChipIds=" + chipIdsResp.value);
if (chipIdsResp.value.size() == 0) {
Log.e(TAG, "Should have at least 1 chip!");
return;
}
// register a callback for each chip
Mutable<IWifiChip> chipResp = new Mutable<>();
for (Integer chipId: chipIdsResp.value) {
mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
statusOk.value = false;
if (status.code == WifiStatusCode.SUCCESS) {
if (chip == null) {
Log.wtf(TAG, "getChip failed, chip " + chipId + " is null");
}
statusOk.value = true;
}
if (statusOk.value) {
chipResp.value = chip;
} else {
Log.e(TAG, "getChip failed: " + statusString(status));
}
});
if (!statusOk.value) {
continue; // still try next one?
}
IWifiChipEventCallback.Stub callback =
new IWifiChipEventCallback.Stub() {
@Override
public void onChipReconfigured(int modeId) throws RemoteException {
Log.d(TAG, "onChipReconfigured: modeId=" + modeId);
}
@Override
public void onChipReconfigureFailure(WifiStatus status)
throws RemoteException {
Log.e(TAG, "onChipReconfigureFailure: status=" + statusString(
status));
}
@Override
public void onIfaceAdded(int type, String name)
throws RemoteException {
Log.d(TAG, "onIfaceAdded: type=" + type + ", name=" + name);
}
@Override
public void onIfaceRemoved(int type, String name)
throws RemoteException {
Log.d(TAG, "onIfaceRemoved: type=" + type + ", name=" + name);
}
@Override
public void onDebugRingBufferDataAvailable(
WifiDebugRingBufferStatus status,
ArrayList<Byte> data) throws RemoteException {
Log.d(TAG, "onDebugRingBufferDataAvailable");
}
@Override
public void onDebugErrorAlert(int errorCode,
ArrayList<Byte> debugData)
throws RemoteException {
Log.d(TAG, "onDebugErrorAlert");
}
};
mDebugCallbacks.put(chipId, callback); // store to prevent GC: needed by HIDL
WifiStatus status = chipResp.value.registerEventCallback(callback);
if (status.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "registerEventCallback failed: " + statusString(status));
continue; // still try next one?
}
}
} catch (RemoteException e) {
Log.e(TAG, "initIWifiChipDebugListeners: exception: " + e);
return;
}
}
}
@Nullable
private WifiChipInfo[] mCachedWifiChipInfos = null;
/**
* Get current information about all the chips in the system: modes, current mode (if any), and
* any existing interfaces.
*
* Intended to be called for any external iface support related queries. This information is
* cached to reduce performance overhead (unlike {@link #getAllChipInfo()}).
*/
private WifiChipInfo[] getAllChipInfoCached() {
if (mCachedWifiChipInfos == null) {
mCachedWifiChipInfos = getAllChipInfo();
}
return mCachedWifiChipInfos;
}
/**
* Get current information about all the chips in the system: modes, current mode (if any), and
* any existing interfaces.
*
* Intended to be called whenever we need to configure the chips - information is NOT cached (to
* reduce the likelihood that we get out-of-sync).
*/
private WifiChipInfo[] getAllChipInfo() {
if (VDBG) Log.d(TAG, "getAllChipInfo");
synchronized (mLock) {
if (!isWifiStarted()) {
return null;
}
try {
Mutable<Boolean> statusOk = new Mutable<>(false);
Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
// get all chip IDs
mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
chipIdsResp.value = chipIds;
} else {
Log.e(TAG, "getChipIds failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
if (VDBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
if (chipIdsResp.value.size() == 0) {
Log.e(TAG, "Should have at least 1 chip!");
return null;
}
int chipInfoIndex = 0;
WifiChipInfo[] chipsInfo = new WifiChipInfo[chipIdsResp.value.size()];
Mutable<IWifiChip> chipResp = new Mutable<>();
for (Integer chipId: chipIdsResp.value) {
mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
chipResp.value = chip;
} else {
Log.e(TAG, "getChip failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
Mutable<ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode>>
availableModesResp = new Mutable<>();
android.hardware.wifi.V1_6.IWifiChip chipV16 =
getWifiChipForV1_6Mockable(chipResp.value);
if (chipV16 != null) {
chipV16.getAvailableModes_1_6((WifiStatus status,
ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode> modes) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
availableModesResp.value = modes;
} else {
Log.e(TAG, "getAvailableModes_1_6 failed: "
+ statusString(status));
}
});
} else {
chipResp.value.getAvailableModes((WifiStatus status,
ArrayList<IWifiChip.ChipMode> modes) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
availableModesResp.value = upgradeV1_0ChipModesToV1_6(modes);
} else {
Log.e(TAG, "getAvailableModes failed: "
+ statusString(status));
}
});
}
if (!statusOk.value) {
return null;
}
Mutable<Boolean> currentModeValidResp = new Mutable<>(false);
Mutable<Integer> currentModeResp = new Mutable<>(0);
chipResp.value.getMode((WifiStatus status, int modeId) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
currentModeValidResp.value = true;
currentModeResp.value = modeId;
} else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
statusOk.value = true; // valid response
} else {
Log.e(TAG, "getMode failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
Mutable<Long> chipCapabilities = new Mutable<>(0L);
chipCapabilities.value = getChipCapabilities(chipResp.value);
Mutable<ArrayList<String>> ifaceNamesResp = new Mutable<>();
Mutable<Integer> ifaceIndex = new Mutable<>(0);
chipResp.value.getStaIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getStaIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] staIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getStaIface(ifaceName,
(WifiStatus status, IWifiStaIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
ifaceInfo.createType = HDM_CREATE_IFACE_STA;
staIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getStaIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
ifaceIndex.value = 0;
chipResp.value.getApIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getApIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] apIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getApIface(ifaceName,
(WifiStatus status, IWifiApIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
ifaceInfo.createType = HDM_CREATE_IFACE_AP;
apIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getApIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
Mutable<Integer> numBridgedAps = new Mutable<>(0);
for (WifiIfaceInfo apIfaceInfo : apIfaces) {
android.hardware.wifi.V1_5.IWifiApIface wifiApIfaceV15 =
getIWifiApIfaceForV1_5Mockable((IWifiApIface) apIfaceInfo.iface);
if (wifiApIfaceV15 == null) {
continue;
}
try {
wifiApIfaceV15.getBridgedInstances((status, instances) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
// Only count bridged APs with more than 1 instance as a bridged
// AP; 1 instance bridged APs will be counted as single AP.
if (instances != null && instances.size() > 1) {
apIfaceInfo.createType = HDM_CREATE_IFACE_AP_BRIDGE;
numBridgedAps.value++;
}
} else {
Log.e(TAG, "getBridgedInstances failed: "
+ statusString(status));
}
});
} catch (RemoteException e) {
Log.e(TAG, "IWifiApIface.getBridgedInstances exception: " + e);
}
}
WifiIfaceInfo[] singleApIfaces =
new WifiIfaceInfo[apIfaces.length - numBridgedAps.value];
WifiIfaceInfo[] bridgedApIfaces = new WifiIfaceInfo[numBridgedAps.value];
int singleApIndex = 0;
int bridgedApIndex = 0;
for (WifiIfaceInfo apIfaceInfo : apIfaces) {
if (apIfaceInfo.createType == HDM_CREATE_IFACE_AP_BRIDGE) {
bridgedApIfaces[bridgedApIndex++] = apIfaceInfo;
} else {
singleApIfaces[singleApIndex++] = apIfaceInfo;
}
}
ifaceIndex.value = 0;
chipResp.value.getP2pIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getP2pIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] p2pIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getP2pIface(ifaceName,
(WifiStatus status, IWifiP2pIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
ifaceInfo.createType = HDM_CREATE_IFACE_P2P;
p2pIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getP2pIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
ifaceIndex.value = 0;
chipResp.value.getNanIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getNanIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] nanIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getNanIface(ifaceName,
(WifiStatus status, IWifiNanIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
ifaceInfo.createType = HDM_CREATE_IFACE_NAN;
nanIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getNanIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
WifiChipInfo chipInfo = new WifiChipInfo();
chipsInfo[chipInfoIndex++] = chipInfo;
chipInfo.chip = chipResp.value;
chipInfo.chipId = chipId;
chipInfo.availableModes = availableModesResp.value;
chipInfo.currentModeIdValid = currentModeValidResp.value;
chipInfo.currentModeId = currentModeResp.value;
chipInfo.chipCapabilities = chipCapabilities.value;
chipInfo.ifaces[HDM_CREATE_IFACE_STA] = staIfaces;
chipInfo.ifaces[HDM_CREATE_IFACE_AP] = singleApIfaces;
chipInfo.ifaces[HDM_CREATE_IFACE_AP_BRIDGE] = bridgedApIfaces;
chipInfo.ifaces[HDM_CREATE_IFACE_P2P] = p2pIfaces;
chipInfo.ifaces[HDM_CREATE_IFACE_NAN] = nanIfaces;
}
return chipsInfo;
} catch (RemoteException e) {
Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e);
}
}
return null;
}
private ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode>
upgradeV1_0ChipModesToV1_6(ArrayList<IWifiChip.ChipMode> oldChipModes) {
ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode> newChipModes = new ArrayList<>();
for (IWifiChip.ChipMode oldChipMode : oldChipModes) {
android.hardware.wifi.V1_6.IWifiChip.ChipMode newChipMode =
new android.hardware.wifi.V1_6.IWifiChip.ChipMode();
newChipMode.id = oldChipMode.id;
newChipMode.availableCombinations = new ArrayList<>();
for (IWifiChip.ChipIfaceCombination oldCombo : oldChipMode.availableCombinations) {
android.hardware.wifi.V1_6.IWifiChip.ChipConcurrencyCombination newCombo =
new ChipConcurrencyCombination();
newCombo.limits = new ArrayList<>();
boolean isStaInCombination = false;
for (IWifiChip.ChipIfaceCombinationLimit oldLimit : oldCombo.limits) {
if (oldLimit.types.contains(IfaceType.STA)) {
isStaInCombination = true;
break;
}
}
// Add Bridged AP based on the overlays
boolean canAddBridgedAp = isBridgedSoftApSupportedMockable() && !(isStaInCombination
&& !isStaWithBridgedSoftApConcurrencySupportedMockable());
for (IWifiChip.ChipIfaceCombinationLimit oldLimit : oldCombo.limits) {
ChipConcurrencyCombinationLimit newLimit =
new ChipConcurrencyCombinationLimit();
newLimit.types = new ArrayList<>();
for (int oldType : oldLimit.types) {
int newType = IFACE_TYPE_TO_CONCURRENCY_TYPE_MAP.get(oldType);
newLimit.types.add(newType);
if (oldType == IfaceType.AP && canAddBridgedAp) {
newLimit.types.add(IfaceConcurrencyType.AP_BRIDGED);
}
}
newLimit.maxIfaces = oldLimit.maxIfaces;
newCombo.limits.add(newLimit);
}
newChipMode.availableCombinations.add(newCombo);
}
newChipModes.add(newChipMode);
}
return newChipModes;
}
@Nullable
private StaticChipInfo[] mCachedStaticChipInfos = null;
@NonNull
private StaticChipInfo[] getStaticChipInfos() {
if (mCachedStaticChipInfos == null) {
mCachedStaticChipInfos = loadStaticChipInfoFromStore();
}
return mCachedStaticChipInfos;
}
private void saveStaticChipInfoToStore(StaticChipInfo[] staticChipInfos) {
try {
JSONArray staticChipInfosJson = new JSONArray();
for (StaticChipInfo staticChipInfo : staticChipInfos) {
staticChipInfosJson.put(staticChipInfoToJson(staticChipInfo));
}
mWifiInjector.getSettingsConfigStore().put(WIFI_STATIC_CHIP_INFO,
staticChipInfosJson.toString());
} catch (JSONException e) {
Log.e(TAG, "JSONException while converting StaticChipInfo to JSON: " + e);
}
}
private StaticChipInfo[] loadStaticChipInfoFromStore() {
StaticChipInfo[] staticChipInfos = new StaticChipInfo[0];
String configString = mWifiInjector.getSettingsConfigStore().get(WIFI_STATIC_CHIP_INFO);
if (TextUtils.isEmpty(configString)) {
return staticChipInfos;
}
try {
JSONArray staticChipInfosJson = new JSONArray(
mWifiInjector.getSettingsConfigStore().get(WIFI_STATIC_CHIP_INFO));
staticChipInfos = new StaticChipInfo[staticChipInfosJson.length()];
for (int i = 0; i < staticChipInfosJson.length(); i++) {
staticChipInfos[i] = jsonToStaticChipInfo(staticChipInfosJson.getJSONObject(i));
}
} catch (JSONException e) {
Log.e(TAG, "Failed to load static chip info from store: " + e);
}
return staticChipInfos;
}
private StaticChipInfo[] convertWifiChipInfoToStaticChipInfos(WifiChipInfo[] chipInfos) {
StaticChipInfo[] staticChipInfos = new StaticChipInfo[chipInfos.length];
for (int i = 0; i < chipInfos.length; i++) {
WifiChipInfo chipInfo = chipInfos[i];
staticChipInfos[i] = new StaticChipInfo(
chipInfo.chipId,
chipInfo.chipCapabilities,
chipInfo.availableModes);
}
return staticChipInfos;
}
/**
* Checks the local state of this object (the cached state) against the input 'chipInfos'
* state (which is a live representation of the Wi-Fi firmware status - read through the HAL).
* Returns 'true' if there are no discrepancies - 'false' otherwise.
*
* A discrepancy is if any local state contains references to a chip or interface which are not
* found on the information read from the chip.
*
* Also, fills in the |requestorWs| corresponding to each active iface in |WifiChipInfo|.
*/
private boolean validateInterfaceCacheAndRetrieveRequestorWs(WifiChipInfo[] chipInfos) {
if (VDBG) Log.d(TAG, "validateInterfaceCache");
synchronized (mLock) {
for (InterfaceCacheEntry entry: mInterfaceInfoCache.values()) {
// search for chip
WifiChipInfo matchingChipInfo = null;
for (WifiChipInfo ci: chipInfos) {
if (ci.chipId == entry.chipId) {
matchingChipInfo = ci;
break;
}
}
if (matchingChipInfo == null) {
Log.e(TAG, "validateInterfaceCache: no chip found for " + entry);
return false;
}
// search for matching interface cache entry by iterating through the corresponding
// HdmIfaceTypeForCreation values.
boolean matchFound = false;
for (int createType : CREATE_TYPES_BY_PRIORITY) {
if (HAL_IFACE_MAP.get(createType) != entry.type) {
continue;
}
WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[createType];
if (ifaceInfoList == null) {
Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry);
return false;
}
for (WifiIfaceInfo ifaceInfo : ifaceInfoList) {
if (ifaceInfo.name.equals(entry.name)) {
ifaceInfo.requestorWsHelper = entry.requestorWsHelper;
matchFound = true;
break;
}
}
}
if (!matchFound) {
Log.e(TAG, "validateInterfaceCache: no interface found for " + entry);
return false;
}
}
}
return true;
}
private boolean isWifiStarted() {
if (VDBG) Log.d(TAG, "isWifiStart");
synchronized (mLock) {
try {
if (mWifi == null) {
return false;
} else {
return mWifi.isStarted();
}
} catch (RemoteException e) {
Log.e(TAG, "isWifiStarted exception: " + e);
return false;
}
}
}
private boolean startWifi() {
if (VDBG) Log.d(TAG, "startWifi");
initIWifiIfNecessary();
synchronized (mLock) {
try {
if (mWifi == null) {
Log.w(TAG, "startWifi called but mWifi is null!?");
return false;
} else {
int triedCount = 0;
while (triedCount <= START_HAL_RETRY_TIMES) {
WifiStatus status = mWifi.start();
if (status.code == WifiStatusCode.SUCCESS) {
initIWifiChipDebugListeners();
managerStatusListenerDispatch();
if (triedCount != 0) {
Log.d(TAG, "start IWifi succeeded after trying "
+ triedCount + " times");
}
WifiChipInfo[] wifiChipInfos = getAllChipInfo();
if (wifiChipInfos != null) {
mCachedStaticChipInfos =
convertWifiChipInfoToStaticChipInfos(getAllChipInfo());
saveStaticChipInfoToStore(mCachedStaticChipInfos);
} else {
Log.e(TAG, "Started wifi but could not get current chip info.");
}
return true;
} else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
// Should retry. Hal might still be stopping. the registered event
// callback will not be cleared.
Log.e(TAG, "Cannot start IWifi: " + statusString(status)
+ ", Retrying...");
try {
Thread.sleep(START_HAL_RETRY_INTERVAL_MS);
} catch (InterruptedException ignore) {
// no-op
}
triedCount++;
} else {
// Should not retry on other failures.
// Will be handled in the onFailure event.
Log.e(TAG, "Cannot start IWifi: " + statusString(status));
return false;
}
}
Log.e(TAG, "Cannot start IWifi after trying " + triedCount + " times");
return false;
}
} catch (RemoteException e) {
Log.e(TAG, "startWifi exception: " + e);
return false;
}
}
}
private void stopWifi() {
if (VDBG) Log.d(TAG, "stopWifi");
try {
if (mWifi == null) {
Log.w(TAG, "stopWifi called but mWifi is null!?");
} else {
WifiStatus status = mWifi.stop();
if (status.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "Cannot stop IWifi: " + statusString(status));
}
// even on failure since WTF??
teardownInternal();
}
} catch (RemoteException e) {
Log.e(TAG, "stopWifi exception: " + e);
}
}
private class WifiEventCallback extends IWifiEventCallback.Stub {
@Override
public void onStart() throws RemoteException {
mEventHandler.post(() -> {
if (VDBG) Log.d(TAG, "IWifiEventCallback.onStart");
// NOP: only happens in reaction to my calls - will handle directly
});
}
@Override
public void onStop() throws RemoteException {
mEventHandler.post(() -> {
if (VDBG) Log.d(TAG, "IWifiEventCallback.onStop");
// NOP: only happens in reaction to my calls - will handle directly
});
}
@Override
public void onFailure(WifiStatus status) throws RemoteException {
mEventHandler.post(() -> {
Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
synchronized (mLock) {
mWifi = null;
mIsReady = false;
teardownInternal();
}
});
}
}
private class WifiEventCallbackV15 extends
android.hardware.wifi.V1_5.IWifiEventCallback.Stub {
private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
@Override
public void onStart() throws RemoteException {
mWifiEventCallback.onStart();
}
@Override
public void onStop() throws RemoteException {
mWifiEventCallback.onStop();
}
@Override
public void onFailure(WifiStatus status) throws RemoteException {
mWifiEventCallback.onFailure(status);
}
@Override
public void onSubsystemRestart(WifiStatus status) throws RemoteException {
Log.i(TAG, "onSubsystemRestart");
mEventHandler.post(() -> {
Log.i(TAG, "IWifiEventCallback.onSubsystemRestart: " + statusString(status));
synchronized (mLock) {
Log.i(TAG, "Attempting to invoke mSubsystemRestartListener");
for (SubsystemRestartListenerProxy cb : mSubsystemRestartListener) {
Log.i(TAG, "Invoking mSubsystemRestartListener");
cb.action();
}
}
});
}
}
private void managerStatusListenerDispatch() {
synchronized (mLock) {
for (ManagerStatusListenerProxy cb : mManagerStatusListeners) {
cb.trigger();
}
}
}
private class ManagerStatusListenerProxy extends
ListenerProxy<ManagerStatusListener> {
ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
super(statusListener, handler, "ManagerStatusListenerProxy");
}
@Override
protected void action() {
mListener.onStatusChanged();
}
}
private Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
Set<Integer> results = new HashSet<>();
WifiChipInfo[] chipInfos = getAllChipInfoCached();
if (chipInfos == null) {
Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found");
return results;
}
Mutable<Integer> chipIdIfProvided = new Mutable<>(0); // NOT using 0 as a magic value
if (chip != null) {
Mutable<Boolean> statusOk = new Mutable<>(false);
try {
chip.getId((WifiStatus status, int id) -> {
if (status.code == WifiStatusCode.SUCCESS) {
chipIdIfProvided.value = id;
statusOk.value = true;
} else {
Log.e(TAG, "getSupportedIfaceTypesInternal: IWifiChip.getId() error: "
+ statusString(status));
statusOk.value = false;
}
});
} catch (RemoteException e) {
Log.e(TAG, "getSupportedIfaceTypesInternal IWifiChip.getId() exception: " + e);
return results;
}
if (!statusOk.value) {
return results;
}
}
for (WifiChipInfo wci: chipInfos) {
if (chip != null && wci.chipId != chipIdIfProvided.value) {
continue;
}
// Map the V1_6 IfaceConcurrencyTypes to the corresponding IfaceType.
for (android.hardware.wifi.V1_6.IWifiChip.ChipMode cm: wci.availableModes) {
for (ChipConcurrencyCombination cic : cm.availableCombinations) {
for (ChipConcurrencyCombinationLimit cicl : cic.limits) {
for (int concurrencyType: cicl.types) {
results.add(HAL_IFACE_MAP.get(
CONCURRENCY_TYPE_TO_CREATE_TYPE_MAP.get(concurrencyType)));
}
}
}
}
}
return results;
}
private IWifiIface createIface(@HdmIfaceTypeForCreation int createIfaceType,
long requiredChipCapabilities, InterfaceDestroyedListener destroyedListener,
Handler handler, WorkSource requestorWs) {
if (mDbg) {
Log.d(TAG, "createIface: createIfaceType=" + createIfaceType
+ ", requiredChipCapabilities=" + requiredChipCapabilities
+ ", requestorWs=" + requestorWs);
}
if (destroyedListener != null && handler == null) {
Log.wtf(TAG, "createIface: createIfaceType=" + createIfaceType
+ "with NonNull destroyedListener but Null handler");
return null;
}
synchronized (mLock) {
WifiChipInfo[] chipInfos = getAllChipInfo();
if (chipInfos == null) {
Log.e(TAG, "createIface: no chip info found");
stopWifi(); // major error: shutting down
// Event callback has been invalidated in HAL stop, register it again.
registerWifiHalEventCallback();
return null;
}
if (!validateInterfaceCacheAndRetrieveRequestorWs(chipInfos)) {
Log.e(TAG, "createIface: local cache is invalid!");
stopWifi(); // major error: shutting down
// Event callback has been invalidated in HAL stop, register it again.
registerWifiHalEventCallback();
return null;
}
return createIfaceIfPossible(
chipInfos, createIfaceType, requiredChipCapabilities,
destroyedListener, handler, requestorWs);
}
}
private static boolean isChipCapabilitiesSupported(long currentChipCapabilities,
long requiredChipCapabilities) {
if (requiredChipCapabilities == CHIP_CAPABILITY_ANY) return true;
if (CHIP_CAPABILITY_UNINITIALIZED == currentChipCapabilities) return true;
return (currentChipCapabilities & requiredChipCapabilities)
== requiredChipCapabilities;
}
private IfaceCreationData getBestIfaceCreationProposal(
WifiChipInfo[] chipInfos, @HdmIfaceTypeForCreation int createIfaceType,
long requiredChipCapabilities, WorkSource requestorWs) {
int targetHalIfaceType = HAL_IFACE_MAP.get(createIfaceType);
if (VDBG) {
Log.d(TAG, "getBestIfaceCreationProposal: chipInfos=" + Arrays.deepToString(chipInfos)
+ ", createIfaceType=" + createIfaceType
+ ", targetHalIfaceType=" + targetHalIfaceType
+ ", requiredChipCapabilities=" + requiredChipCapabilities
+ ", requestorWs=" + requestorWs);
}
synchronized (mLock) {
IfaceCreationData bestIfaceCreationProposal = null;
for (WifiChipInfo chipInfo : chipInfos) {
if (!isChipCapabilitiesSupported(
chipInfo.chipCapabilities, requiredChipCapabilities)) {
continue;
}
SparseArray<List<int[][]>> expandedCreateTypeCombosPerChipModeId =
getExpandedCreateTypeCombosPerChipModeId(chipInfo.availableModes);
for (int i = 0; i < expandedCreateTypeCombosPerChipModeId.size(); i++) {
int chipModeId = expandedCreateTypeCombosPerChipModeId.keyAt(i);
for (int[][] expandedCreateTypeCombo :
expandedCreateTypeCombosPerChipModeId.get(chipModeId)) {
for (int[] createTypeCombo : expandedCreateTypeCombo) {
IfaceCreationData currentProposal = canCreateTypeComboSupportRequest(
chipInfo, chipModeId, createTypeCombo, createIfaceType,
requestorWs);
if (compareIfaceCreationData(currentProposal,
bestIfaceCreationProposal)) {
if (VDBG) Log.d(TAG, "new proposal accepted");
bestIfaceCreationProposal = currentProposal;
}
}
}
}
}
return bestIfaceCreationProposal;
}
}
/**
* Returns a SparseArray indexed by ChipModeId, containing Lists of expanded create type combos
* supported by that id.
*/
private SparseArray<List<int[][]>> getExpandedCreateTypeCombosPerChipModeId(
ArrayList<android.hardware.wifi.V1_6.IWifiChip.ChipMode> chipModes) {
SparseArray<List<int[][]>> combosPerChipModeId = new SparseArray<>();
for (android.hardware.wifi.V1_6.IWifiChip.ChipMode chipMode : chipModes) {
List<int[][]> expandedCreateTypeCombos = new ArrayList<>();
for (ChipConcurrencyCombination chipConcurrencyCombo
: chipMode.availableCombinations) {
expandedCreateTypeCombos.add(expandCreateTypeCombo(chipConcurrencyCombo));
}
combosPerChipModeId.put(chipMode.id, expandedCreateTypeCombos);
}
return combosPerChipModeId;
}
private IWifiIface createIfaceIfPossible(
WifiChipInfo[] chipInfos, @HdmIfaceTypeForCreation int createIfaceType,
long requiredChipCapabilities, InterfaceDestroyedListener destroyedListener,
Handler handler, WorkSource requestorWs) {
int targetHalIfaceType = HAL_IFACE_MAP.get(createIfaceType);
if (VDBG) {
Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
+ ", createIfaceType=" + createIfaceType
+ ", targetHalIfaceType=" + targetHalIfaceType
+ ", requiredChipCapabilities=" + requiredChipCapabilities
+ ", requestorWs=" + requestorWs);
}
synchronized (mLock) {
IfaceCreationData bestIfaceCreationProposal = getBestIfaceCreationProposal(chipInfos,
createIfaceType, requiredChipCapabilities, requestorWs);
if (bestIfaceCreationProposal != null) {
IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal,
createIfaceType);
if (iface == null) {
// If the chip reconfiguration failed, we'll need to clean up internal state.
Log.e(TAG, "Teardown Wifi internal state");
mWifi = null;
mIsReady = false;
teardownInternal();
} else {
InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry();
cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip;
cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId;
cacheEntry.name = getName(iface);
cacheEntry.type = targetHalIfaceType;
cacheEntry.requestorWsHelper = mWifiInjector.makeWsHelper(requestorWs);
if (destroyedListener != null) {
cacheEntry.destroyedListeners.add(
new InterfaceDestroyedListenerProxy(
cacheEntry.name, destroyedListener, handler));
}
cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
if (mDbg) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
mInterfaceInfoCache.put(
Pair.create(cacheEntry.name, cacheEntry.type), cacheEntry);
return iface;
}
}
}
Log.e(TAG, "createIfaceIfPossible: Failed to create iface for ifaceType=" + createIfaceType
+ ", requestorWs=" + requestorWs);
return null;
}
/**
* Expands (or provides an alternative representation) of the ChipConcurrencyCombination as all
* possible combinations of @HdmIfaceTypeForCreation.
*
* Returns [# of combinations][4 (@HdmIfaceTypeForCreation)]
*
* Note: there could be duplicates - allow (inefficient but ...).
* TODO: optimize by using a Set as opposed to a []: will remove duplicates. Will need to
* provide correct hashes.
*/
private int[][] expandCreateTypeCombo(ChipConcurrencyCombination chipConcurrencyCombo) {
int numOfCombos = 1;
for (ChipConcurrencyCombinationLimit limit : chipConcurrencyCombo.limits) {
for (int i = 0; i < limit.maxIfaces; ++i) {
numOfCombos *= limit.types.size();
}
}
int[][] expandedCreateTypeCombo =
new int[numOfCombos][CREATE_TYPES_BY_PRIORITY.length];
int span = numOfCombos; // span of an individual type (or sub-tree size)
for (ChipConcurrencyCombinationLimit limit : chipConcurrencyCombo.limits) {
for (int i = 0; i < limit.maxIfaces; ++i) {
span /= limit.types.size();
for (int k = 0; k < numOfCombos; ++k) {
expandedCreateTypeCombo[k][CONCURRENCY_TYPE_TO_CREATE_TYPE_MAP.get(
limit.types.get((k / span) % limit.types.size()))]++;
}
}
}
if (VDBG) {
Log.d(TAG, "ChipConcurrencyCombo " + chipConcurrencyCombo
+ " expands to HdmIfaceTypeForCreation combo "
+ Arrays.deepToString(expandedCreateTypeCombo));
}
return expandedCreateTypeCombo;
}
private class IfaceCreationData {
public WifiChipInfo chipInfo;
public int chipModeId;
public List<WifiIfaceInfo> interfacesToBeRemovedFirst;
public List<WifiIfaceInfo> interfacesToBeDowngraded;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("{chipInfo=").append(chipInfo).append(", chipModeId=").append(chipModeId)
.append(", interfacesToBeRemovedFirst=").append(interfacesToBeRemovedFirst)
.append(", interfacesToBeDowngraded=").append(interfacesToBeDowngraded)
.append(")");
return sb.toString();
}
}
/**
* Checks whether the input chip-create-type-combo can support the requested create type:
* if not then returns null, if yes then returns information containing the list of interfaces
* which would have to be removed first before an interface of the given create type can be
* created.
*
* Note: the list of interfaces to be removed is EMPTY if a chip mode change is required - in
* that case ALL the interfaces on the current chip have to be removed first.
*
* Response determined based on:
* - Mode configuration: i.e. could the mode support the interface type in principle
*/
private IfaceCreationData canCreateTypeComboSupportRequest(
WifiChipInfo chipInfo,
int chipModeId,
int[] chipCreateTypeCombo,
@HdmIfaceTypeForCreation int requestedCreateType,
WorkSource requestorWs) {
if (VDBG) {
Log.d(TAG, "canCreateTypeComboSupportRequest: chipInfo=" + chipInfo
+ ", chipModeId=" + chipModeId
+ ", chipCreateTypeCombo=" + Arrays.toString(chipCreateTypeCombo)
+ ", requestedCreateType=" + requestedCreateType
+ ", requestorWs=" + requestorWs);
}
// short-circuit: does the combo even support the requested type?
if (chipCreateTypeCombo[requestedCreateType] == 0) {
if (VDBG) Log.d(TAG, "Requested create type not supported by combo");
return null;
}
boolean isChipModeChangeProposed =
chipInfo.currentModeIdValid && chipInfo.currentModeId != chipModeId;
// short-circuit: can't change chip-mode if an existing interface on this chip has a higher
// priority than the requested interface
if (isChipModeChangeProposed) {
for (int existingCreateType : CREATE_TYPES_BY_PRIORITY) {
WifiIfaceInfo[] createTypeIfaces = chipInfo.ifaces[existingCreateType];
if (selectInterfacesToDelete(createTypeIfaces.length, requestedCreateType,
requestorWs, existingCreateType, createTypeIfaces) == null) {
if (VDBG) {
Log.d(TAG, "Couldn't delete existing create type "
+ existingCreateType + " interfaces for requested type");
}
return null;
}
}
// but if priority allows the mode change then we're good to go
IfaceCreationData ifaceCreationData = new IfaceCreationData();
ifaceCreationData.chipInfo = chipInfo;
ifaceCreationData.chipModeId = chipModeId;
return ifaceCreationData;
}
// possibly supported
List<WifiIfaceInfo> interfacesToBeRemovedFirst = new ArrayList<>();
List<WifiIfaceInfo> interfacesToBeDowngraded = new ArrayList<>();
for (int existingCreateType : CREATE_TYPES_BY_PRIORITY) {
WifiIfaceInfo[] createTypeIfaces = chipInfo.ifaces[existingCreateType];
int numExcessIfaces = createTypeIfaces.length - chipCreateTypeCombo[existingCreateType];
// need to count the requested create type as well
if (existingCreateType == requestedCreateType) {
numExcessIfaces += 1;
}
if (numExcessIfaces > 0) { // may need to delete some
// Try downgrading bridged APs before we consider deleting them.
if (existingCreateType == HDM_CREATE_IFACE_AP_BRIDGE) {
int availableSingleApCapacity = chipCreateTypeCombo[HDM_CREATE_IFACE_AP]
- chipInfo.ifaces[HDM_CREATE_IFACE_AP].length;
if (requestedCreateType == HDM_CREATE_IFACE_AP) {
availableSingleApCapacity -= 1;
}
if (availableSingleApCapacity >= numExcessIfaces) {
interfacesToBeDowngraded = selectBridgedApInterfacesToDowngrade(
numExcessIfaces, createTypeIfaces);
if (interfacesToBeDowngraded != null) {
continue;
}
// Can't downgrade enough bridged APs, fall through to delete them.
if (VDBG) {
Log.d(TAG, "Could not downgrade enough bridged APs for request.");
}
}
}
List<WifiIfaceInfo> selectedIfacesToDelete =
selectInterfacesToDelete(numExcessIfaces, requestedCreateType, requestorWs,
existingCreateType, createTypeIfaces);
if (selectedIfacesToDelete == null) {
if (VDBG) {
Log.d(TAG, "Would need to delete some higher priority interfaces");
}
return null;
}
interfacesToBeRemovedFirst.addAll(selectedIfacesToDelete);
}
}
IfaceCreationData ifaceCreationData = new IfaceCreationData();
ifaceCreationData.chipInfo = chipInfo;
ifaceCreationData.chipModeId = chipModeId;
ifaceCreationData.interfacesToBeRemovedFirst = interfacesToBeRemovedFirst;
ifaceCreationData.interfacesToBeDowngraded = interfacesToBeDowngraded;
return ifaceCreationData;
}
/**
* Compares two options to create an interface and determines which is the 'best'. Returns
* true if proposal 1 (val1) is better, other false.
*
* Note: both proposals are 'acceptable' bases on priority criteria.
*
* Criteria:
* - Proposal is better if it means removing fewer high priority interfaces, or downgrades the
* fewest interfaces.
*/
private boolean compareIfaceCreationData(IfaceCreationData val1, IfaceCreationData val2) {
if (VDBG) Log.d(TAG, "compareIfaceCreationData: val1=" + val1 + ", val2=" + val2);
// deal with trivial case of one or the other being null
if (val1 == null) {
return false;
} else if (val2 == null) {
return true;
}
int[] val1NumIfacesToBeRemoved = new int[CREATE_TYPES_BY_PRIORITY.length];
if (val1.chipInfo.currentModeIdValid
&& val1.chipInfo.currentModeId != val1.chipModeId) {
for (int createType : CREATE_TYPES_BY_PRIORITY) {
val1NumIfacesToBeRemoved[createType] = val1.chipInfo.ifaces[createType].length;
}
} else {
for (WifiIfaceInfo ifaceToRemove : val1.interfacesToBeRemovedFirst) {
val1NumIfacesToBeRemoved[ifaceToRemove.createType]++;
}
}
int[] val2NumIfacesToBeRemoved = new int[CREATE_TYPES_BY_PRIORITY.length];
if (val2.chipInfo.currentModeIdValid
&& val2.chipInfo.currentModeId != val2.chipModeId) {
for (int createType : CREATE_TYPES_BY_PRIORITY) {
val2NumIfacesToBeRemoved[createType] = val2.chipInfo.ifaces[createType].length;
}
} else {
for (WifiIfaceInfo ifaceToRemove : val2.interfacesToBeRemovedFirst) {
val2NumIfacesToBeRemoved[ifaceToRemove.createType]++;
}
}
for (int createType: CREATE_TYPES_BY_PRIORITY) {
if (val1NumIfacesToBeRemoved[createType] < val2NumIfacesToBeRemoved[createType]) {
if (VDBG) {
Log.d(TAG, "decision based on createType=" + createType + ": "
+ val1NumIfacesToBeRemoved[createType]
+ " < " + val2NumIfacesToBeRemoved[createType]);
}
return true;
}
}
int val1NumIFacesToBeDowngraded = val1.interfacesToBeDowngraded != null
? val1.interfacesToBeDowngraded.size() : 0;
int val2NumIFacesToBeDowngraded = val2.interfacesToBeDowngraded != null
? val2.interfacesToBeDowngraded.size() : 0;
if (val1NumIFacesToBeDowngraded != val2NumIFacesToBeDowngraded) {
return val1NumIFacesToBeDowngraded < val2NumIFacesToBeDowngraded;
}
// arbitrary - flip a coin
if (VDBG) Log.d(TAG, "proposals identical - flip a coin");
return false;
}
private static final int PRIORITY_INTERNAL = 0;
private static final int PRIORITY_BG = 1;
private static final int PRIORITY_FG_SERVICE = 2;
private static final int PRIORITY_FG_APP = 3;
private static final int PRIORITY_SYSTEM = 4;
private static final int PRIORITY_PRIVILEGED = 5;
// Keep these in sync with any additions/deletions to above buckets.
private static final int PRIORITY_MIN = PRIORITY_INTERNAL;
private static final int PRIORITY_MAX = PRIORITY_PRIVILEGED;
@IntDef(prefix = { "PRIORITY_" }, value = {
PRIORITY_INTERNAL,
PRIORITY_BG,
PRIORITY_FG_SERVICE,
PRIORITY_FG_APP,
PRIORITY_SYSTEM,
PRIORITY_PRIVILEGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestorWsPriority {}
/**
* Returns integer priority level for the provided |ws| based on rules mentioned in
* {@link #selectInterfacesToDelete(int, int, WorkSource, int, WifiIfaceInfo[])}.
*/
private static @RequestorWsPriority int getRequestorWsPriority(WorkSourceHelper ws) {
if (ws.hasAnyPrivilegedAppRequest()) return PRIORITY_PRIVILEGED;
if (ws.hasAnySystemAppRequest()) return PRIORITY_SYSTEM;
if (ws.hasAnyForegroundAppRequest(/* allowOverlayBypass */ true)) return PRIORITY_FG_APP;
if (ws.hasAnyForegroundServiceRequest()) return PRIORITY_FG_SERVICE;
if (ws.hasAnyInternalRequest()) return PRIORITY_INTERNAL;
return PRIORITY_BG;
}
/**
* Returns whether interface request from |newRequestorWsPriority| is allowed to delete an
* interface request from |existingRequestorWsPriority|.
*
* Rule:
* - If |newRequestorWsPriority| > |existingRequestorWsPriority|, then YES.
* - If they are at the same priority level, then
* - If both are privileged and not for the same interface type, then YES.
* - Else, NO.
*/
private static boolean allowedToDelete(
@HdmIfaceTypeForCreation int requestedCreateType,
@RequestorWsPriority int newRequestorWsPriority,
@HdmIfaceTypeForCreation int existingCreateType,
@RequestorWsPriority int existingRequestorWsPriority) {
if (!SdkLevel.isAtLeastS()) {
return allowedToDeleteForR(requestedCreateType, existingCreateType);
}
// If the new request is higher priority than existing priority, then the new requestor
// wins. This is because at all other priority levels (except privileged), existing caller
// wins if both the requests are at the same priority level.
if (newRequestorWsPriority > existingRequestorWsPriority) {
return true;
}
if (newRequestorWsPriority == existingRequestorWsPriority) {
// If both the requests are same priority for the same iface type, the existing
// requestor wins.
if (requestedCreateType == existingCreateType) {
return false;
}
// If both the requests are privileged, the new requestor wins. The exception is for
// backwards compatibility with P2P Settings, prefer SoftAP over P2P for when the user
// enables SoftAP with P2P Settings open.
if (newRequestorWsPriority == PRIORITY_PRIVILEGED) {
if (requestedCreateType == HDM_CREATE_IFACE_P2P
&& (existingCreateType == HDM_CREATE_IFACE_AP
|| existingCreateType == HDM_CREATE_IFACE_AP_BRIDGE)) {
return false;
}
return true;
}
}
return false;
}
/**
* Returns true if we're allowed to delete the existing interface type for the requested
* interface type.
*
* Rules - applies in order:
*
* General rules:
* 1. No interface will be destroyed for a requested interface of the same type
*
* Type-specific rules (but note that the general rules are applied first):
* 2. Request for AP or STA will destroy any other interface
* 3. Request for P2P will destroy NAN-only
* 4. Request for NAN will destroy P2P-only
*/
private static boolean allowedToDeleteForR(
@HdmIfaceTypeForCreation int requestedCreateType,
@HdmIfaceTypeForCreation int existingCreateType) {
// rule 1
if (existingCreateType == requestedCreateType) {
return false;
}
// rule 2
if (requestedCreateType == HDM_CREATE_IFACE_P2P) {
return existingCreateType == HDM_CREATE_IFACE_NAN;
}
// rule 3
if (requestedCreateType == HDM_CREATE_IFACE_NAN) {
return existingCreateType == HDM_CREATE_IFACE_P2P;
}
// rule 4, the requestedCreateType is either AP/AP_BRIDGED or STA
return true;
}
/**
* Selects the interfaces of a given type and quantity to delete for a requested interface.
* If the specified quantity of interfaces cannot be deleted, returns null.
*
* Only interfaces with lower priority than the requestor will be selected, in ascending order
* of priority. Priority is determined by the following rules:
* 1. Requests for interfaces have the following priority which are based on corresponding
* requesting app's context. Priorities in decreasing order (i.e (i) has the highest priority,
* (v) has the lowest priority).
* - (i) Requests from privileged apps (i.e settings, setup wizard, connectivity stack, etc)
* - (ii) Requests from system apps.
* - (iii) Requests from foreground apps.
* - (iv) Requests from foreground services.
* - (v) Requests from everything else (lumped together as "background").
* Note: If there are more than 1 app requesting for a particular interface, then we consider
* the priority of the highest priority app among them.
* For ex: If there is a system app and a foreground requesting for NAN iface, then we use the
* system app to determine the priority of the interface request.
* 2. If there are 2 conflicting interface requests from apps with the same priority, then
* - (i) If both the apps are privileged and not for the same interface type, the new request
* wins (last caller wins).
* - (ii) Else, the existing request wins (first caller wins).
* Note: Privileged apps are the ones that the user is directly interacting with, hence we use
* last caller wins to decide among those, for all other apps we try to minimize disruption to
* existing requests.
* For ex: User turns on wifi, then hotspot on legacy devices which do not support STA + AP, we
* want the last request from the user (i.e hotspot) to be honored.
*
* @param requestedQuantity Number of interfaces which need to be selected.
* @param requestedCreateType Requested iface type.
* @param requestorWs Requestor worksource.
* @param existingCreateType Existing iface type.
* @param existingInterfaces Array of interfaces to be selected from in order of creation.
*/
private List<WifiIfaceInfo> selectInterfacesToDelete(int requestedQuantity,
@HdmIfaceTypeForCreation int requestedCreateType, @NonNull WorkSource requestorWs,
@HdmIfaceTypeForCreation int existingCreateType,
@NonNull WifiIfaceInfo[] existingInterfaces) {
if (VDBG) {
Log.d(TAG, "selectInterfacesToDelete: requestedQuantity=" + requestedQuantity
+ ", requestedCreateType=" + requestedCreateType
+ ", requestorWs=" + requestorWs
+ ", existingCreateType=" + existingCreateType
+ ", existingInterfaces=" + Arrays.toString(existingInterfaces));
}
WorkSourceHelper newRequestorWsHelper = mWifiInjector.makeWsHelper(requestorWs);
boolean lookupError = false;
// Map of priority levels to ifaces to delete.
Map<Integer, List<WifiIfaceInfo>> ifacesToDeleteMap = new HashMap<>();
// Reverse order to make sure later created interfaces deleted firstly
for (int i = existingInterfaces.length - 1; i >= 0; i--) {
WifiIfaceInfo info = existingInterfaces[i];
InterfaceCacheEntry cacheEntry;
synchronized (mLock) {
cacheEntry = mInterfaceInfoCache.get(Pair.create(info.name, getType(info.iface)));
}
if (cacheEntry == null) {
Log.e(TAG,
"selectInterfacesToDelete: can't find cache entry with name=" + info.name);
lookupError = true;
break;
}
int newRequestorWsPriority = getRequestorWsPriority(newRequestorWsHelper);
int existingRequestorWsPriority = getRequestorWsPriority(cacheEntry.requestorWsHelper);
boolean isAllowedToDelete = allowedToDelete(requestedCreateType, newRequestorWsPriority,
existingCreateType, existingRequestorWsPriority);
if (VDBG) {
Log.d(TAG, "info=" + info + ": allowedToDelete=" + isAllowedToDelete
+ " (requestedCreateType=" + requestedCreateType
+ ", newRequestorWsPriority=" + newRequestorWsPriority
+ ", existingCreateType=" + existingCreateType
+ ", existingRequestorWsPriority=" + existingRequestorWsPriority + ")");
}
if (isAllowedToDelete) {
ifacesToDeleteMap.computeIfAbsent(
existingRequestorWsPriority, v -> new ArrayList<>()).add(info);
}
}
List<WifiIfaceInfo> ifacesToDelete;
if (lookupError) {
Log.e(TAG, "selectInterfacesToDelete: falling back to arbitrary selection");
ifacesToDelete = Arrays.asList(Arrays.copyOf(existingInterfaces, requestedQuantity));
} else {
int numIfacesToDelete = 0;
ifacesToDelete = new ArrayList<>(requestedQuantity);
// Iterate from lowest priority to highest priority ifaces.
for (int i = PRIORITY_MIN; i <= PRIORITY_MAX; i++) {
List<WifiIfaceInfo> ifacesToDeleteListWithinPriority =
ifacesToDeleteMap.getOrDefault(i, new ArrayList<>());
int numIfacesToDeleteWithinPriority =
Math.min(requestedQuantity - numIfacesToDelete,
ifacesToDeleteListWithinPriority.size());
ifacesToDelete.addAll(
ifacesToDeleteListWithinPriority.subList(
0, numIfacesToDeleteWithinPriority));
numIfacesToDelete += numIfacesToDeleteWithinPriority;
if (numIfacesToDelete == requestedQuantity) {
break;
}
}
}
if (ifacesToDelete.size() < requestedQuantity) {
return null;
}
return ifacesToDelete;
}
/**
* Selects the requested quantity of bridged AP ifaces available for downgrade in order of
* creation, or returns null if the requested quantity cannot be satisfied.
*
* @param requestedQuantity Number of interfaces which need to be selected
* @param bridgedApIfaces Array of bridged AP interfaces in order of creation
*/
private List<WifiIfaceInfo> selectBridgedApInterfacesToDowngrade(int requestedQuantity,
WifiIfaceInfo[] bridgedApIfaces) {
List<WifiIfaceInfo> ifacesToDowngrade = new ArrayList<>();
for (WifiIfaceInfo ifaceInfo : bridgedApIfaces) {
SoftApManager softApManager = mSoftApManagers.get(ifaceInfo.iface);
if (softApManager == null) {
Log.e(TAG, "selectBridgedApInterfacesToDowngrade: Could not find SoftApManager for"
+ " iface: " + ifaceInfo.iface);
continue;
}
String instanceForRemoval =
softApManager.getBridgedApDowngradeIfaceInstanceForRemoval();
if (instanceForRemoval == null) {
continue;
}
ifacesToDowngrade.add(ifaceInfo);
if (ifacesToDowngrade.size() >= requestedQuantity) {
break;
}
}
if (ifacesToDowngrade.size() < requestedQuantity) {
return null;
}
if (VDBG) {
Log.i(TAG, "selectBridgedApInterfacesToDowngrade: ifaces to downgrade "
+ ifacesToDowngrade);
}
return ifacesToDowngrade;
}
/**
* Checks whether the expanded @HdmIfaceTypeForCreation combo can support the requested combo.
*/
private boolean canCreateTypeComboSupportRequestedCreateTypeCombo(
int[] chipCombo, int[] requestedCombo) {
if (VDBG) {
Log.d(TAG, "canCreateTypeComboSupportRequestedCreateTypeCombo: "
+ "chipCombo=" + Arrays.toString(chipCombo)
+ ", requestedCombo=" + Arrays.toString(requestedCombo));
}
for (int createType : CREATE_TYPES_BY_PRIORITY) {
if (chipCombo[createType]
< requestedCombo[createType]) {
if (VDBG) Log.d(TAG, "Requested type not supported by combo");
return false;
}
}
return true;
}
/**
* Performs chip reconfiguration per the input:
* - Removes the specified interfaces
* - Reconfigures the chip to the new chip mode (if necessary)
* - Creates the new interface
*
* Returns the newly created interface or a null on any error.
*/
private IWifiIface executeChipReconfiguration(IfaceCreationData ifaceCreationData,
@HdmIfaceTypeForCreation int createIfaceType) {
if (mDbg) {
Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData
+ ", createIfaceType=" + createIfaceType);
}
synchronized (mLock) {
try {
// is this a mode change?
boolean isModeConfigNeeded = !ifaceCreationData.chipInfo.currentModeIdValid
|| ifaceCreationData.chipInfo.currentModeId != ifaceCreationData.chipModeId;
if (mDbg) Log.d(TAG, "isModeConfigNeeded=" + isModeConfigNeeded);
// first delete interfaces/change modes
if (isModeConfigNeeded) {
// remove all interfaces pre mode-change
// TODO: is this necessary? note that even if we don't want to explicitly
// remove the interfaces we do need to call the onDeleted callbacks - which
// this does
for (WifiIfaceInfo[] ifaceInfos : ifaceCreationData.chipInfo.ifaces) {
for (WifiIfaceInfo ifaceInfo : ifaceInfos) {
removeIfaceInternal(ifaceInfo.iface,
/* validateRttController */false); // ignore return value
}
}
WifiStatus status = ifaceCreationData.chipInfo.chip.configureChip(
ifaceCreationData.chipModeId);
if (status.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "executeChipReconfiguration: configureChip error: "
+ statusString(status));
return null;
}
} else {
// remove all interfaces on the delete list
for (WifiIfaceInfo ifaceInfo : ifaceCreationData.interfacesToBeRemovedFirst) {
removeIfaceInternal(ifaceInfo.iface,
/* validateRttController */false); // ignore return value
}
// downgrade all interfaces on the downgrade list
for (WifiIfaceInfo ifaceInfo : ifaceCreationData.interfacesToBeDowngraded) {
if (ifaceInfo.createType == HDM_CREATE_IFACE_AP_BRIDGE) {
if (!downgradeBridgedApIface(ifaceInfo)) {
Log.e(TAG, "executeChipReconfiguration: failed to downgrade bridged"
+ " AP: " + ifaceInfo);
return null;
}
}
}
}
// create new interface
Mutable<WifiStatus> statusResp = new Mutable<>();
Mutable<IWifiIface> ifaceResp = new Mutable<>();
switch (createIfaceType) {
case HDM_CREATE_IFACE_STA:
ifaceCreationData.chipInfo.chip.createStaIface(
(WifiStatus status, IWifiStaIface iface) -> {
statusResp.value = status;
ifaceResp.value = iface;
});
break;
case HDM_CREATE_IFACE_AP_BRIDGE:
android.hardware.wifi.V1_5.IWifiChip chip15 =
getWifiChipForV1_5Mockable(ifaceCreationData.chipInfo.chip);
if (chip15 != null) {
chip15.createBridgedApIface(
(WifiStatus status,
android.hardware.wifi.V1_5.IWifiApIface iface) -> {
statusResp.value = status;
ifaceResp.value = iface;
});
} else {
Log.e(TAG, "Hal doesn't support to create AP bridge mode");
return null;
}
break;
case HDM_CREATE_IFACE_AP:
ifaceCreationData.chipInfo.chip.createApIface(
(WifiStatus status, IWifiApIface iface) -> {
statusResp.value = status;
ifaceResp.value = iface;
});
break;
case HDM_CREATE_IFACE_P2P:
ifaceCreationData.chipInfo.chip.createP2pIface(
(WifiStatus status, IWifiP2pIface iface) -> {
statusResp.value = status;
ifaceResp.value = iface;
});
break;
case HDM_CREATE_IFACE_NAN:
ifaceCreationData.chipInfo.chip.createNanIface(
(WifiStatus status, IWifiNanIface iface) -> {
statusResp.value = status;
ifaceResp.value = iface;
});
break;
}
updateRttControllerWhenInterfaceChanges();
if (statusResp.value.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "executeChipReconfiguration: failed to create interface"
+ " createIfaceType=" + createIfaceType + ": "
+ statusString(statusResp.value));
return null;
}
return ifaceResp.value;
} catch (RemoteException e) {
Log.e(TAG, "executeChipReconfiguration exception: " + e);
return null;
}
}
}
/**
* Remove a Iface from IWifiChip.
* @param iface the interface need to be removed
* @param validateRttController if RttController validation is required. If any iface creation
* is guaranteed after removing iface, this can be false. Otherwise
* this must be true.
* @return True if removal succeed, otherwise false.
*/
private boolean removeIfaceInternal(IWifiIface iface, boolean validateRttController) {
String name = getName(iface);
int type = getType(iface);
if (mDbg) Log.d(TAG, "removeIfaceInternal: iface(name)=" + name + ", type=" + type);
if (type == -1) {
Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + name);
return false;
}
synchronized (mLock) {
if (mWifi == null) {
Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + name);
return false;
}
IWifiChip chip = getChip(iface);
if (chip == null) {
Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + name);
return false;
}
if (name == null) {
Log.e(TAG, "removeIfaceInternal: can't get name");
return false;
}
// dispatch listeners on other threads to prevent race conditions in case the HAL is
// blocking and they get notification about destruction from HAL before cleaning up
// status.
dispatchDestroyedListeners(name, type, true);
WifiStatus status = null;
try {
switch (type) {
case IfaceType.STA:
status = chip.removeStaIface(name);
break;
case IfaceType.AP:
status = chip.removeApIface(name);
break;
case IfaceType.P2P:
status = chip.removeP2pIface(name);
break;
case IfaceType.NAN:
status = chip.removeNanIface(name);
break;
default:
Log.wtf(TAG, "removeIfaceInternal: invalid type=" + type);
return false;
}
} catch (RemoteException e) {
Log.e(TAG, "IWifiChip.removeXxxIface exception: " + e);
}
// dispatch listeners no matter what status
dispatchDestroyedListeners(name, type, false);
if (validateRttController) {
// Try to update the RttController
updateRttControllerWhenInterfaceChanges();
}
if (status != null && status.code == WifiStatusCode.SUCCESS) {
return true;
} else {
Log.e(TAG, "IWifiChip.removeXxxIface failed: " + statusString(status));
return false;
}
}
}
// dispatch all destroyed listeners registered for the specified interface AND remove the
// cache entries for the called listeners
// onlyOnOtherThreads = true: only call listeners on other threads
// onlyOnOtherThreads = false: call all listeners
private void dispatchDestroyedListeners(String name, int type, boolean onlyOnOtherThreads) {
if (VDBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name);
List<InterfaceDestroyedListenerProxy> triggerList = new ArrayList<>();
synchronized (mLock) {
InterfaceCacheEntry entry = mInterfaceInfoCache.get(Pair.create(name, type));
if (entry == null) {
Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)=" + name);
return;
}
Iterator<InterfaceDestroyedListenerProxy> iterator =
entry.destroyedListeners.iterator();
while (iterator.hasNext()) {
InterfaceDestroyedListenerProxy listener = iterator.next();
if (!onlyOnOtherThreads || !listener.requestedToRunInCurrentThread()) {
triggerList.add(listener);
iterator.remove();
}
}
if (!onlyOnOtherThreads) { // leave entry until final call to *all* callbacks
mInterfaceInfoCache.remove(Pair.create(name, type));
}
}
for (InterfaceDestroyedListenerProxy listener : triggerList) {
listener.trigger();
}
}
// dispatch all destroyed listeners registered to all interfaces
private void dispatchAllDestroyedListeners() {
if (VDBG) Log.d(TAG, "dispatchAllDestroyedListeners");
List<InterfaceDestroyedListenerProxy> triggerList = new ArrayList<>();
synchronized (mLock) {
for (InterfaceCacheEntry cacheEntry: mInterfaceInfoCache.values()) {
for (InterfaceDestroyedListenerProxy listener : cacheEntry.destroyedListeners) {
triggerList.add(listener);
}
cacheEntry.destroyedListeners.clear(); // for insurance
}
mInterfaceInfoCache.clear();
}
for (InterfaceDestroyedListenerProxy listener : triggerList) {
listener.trigger();
}
}
private boolean downgradeBridgedApIface(WifiIfaceInfo bridgedApIfaceInfo) {
SoftApManager bridgedSoftApManager = mSoftApManagers.get(bridgedApIfaceInfo.iface);
if (bridgedSoftApManager == null) {
Log.e(TAG, "Could not find SoftApManager for bridged AP iface "
+ bridgedApIfaceInfo.iface);
return false;
}
String name = getName(bridgedApIfaceInfo.iface);
if (name == null) {
return false;
}
IWifiChip chip = getChip(bridgedApIfaceInfo.iface);
if (chip == null) {
return false;
}
android.hardware.wifi.V1_5.IWifiChip chip15 = getWifiChipForV1_5Mockable(chip);
if (chip15 == null) {
return false;
}
String instanceForRemoval =
bridgedSoftApManager.getBridgedApDowngradeIfaceInstanceForRemoval();
try {
chip15.removeIfaceInstanceFromBridgedApIface(name, instanceForRemoval);
} catch (RemoteException e) {
Log.e(TAG,
"IWifiChip.removeIfaceInstanceFromBridgedApIface exception: " + e);
return false;
}
return true;
}
private abstract class ListenerProxy<LISTENER> {
protected LISTENER mListener;
private Handler mHandler;
// override equals & hash to make sure that the container HashSet is unique with respect to
// the contained listener
@Override
public boolean equals(Object obj) {
return mListener == ((ListenerProxy<LISTENER>) obj).mListener;
}
@Override
public int hashCode() {
return mListener.hashCode();
}
public boolean requestedToRunInCurrentThread() {
if (mHandler == null) return true;
long currentTid = mWifiInjector.getCurrentThreadId();
long handlerTid = mHandler.getLooper().getThread().getId();
return currentTid == handlerTid;
}
void trigger() {
// TODO(b/199792691): The thread check is needed to preserve the existing
// assumptions of synchronous execution of the "onDestroyed" callback as much as
// possible. This is needed to prevent regressions caused by posting to the handler
// thread changing the code execution order.
// When all wifi services (ie. WifiAware, WifiP2p) get moved to the wifi handler
// thread, remove this thread check and the Handler#post() and simply always
// invoke the callback directly.
if (requestedToRunInCurrentThread()) {
// Already running on the same handler thread. Trigger listener synchronously.
action();
} else {
// Current thread is not the thread the listener should be invoked on.
// Post action to the intended thread.
mHandler.postAtFrontOfQueue(() -> {
action();
});
}
}
protected void action() {}
ListenerProxy(LISTENER listener, Handler handler, String tag) {
mListener = listener;
mHandler = handler;
}
}
private class SubsystemRestartListenerProxy extends
ListenerProxy<SubsystemRestartListener> {
SubsystemRestartListenerProxy(@NonNull SubsystemRestartListener subsystemRestartListener,
Handler handler) {
super(subsystemRestartListener, handler, "SubsystemRestartListenerProxy");
}
@Override
protected void action() {
mListener.onSubsystemRestart();
}
}
private class InterfaceDestroyedListenerProxy extends
ListenerProxy<InterfaceDestroyedListener> {
private final String mIfaceName;
InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
@NonNull InterfaceDestroyedListener destroyedListener,
@NonNull Handler handler) {
super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
mIfaceName = ifaceName;
}
@Override
protected void action() {
mListener.onDestroyed(mIfaceName);
}
}
private class InterfaceRttControllerLifecycleCallbackProxy implements
InterfaceRttControllerLifecycleCallback {
private InterfaceRttControllerLifecycleCallback mCallback;
private Handler mHandler;
InterfaceRttControllerLifecycleCallbackProxy(
InterfaceRttControllerLifecycleCallback callback, Handler handler) {
mCallback = callback;
mHandler = handler;
}
// override equals & hash to make sure that the container HashSet is unique with respect to
// the contained listener
@Override
public boolean equals(Object obj) {
return mCallback == ((InterfaceRttControllerLifecycleCallbackProxy) obj).mCallback;
}
@Override
public int hashCode() {
return mCallback.hashCode();
}
@Override
public void onNewRttController(IWifiRttController controller) {
mHandler.post(() -> mCallback.onNewRttController(controller));
}
@Override
public void onRttControllerDestroyed() {
mHandler.post(() -> mCallback.onRttControllerDestroyed());
}
}
private void dispatchRttControllerLifecycleOnNew() {
if (VDBG) {
Log.v(TAG, "dispatchRttControllerLifecycleOnNew: # cbs="
+ mRttControllerLifecycleCallbacks.size());
}
for (InterfaceRttControllerLifecycleCallbackProxy cbp : mRttControllerLifecycleCallbacks) {
cbp.onNewRttController(mIWifiRttController);
}
}
private void dispatchRttControllerLifecycleOnDestroyed() {
for (InterfaceRttControllerLifecycleCallbackProxy cbp : mRttControllerLifecycleCallbacks) {
cbp.onRttControllerDestroyed();
}
}
/**
* Updates the RttController when the interface changes:
* - Handles callbacks to registered listeners
* - Handles creation of new RttController
*/
private void updateRttControllerWhenInterfaceChanges() {
synchronized (mLock) {
if (validateRttController()) {
if (mDbg) {
Log.d(TAG, "Current RttController is valid, Don't try to create a new one");
}
return;
}
boolean controllerDestroyed = mIWifiRttController != null;
mIWifiRttController = null;
if (mRttControllerLifecycleCallbacks.size() == 0) {
Log.d(TAG, "updateRttController: no one is interested in RTT controllers");
return;
}
IWifiRttController newRttController = createRttControllerIfPossible();
if (newRttController == null) {
if (controllerDestroyed) {
dispatchRttControllerLifecycleOnDestroyed();
}
} else {
mIWifiRttController = newRttController;
dispatchRttControllerLifecycleOnNew();
}
}
}
private boolean validateRttController() {
if (mIWifiRttController == null) {
return false;
}
Mutable<Boolean> isRttControllerValid = new Mutable<>(false);
try {
mIWifiRttController.getBoundIface(
(status, iface) -> {
if (status.code == WifiStatusCode.SUCCESS) {
isRttControllerValid.value = true;
}
});
} catch (RemoteException e) {
Log.e(TAG, "RttController: exception getBoundIface" + e);
}
return isRttControllerValid.value;
}
/**
* Try to create a new RttController.
*
* @return The new RttController - or null on failure.
*/
private IWifiRttController createRttControllerIfPossible() {
synchronized (mLock) {
if (!isWifiStarted()) {
Log.d(TAG, "createRttControllerIfPossible: Wifi is not started");
return null;
}
WifiChipInfo[] chipInfos = getAllChipInfo();
if (chipInfos == null) {
Log.d(TAG, "createRttControllerIfPossible: no chip info found - most likely chip "
+ "not up yet");
return null;
}
for (WifiChipInfo chipInfo : chipInfos) {
if (!chipInfo.currentModeIdValid) {
if (VDBG) {
Log.d(TAG, "createRttControllerIfPossible: chip not configured yet: "
+ chipInfo);
}
continue;
}
Mutable<IWifiRttController> rttResp = new Mutable<>();
try {
android.hardware.wifi.V1_6.IWifiChip chip16 =
android.hardware.wifi.V1_6.IWifiChip.castFrom(chipInfo.chip);
android.hardware.wifi.V1_4.IWifiChip chip14 =
android.hardware.wifi.V1_4.IWifiChip.castFrom(chipInfo.chip);
if (chip16 != null) {
chip16.createRttController_1_6(null,
(WifiStatus status,
android.hardware.wifi.V1_6.IWifiRttController rtt) -> {
if (status.code == WifiStatusCode.SUCCESS) {
rttResp.value = rtt;
} else {
Log.e(TAG, "IWifiChip.createRttController_1_6 failed: "
+ statusString(status));
}
});
} else if (chip14 != null) {
chip14.createRttController_1_4(null,
(WifiStatus status,
android.hardware.wifi.V1_4.IWifiRttController rtt) -> {
if (status.code == WifiStatusCode.SUCCESS) {
rttResp.value = rtt;
} else {
Log.e(TAG, "IWifiChip.createRttController_1_4 failed: "
+ statusString(status));
}
});
} else {
chipInfo.chip.createRttController(null,
(WifiStatus status, IWifiRttController rtt) -> {
if (status.code == WifiStatusCode.SUCCESS) {
rttResp.value = rtt;
} else {
Log.e(TAG, "IWifiChip.createRttController failed: "
+ statusString(status));
}
});
}
} catch (RemoteException e) {
Log.e(TAG, "IWifiChip.createRttController exception: " + e);
}
if (rttResp.value != null) {
return rttResp.value;
}
}
}
Log.w(TAG, "createRttControllerIfPossible: not available from any of the chips");
return null;
}
// general utilities
private static String statusString(WifiStatus status) {
if (status == null) {
return "status=null";
}
StringBuilder sb = new StringBuilder();
sb.append(status.code).append(" (").append(status.description).append(")");
return sb.toString();
}
// Will return -1 for invalid results! Otherwise will return one of the 4 valid values.
private static int getType(IWifiIface iface) {
Mutable<Integer> typeResp = new Mutable<>(-1);
try {
iface.getType((WifiStatus status, int type) -> {
if (status.code == WifiStatusCode.SUCCESS) {
typeResp.value = type;
} else {
Log.e(TAG, "Error on getType: " + statusString(status));
}
});
} catch (RemoteException e) {
Log.e(TAG, "Exception on getType: " + e);
}
return typeResp.value;
}
/**
* 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(String method, WifiStatus status) {
if (status.code == WifiStatusCode.SUCCESS) return true;
Log.e(TAG, "Error on " + method + ": " + statusString(status));
return false;
}
private static SparseBooleanArray convertRadioCombinationMatrixToLookupTable(
WifiRadioCombinationMatrix matrix) {
SparseBooleanArray lookupTable = new SparseBooleanArray();
if (null == matrix) return lookupTable;
for (WifiRadioCombination combination: matrix.radioCombinations) {
int bandMask = 0;
for (WifiRadioConfiguration config: combination.radioConfigurations) {
bandMask |= 1 << config.bandInfo;
}
if ((bandMask & DBS_24G_5G_MASK) == DBS_24G_5G_MASK) {
lookupTable.put(DBS_24G_5G_MASK, true);
} else if ((bandMask & DBS_5G_6G_MASK) == DBS_5G_6G_MASK) {
lookupTable.put(DBS_5G_6G_MASK, true);
}
}
return lookupTable;
}
/**
* Get the chip capabilities
*
* This is called before creating an interface and needs at least v1.5 HAL.
*
* @param wifiChip WifiChip
* @return bitmask defined by HAL interface
*/
public long getChipCapabilities(@NonNull IWifiChip wifiChip) {
long featureSet = 0;
if (wifiChip == null) return featureSet;
try {
final Mutable<Long> feat = new Mutable<>(CHIP_CAPABILITY_UNINITIALIZED);
synchronized (mLock) {
android.hardware.wifi.V1_5.IWifiChip iWifiChipV15 =
getWifiChipForV1_5Mockable(wifiChip);
// HAL newer than v1.5 support getting capabilities before creating an interface.
if (iWifiChipV15 != null) {
iWifiChipV15.getCapabilities_1_5((status, capabilities) -> {
if (!ok("getCapabilities_1_5", status)) return;
feat.value = (long) capabilities;
});
}
}
featureSet = feat.value;
} catch (RemoteException e) {
Log.e(TAG, "Exception on getCapabilities: " + e);
return 0;
}
return featureSet;
}
/**
* Get the supported radio combination matrix.
*
* This is called after creating an interface and need at least v1.6 HAL.
*
* @param wifiChip WifiChip
* @return Wifi radio combinmation matrix
*/
private WifiRadioCombinationMatrix getChipSupportedRadioCombinationsMatrix(
@NonNull IWifiChip wifiChip) {
synchronized (mLock) {
if (null == wifiChip) return null;
android.hardware.wifi.V1_6.IWifiChip chipV16 =
getWifiChipForV1_6Mockable(wifiChip);
if (null == chipV16) return null;
Mutable<WifiRadioCombinationMatrix> radioCombinationMatrixResp =
new Mutable<>();
radioCombinationMatrixResp.value = null;
try {
chipV16.getSupportedRadioCombinationsMatrix((WifiStatus status,
WifiRadioCombinationMatrix matrix) -> {
if (status.code == WifiStatusCode.SUCCESS) {
radioCombinationMatrixResp.value = matrix;
if (mDbg) {
Log.d(TAG, "radioCombinationMatrix=" + matrix);
}
} else {
Log.e(TAG, "getSupportedRadioCombinationsMatrix failed: "
+ statusString(status));
}
});
} catch (RemoteException e) {
Log.e(TAG, "Exception on getSupportedRadioCombinationsMatrix: " + e);
radioCombinationMatrixResp.value = null;
}
return radioCombinationMatrixResp.value;
}
}
/**
* Dump the internal state of the class.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Dump of HalDeviceManager:");
pw.println(" mServiceManager: " + mServiceManager);
pw.println(" mWifi: " + mWifi);
pw.println(" mManagerStatusListeners: " + mManagerStatusListeners);
pw.println(" mInterfaceInfoCache: " + mInterfaceInfoCache);
pw.println(" mDebugChipsInfo: " + Arrays.toString(getAllChipInfo()));
}
}