/* * 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 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 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 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 ""; } Mutable 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 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> 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> 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> 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> 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 mManagerStatusListeners = new HashSet<>(); private final Set mRttControllerLifecycleCallbacks = new HashSet<>(); private final SparseArray mDebugCallbacks = new SparseArray<>(); private boolean mIsReady; private final Set 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, InterfaceCacheEntry> mInterfaceInfoCache = new HashMap<>(); private class InterfaceCacheEntry { public IWifiChip chip; public int chipId; public String name; public int type; public Set 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 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 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 statusOk = new Mutable<>(false); Mutable> chipIdsResp = new Mutable<>(); // get all chip IDs mWifi.getChipIds((WifiStatus status, ArrayList 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 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 data) throws RemoteException { Log.d(TAG, "onDebugRingBufferDataAvailable"); } @Override public void onDebugErrorAlert(int errorCode, ArrayList 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 statusOk = new Mutable<>(false); Mutable> chipIdsResp = new Mutable<>(); // get all chip IDs mWifi.getChipIds((WifiStatus status, ArrayList 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 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> availableModesResp = new Mutable<>(); android.hardware.wifi.V1_6.IWifiChip chipV16 = getWifiChipForV1_6Mockable(chipResp.value); if (chipV16 != null) { chipV16.getAvailableModes_1_6((WifiStatus status, ArrayList 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 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 currentModeValidResp = new Mutable<>(false); Mutable 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 chipCapabilities = new Mutable<>(0L); chipCapabilities.value = getChipCapabilities(chipResp.value); Mutable> ifaceNamesResp = new Mutable<>(); Mutable ifaceIndex = new Mutable<>(0); chipResp.value.getStaIfaceNames( (WifiStatus status, ArrayList 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 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 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 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 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 upgradeV1_0ChipModesToV1_6(ArrayList oldChipModes) { ArrayList 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 { ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) { super(statusListener, handler, "ManagerStatusListenerProxy"); } @Override protected void action() { mListener.onStatusChanged(); } } private Set getSupportedIfaceTypesInternal(IWifiChip chip) { Set results = new HashSet<>(); WifiChipInfo[] chipInfos = getAllChipInfoCached(); if (chipInfos == null) { Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found"); return results; } Mutable chipIdIfProvided = new Mutable<>(0); // NOT using 0 as a magic value if (chip != null) { Mutable 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> 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> getExpandedCreateTypeCombosPerChipModeId( ArrayList chipModes) { SparseArray> combosPerChipModeId = new SparseArray<>(); for (android.hardware.wifi.V1_6.IWifiChip.ChipMode chipMode : chipModes) { List 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 interfacesToBeRemovedFirst; public List 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 interfacesToBeRemovedFirst = new ArrayList<>(); List 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 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 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> 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 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 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 selectBridgedApInterfacesToDowngrade(int requestedQuantity, WifiIfaceInfo[] bridgedApIfaces) { List 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 statusResp = new Mutable<>(); Mutable 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 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 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 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 { 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) 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 { SubsystemRestartListenerProxy(@NonNull SubsystemRestartListener subsystemRestartListener, Handler handler) { super(subsystemRestartListener, handler, "SubsystemRestartListenerProxy"); } @Override protected void action() { mListener.onSubsystemRestart(); } } private class InterfaceDestroyedListenerProxy extends ListenerProxy { 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 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 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 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 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 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())); } }