/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import android.annotation.NonNull; import android.annotation.Nullable; 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.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.os.Handler; import android.os.HidlSupport.Mutable; import android.os.HwRemoteBinder; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; import android.util.MutableBoolean; import android.util.MutableInt; import android.util.Pair; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; 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; 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; @VisibleForTesting public static final String HAL_INSTANCE_NAME = "default"; private final Clock mClock; // public API public HalDeviceManager(Clock clock) { mClock = clock; mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashMap<>()); mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashMap<>()); mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashMap<>()); mInterfaceAvailableForRequestListeners.put(IfaceType.NAN, new HashMap<>()); } /* package */ void enableVerboseLogging(int verbose) { if (verbose > 0) { mDbg = true; } else { mDbg = false; } 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 isSupportedInternal(); } /** * 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 mWifi != null; } /** * 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() { stopWifi(); } /** * 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 lowPrioritySta Indicates whether the requested STA is a low priority STA. The priority * and preemption rules for low priority STA are: * - Do not destroy any interface for it (even another low priority STA) * - Destroy it for any other request * @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. Null implies the listener will be * invoked synchronously from the context of the client which triggered the * iface destruction. * @return A newly created interface - or null if the interface could not be created. */ public IWifiStaIface createStaIface(boolean lowPrioritySta, @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) { return (IWifiStaIface) createIface(IfaceType.STA, lowPrioritySta, destroyedListener, handler); } /** * Create AP interface if possible (see createStaIface doc). */ public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) { return (IWifiApIface) createIface(IfaceType.AP, false, destroyedListener, handler); } /** * Create P2P interface if possible (see createStaIface doc). */ public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) { return (IWifiP2pIface) createIface(IfaceType.P2P, false, destroyedListener, handler); } /** * Create NAN interface if possible (see createStaIface doc). */ public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) { return (IWifiNanIface) createIface(IfaceType.NAN, false, destroyedListener, handler); } /** * Removes (releases/destroys) the given interface. Will trigger any registered * InterfaceDestroyedListeners and possibly some InterfaceAvailableForRequestListeners if we * can potentially create some other interfaces as a result of removing this interface. */ public boolean removeIface(IWifiIface iface) { boolean success = removeIfaceInternal(iface); dispatchAvailableForRequestListeners(); return success; } /** * 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) { String name = getName(iface); int type = getType(iface); if (VDBG) Log.d(TAG, "getChip: iface(name)=" + name); synchronized (mLock) { InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type)); if (cacheEntry == null) { Log.e(TAG, "getChip: no entry for iface(name)=" + name); return null; } return cacheEntry.chip; } } /** * Register an InterfaceDestroyedListener to the specified iface - returns true on success * and false on failure. This listener is in addition to the one registered when the interface * was created - allowing non-creators to monitor interface status. * * @param destroyedListener 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. Null implies the listener will be * invoked synchronously from the context of the client which triggered the * iface destruction. */ public boolean registerDestroyedListener(IWifiIface iface, @NonNull InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) { String name = getName(iface); int type = getType(iface); if (VDBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name); synchronized (mLock) { InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type)); if (cacheEntry == null) { Log.e(TAG, "registerDestroyedListener: no entry for iface(name)=" + name); return false; } return cacheEntry.destroyedListeners.add( new InterfaceDestroyedListenerProxy(name, destroyedListener, handler)); } } /** * Register a listener to be called when an interface of the specified type could be requested. * No guarantees are provided (some other entity could request it first). The listener is * active from registration until unregistration - using * unregisterInterfaceAvailableForRequestListener(). * * Only a single instance of a listener will be registered (even if the specified looper is * different). * * Note that if it is possible to create the specified interface type at registration time * then the callback will be triggered immediately. * * @param ifaceType The interface type (IfaceType) to be monitored. * @param listener Listener to call when an interface of the requested * type could be created * @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 * mode change. */ public void registerInterfaceAvailableForRequestListener(int ifaceType, @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) { if (VDBG) { Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType + ", listener=" + listener + ", handler=" + handler); } synchronized (mLock) { InterfaceAvailableForRequestListenerProxy proxy = new InterfaceAvailableForRequestListenerProxy(listener, handler); if (mInterfaceAvailableForRequestListeners.get(ifaceType).containsKey(proxy)) { if (VDBG) { Log.d(TAG, "registerInterfaceAvailableForRequestListener: dup listener skipped: " + listener); } return; } mInterfaceAvailableForRequestListeners.get(ifaceType).put(proxy, null); } WifiChipInfo[] chipInfos = getAllChipInfo(); if (chipInfos == null) { Log.e(TAG, "registerInterfaceAvailableForRequestListener: no chip info found - but " + "possibly registered pre-started - ignoring"); return; } dispatchAvailableForRequestListenersForType(ifaceType, chipInfos); } /** * Unregisters a listener registered with registerInterfaceAvailableForRequestListener(). */ public void unregisterInterfaceAvailableForRequestListener( int ifaceType, InterfaceAvailableForRequestListener listener) { if (VDBG) { Log.d(TAG, "unregisterInterfaceAvailableForRequestListener: ifaceType=" + ifaceType); } synchronized (mLock) { mInterfaceAvailableForRequestListeners.get(ifaceType).remove( new InterfaceAvailableForRequestListenerProxy(listener, null)); } } /** * 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 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 when an interface type availability for creation is changed. */ public interface InterfaceAvailableForRequestListener { /** * Called when an interface type availability for creation is updated. Registered with * registerInterfaceAvailableForRequestListener() and unregistered with * unregisterInterfaceAvailableForRequestListener(). */ void onAvailabilityChanged(boolean isAvailable); } /** * Creates a IWifiRttController. A direct match to the IWifiChip.createRttController() method. * * Returns the created IWifiRttController or a null on error. */ public IWifiRttController createRttController() { if (VDBG) Log.d(TAG, "createRttController"); synchronized (mLock) { if (mWifi == null) { Log.e(TAG, "createRttController: null IWifi"); return null; } WifiChipInfo[] chipInfos = getAllChipInfo(); if (chipInfos == null) { Log.e(TAG, "createRttController: no chip info found"); stopWifi(); // major error: shutting down return null; } for (WifiChipInfo chipInfo : chipInfos) { Mutable rttResp = new Mutable<>(); try { 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.e(TAG, "createRttController: not available from any of the chips"); return null; } } // 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 final Object mLock = new Object(); private IServiceManager mServiceManager; private IWifi mWifi; private final WifiEventCallback mWifiEventCallback = new WifiEventCallback(); private final Set mManagerStatusListeners = new HashSet<>(); private final SparseArray> mInterfaceAvailableForRequestListeners = new SparseArray<>(); private final SparseArray mDebugCallbacks = new SparseArray<>(); /* * 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 boolean isLowPriority; @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{name=").append(name).append(", type=").append(type) .append(", destroyedListeners.size()=").append(destroyedListeners.size()) .append(", creationTime=").append(creationTime).append( ", isLowPriority=").append(isLowPriority).append("}"); return sb.toString(); } } private class WifiIfaceInfo { public String name; public IWifiIface iface; } private class WifiChipInfo { public IWifiChip chip; public int chipId; public ArrayList availableModes; public boolean currentModeIdValid; public int currentModeId; public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[IFACE_TYPES_BY_PRIORITY.length][]; @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); 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(); } catch (RemoteException e) { Log.e(TAG, "Exception getting IWifi service: " + e); return null; } } protected IServiceManager getServiceManagerMockable() { try { return IServiceManager.getService(); } catch (RemoteException e) { Log.e(TAG, "Exception getting IServiceManager: " + e); return null; } } // internal implementation private void initializeInternal() { initIServiceManagerIfNecessary(); if (isSupportedInternal()) { initIWifiIfNecessary(); } } private void teardownInternal() { managerStatusListenerDispatch(); dispatchAllDestroyedListeners(); mInterfaceAvailableForRequestListeners.get(IfaceType.STA).clear(); mInterfaceAvailableForRequestListeners.get(IfaceType.AP).clear(); mInterfaceAvailableForRequestListeners.get(IfaceType.P2P).clear(); mInterfaceAvailableForRequestListeners.get(IfaceType.NAN).clear(); } private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient = cookie -> { 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; } } } } /** * 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 { return (mServiceManager.getTransport(IWifi.kInterfaceName, HAL_INSTANCE_NAME) != IServiceManager.Transport.EMPTY); } catch (RemoteException e) { Log.wtf(TAG, "Exception while operating on IServiceManager: " + e); return false; } } } private final HwRemoteBinder.DeathRecipient mIWifiDeathRecipient = cookie -> { Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie); synchronized (mLock) { // prevents race condition with surrounding method mWifi = null; teardownInternal(); // don't restart: wait for registration notification } }; /** * 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; } WifiStatus status = mWifi.registerEventCallback(mWifiEventCallback); if (status.code != WifiStatusCode.SUCCESS) { Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status)); mWifi = null; return; } // Stopping wifi just in case. This would also trigger the status callback. stopWifi(); } 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 { MutableBoolean statusOk = new MutableBoolean(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; } 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 = status.code == WifiStatusCode.SUCCESS; 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.d(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; } } } /** * 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 (mWifi == null) { Log.e(TAG, "getAllChipInfo: called but mWifi is null!?"); return null; } try { MutableBoolean statusOk = new MutableBoolean(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<>(); chipResp.value.getAvailableModes( (WifiStatus status, ArrayList modes) -> { statusOk.value = status.code == WifiStatusCode.SUCCESS; if (statusOk.value) { availableModesResp.value = modes; } else { Log.e(TAG, "getAvailableModes failed: " + statusString(status)); } }); if (!statusOk.value) { return null; } MutableBoolean currentModeValidResp = new MutableBoolean(false); MutableInt currentModeResp = new MutableInt(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> ifaceNamesResp = new Mutable<>(); MutableInt ifaceIndex = new MutableInt(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; 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; apIfaces[ifaceIndex.value++] = ifaceInfo; } else { Log.e(TAG, "getApIface failed: " + statusString(status)); } }); if (!statusOk.value) { return null; } } 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; 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; 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.ifaces[IfaceType.STA] = staIfaces; chipInfo.ifaces[IfaceType.AP] = apIfaces; chipInfo.ifaces[IfaceType.P2P] = p2pIfaces; chipInfo.ifaces[IfaceType.NAN] = nanIfaces; } return chipsInfo; } catch (RemoteException e) { Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e); } } return null; } /** * 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. */ private boolean validateInterfaceCache(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 interface WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.type]; if (ifaceInfoList == null) { Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry); return false; } boolean matchFound = false; for (WifiIfaceInfo ifaceInfo: ifaceInfoList) { if (ifaceInfo.name.equals(entry.name)) { 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) { Log.w(TAG, "isWifiStarted called but mWifi is 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"); 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"); } return true; } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) { // Should retry. Hal might still be stopping. 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. 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"); synchronized (mLock) { 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 { if (VDBG) Log.d(TAG, "IWifiEventCallback.onStart"); // NOP: only happens in reaction to my calls - will handle directly } @Override public void onStop() throws RemoteException { 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 { Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status)); teardownInternal(); // No need to do anything else: listeners may (will) re-start Wi-Fi } } 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(); } } Set getSupportedIfaceTypesInternal(IWifiChip chip) { Set results = new HashSet<>(); WifiChipInfo[] chipInfos = getAllChipInfo(); if (chipInfos == null) { Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found"); return results; } MutableInt chipIdIfProvided = new MutableInt(0); // NOT using 0 as a magic value if (chip != null) { MutableBoolean statusOk = new MutableBoolean(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; } for (IWifiChip.ChipMode cm: wci.availableModes) { for (IWifiChip.ChipIfaceCombination cic: cm.availableCombinations) { for (IWifiChip.ChipIfaceCombinationLimit cicl: cic.limits) { for (int type: cicl.types) { results.add(type); } } } } } return results; } private IWifiIface createIface(int ifaceType, boolean lowPriority, InterfaceDestroyedListener destroyedListener, Handler handler) { if (mDbg) { Log.d(TAG, "createIface: ifaceType=" + ifaceType + ", lowPriority=" + lowPriority); } synchronized (mLock) { WifiChipInfo[] chipInfos = getAllChipInfo(); if (chipInfos == null) { Log.e(TAG, "createIface: no chip info found"); stopWifi(); // major error: shutting down return null; } if (!validateInterfaceCache(chipInfos)) { Log.e(TAG, "createIface: local cache is invalid!"); stopWifi(); // major error: shutting down return null; } IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, lowPriority, destroyedListener, handler); if (iface != null) { // means that some configuration has changed if (!dispatchAvailableForRequestListeners()) { return null; // catastrophic failure - shut down } } return iface; } } private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType, boolean lowPriority, InterfaceDestroyedListener destroyedListener, Handler handler) { if (VDBG) { Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos) + ", ifaceType=" + ifaceType + ", lowPriority=" + lowPriority); } synchronized (mLock) { IfaceCreationData bestIfaceCreationProposal = null; for (WifiChipInfo chipInfo: chipInfos) { for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) { for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode .availableCombinations) { int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo); if (VDBG) { Log.d(TAG, chipIfaceCombo + " expands to " + Arrays.deepToString(expandedIfaceCombos)); } for (int[] expandedIfaceCombo: expandedIfaceCombos) { IfaceCreationData currentProposal = canIfaceComboSupportRequest( chipInfo, chipMode, expandedIfaceCombo, ifaceType, lowPriority); if (compareIfaceCreationData(currentProposal, bestIfaceCreationProposal)) { if (VDBG) Log.d(TAG, "new proposal accepted"); bestIfaceCreationProposal = currentProposal; } } } } } if (bestIfaceCreationProposal != null) { IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal, ifaceType); if (iface != null) { InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry(); cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip; cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId; cacheEntry.name = getName(iface); cacheEntry.type = ifaceType; if (destroyedListener != null) { cacheEntry.destroyedListeners.add( new InterfaceDestroyedListenerProxy( cacheEntry.name, destroyedListener, handler)); } cacheEntry.creationTime = mClock.getUptimeSinceBootMillis(); cacheEntry.isLowPriority = lowPriority; if (mDbg) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry); mInterfaceInfoCache.put( Pair.create(cacheEntry.name, cacheEntry.type), cacheEntry); return iface; } } } return null; } // similar to createIfaceIfPossible - but simpler code: not looking for best option just // for any option (so terminates on first one). private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos, int ifaceType) { if (VDBG) { Log.d(TAG, "isItPossibleToCreateIface: chipInfos=" + Arrays.deepToString(chipInfos) + ", ifaceType=" + ifaceType); } for (WifiChipInfo chipInfo: chipInfos) { for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) { for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode .availableCombinations) { int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo); if (VDBG) { Log.d(TAG, chipIfaceCombo + " expands to " + Arrays.deepToString(expandedIfaceCombos)); } for (int[] expandedIfaceCombo: expandedIfaceCombos) { if (canIfaceComboSupportRequest(chipInfo, chipMode, expandedIfaceCombo, ifaceType, false) != null) { return true; } } } } } return false; } /** * Expands (or provides an alternative representation) of the ChipIfaceCombination as all * possible combinations of interface. * * Returns [# of combinations][4 (IfaceType)] * * 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[][] expandIfaceCombos(IWifiChip.ChipIfaceCombination chipIfaceCombo) { int numOfCombos = 1; for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) { for (int i = 0; i < limit.maxIfaces; ++i) { numOfCombos *= limit.types.size(); } } int[][] expandedIfaceCombos = new int[numOfCombos][IFACE_TYPES_BY_PRIORITY.length]; int span = numOfCombos; // span of an individual type (or sub-tree size) for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) { for (int i = 0; i < limit.maxIfaces; ++i) { span /= limit.types.size(); for (int k = 0; k < numOfCombos; ++k) { expandedIfaceCombos[k][limit.types.get((k / span) % limit.types.size())]++; } } } return expandedIfaceCombos; } private class IfaceCreationData { public WifiChipInfo chipInfo; public int chipModeId; public List interfacesToBeRemovedFirst; @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("{chipInfo=").append(chipInfo).append(", chipModeId=").append(chipModeId) .append(", interfacesToBeRemovedFirst=").append(interfacesToBeRemovedFirst) .append(")"); return sb.toString(); } } /** * Checks whether the input chip-iface-combo can support the requested interface type: if not * then returns null, if yes then returns information containing the list of interfaces which * would have to be removed first before the requested interface 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 * - Priority information: i.e. are we 'allowed' to remove interfaces in order to create the * requested interface */ private IfaceCreationData canIfaceComboSupportRequest(WifiChipInfo chipInfo, IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType, boolean lowPriority) { if (VDBG) { Log.d(TAG, "canIfaceComboSupportRequest: chipInfo=" + chipInfo + ", chipMode=" + chipMode + ", chipIfaceCombo=" + chipIfaceCombo + ", ifaceType=" + ifaceType + ", lowPriority=" + lowPriority); } // short-circuit: does the chipIfaceCombo even support the requested type? if (chipIfaceCombo[ifaceType] == 0) { if (VDBG) Log.d(TAG, "Requested type not supported by combo"); return null; } boolean isChipModeChangeProposed = chipInfo.currentModeIdValid && chipInfo.currentModeId != chipMode.id; // 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 type: IFACE_TYPES_BY_PRIORITY) { if (chipInfo.ifaces[type].length != 0) { if (lowPriority) { if (VDBG) { Log.d(TAG, "Couldn't delete existing type " + type + " interfaces for a low priority request"); } return null; } if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType, chipInfo.ifaces, chipInfo.ifaces[type].length)) { if (VDBG) { Log.d(TAG, "Couldn't delete existing type " + type + " 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 = chipMode.id; return ifaceCreationData; } // possibly supported List interfacesToBeRemovedFirst = new ArrayList<>(); for (int type: IFACE_TYPES_BY_PRIORITY) { int tooManyInterfaces = chipInfo.ifaces[type].length - chipIfaceCombo[type]; // need to count the requested interface as well if (type == ifaceType) { tooManyInterfaces += 1; } if (tooManyInterfaces > 0) { // may need to delete some if (lowPriority) { if (VDBG) { Log.d(TAG, "Couldn't delete existing type " + type + " interfaces for a low priority request"); } return null; } if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType, chipInfo.ifaces, tooManyInterfaces)) { if (VDBG) { Log.d(TAG, "Would need to delete some higher priority interfaces"); } return null; } // delete the most recently created interfaces or LOW priority interfaces interfacesToBeRemovedFirst = selectInterfacesToDelete(tooManyInterfaces, chipInfo.ifaces[type]); } } IfaceCreationData ifaceCreationData = new IfaceCreationData(); ifaceCreationData.chipInfo = chipInfo; ifaceCreationData.chipModeId = chipMode.id; ifaceCreationData.interfacesToBeRemovedFirst = interfacesToBeRemovedFirst; 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 */ 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; } for (int type: IFACE_TYPES_BY_PRIORITY) { // # of interfaces to be deleted: the list or all interfaces of the type if mode change int numIfacesToDelete1 = 0; if (val1.chipInfo.currentModeIdValid && val1.chipInfo.currentModeId != val1.chipModeId) { numIfacesToDelete1 = val1.chipInfo.ifaces[type].length; } else { numIfacesToDelete1 = val1.interfacesToBeRemovedFirst.size(); } int numIfacesToDelete2 = 0; if (val2.chipInfo.currentModeIdValid && val2.chipInfo.currentModeId != val2.chipModeId) { numIfacesToDelete2 = val2.chipInfo.ifaces[type].length; } else { numIfacesToDelete2 = val2.interfacesToBeRemovedFirst.size(); } if (numIfacesToDelete1 < numIfacesToDelete2) { if (VDBG) { Log.d(TAG, "decision based on type=" + type + ": " + numIfacesToDelete1 + " < " + numIfacesToDelete2); } return true; } } // arbitrary - flip a coin if (VDBG) Log.d(TAG, "proposals identical - flip a coin"); 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 * 2. No interface will be destroyed if one of the requested interfaces already exists * 3. If there are >1 interface of an existing type, then it is ok to destroy that type * interface * * Type-specific rules (but note that the general rules are appied first): * 4. Request for AP or STA will destroy any other interface * 5. Request for P2P will destroy NAN-only (but will destroy a second STA per #3) * 6. Request for NAN will not destroy any interface (but will destroy a second STA per #3) * * Note: the 'numNecessaryInterfaces' is used to specify how many interfaces would be needed to * be deleted. This is used to determine whether there are that many low priority interfaces * of the requested type to delete. */ private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType, int requestedIfaceType, WifiIfaceInfo[][] currentIfaces, int numNecessaryInterfaces) { // rule 0: check for any low priority interfaces int numAvailableLowPriorityInterfaces = 0; for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) { if (entry.type == existingIfaceType && entry.isLowPriority) { numAvailableLowPriorityInterfaces++; } } if (numAvailableLowPriorityInterfaces >= numNecessaryInterfaces) { return true; } // rule 1 if (existingIfaceType == requestedIfaceType) { return false; } // rule 2 if (currentIfaces[requestedIfaceType].length != 0) { return false; } // rule 3 if (currentIfaces[existingIfaceType].length > 1) { return true; } // rule 6 if (requestedIfaceType == IfaceType.NAN) { return false; } // rule 5 if (requestedIfaceType == IfaceType.P2P) { return existingIfaceType == IfaceType.NAN; } // rule 4, the requestIfaceType is either AP or STA return true; } /** * Selects the interfaces to delete. * * Rule: select low priority interfaces and then other interfaces in order of creation time. * * @param excessInterfaces Number of interfaces which need to be selected. * @param interfaces Array of interfaces. */ private List selectInterfacesToDelete(int excessInterfaces, WifiIfaceInfo[] interfaces) { if (VDBG) { Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces + ", interfaces=" + Arrays.toString(interfaces)); } boolean lookupError = false; LongSparseArray orderedListLowPriority = new LongSparseArray<>(); LongSparseArray orderedList = new LongSparseArray<>(); for (WifiIfaceInfo info : interfaces) { InterfaceCacheEntry 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; } if (cacheEntry.isLowPriority) { orderedListLowPriority.append(cacheEntry.creationTime, info); } else { orderedList.append(cacheEntry.creationTime, info); } } if (lookupError) { Log.e(TAG, "selectInterfacesToDelete: falling back to arbitrary selection"); return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces)); } else { List result = new ArrayList<>(excessInterfaces); for (int i = 0; i < excessInterfaces; ++i) { int lowPriorityNextIndex = orderedListLowPriority.size() - i - 1; if (lowPriorityNextIndex >= 0) { result.add(orderedListLowPriority.valueAt(lowPriorityNextIndex)); } else { result.add(orderedList.valueAt( orderedList.size() - i + orderedListLowPriority.size() - 1)); } } return result; } } /** * 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, int ifaceType) { if (mDbg) { Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData + ", ifaceType=" + ifaceType); } 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); // 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); // ignore return value } } // create new interface Mutable statusResp = new Mutable<>(); Mutable ifaceResp = new Mutable<>(); switch (ifaceType) { case IfaceType.STA: ifaceCreationData.chipInfo.chip.createStaIface( (WifiStatus status, IWifiStaIface iface) -> { statusResp.value = status; ifaceResp.value = iface; }); break; case IfaceType.AP: ifaceCreationData.chipInfo.chip.createApIface( (WifiStatus status, IWifiApIface iface) -> { statusResp.value = status; ifaceResp.value = iface; }); break; case IfaceType.P2P: ifaceCreationData.chipInfo.chip.createP2pIface( (WifiStatus status, IWifiP2pIface iface) -> { statusResp.value = status; ifaceResp.value = iface; }); break; case IfaceType.NAN: ifaceCreationData.chipInfo.chip.createNanIface( (WifiStatus status, IWifiNanIface iface) -> { statusResp.value = status; ifaceResp.value = iface; }); break; } if (statusResp.value.code != WifiStatusCode.SUCCESS) { Log.e(TAG, "executeChipReconfiguration: failed to create interface ifaceType=" + ifaceType + ": " + statusString(statusResp.value)); return null; } return ifaceResp.value; } catch (RemoteException e) { Log.e(TAG, "executeChipReconfiguration exception: " + e); return null; } } } private boolean removeIfaceInternal(IWifiIface iface) { 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; } 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); if (status != null && status.code == WifiStatusCode.SUCCESS) { return true; } else { Log.e(TAG, "IWifiChip.removeXxxIface failed: " + statusString(status)); return false; } } } // dispatch all available for request listeners of the specified type AND clean-out the list: // listeners are called once at most! private boolean dispatchAvailableForRequestListeners() { if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListeners"); synchronized (mLock) { WifiChipInfo[] chipInfos = getAllChipInfo(); if (chipInfos == null) { Log.e(TAG, "dispatchAvailableForRequestListeners: no chip info found"); stopWifi(); // major error: shutting down return false; } if (VDBG) { Log.d(TAG, "dispatchAvailableForRequestListeners: chipInfos=" + Arrays.deepToString(chipInfos)); } for (int ifaceType : IFACE_TYPES_BY_PRIORITY) { dispatchAvailableForRequestListenersForType(ifaceType, chipInfos); } } return true; } private void dispatchAvailableForRequestListenersForType(int ifaceType, WifiChipInfo[] chipInfos) { if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListenersForType: ifaceType=" + ifaceType); synchronized (mLock) { Map listeners = mInterfaceAvailableForRequestListeners.get(ifaceType); if (listeners.size() == 0) { return; } boolean isAvailable = isItPossibleToCreateIface(chipInfos, ifaceType); if (VDBG) { Log.d(TAG, "Interface available for: ifaceType=" + ifaceType + " = " + isAvailable); } for (Map.Entry listenerEntry : listeners.entrySet()) { if (listenerEntry.getValue() == null || listenerEntry.getValue() != isAvailable) { if (VDBG) { Log.d(TAG, "Interface available listener dispatched: ifaceType=" + ifaceType + ", listener=" + listenerEntry.getKey()); } listenerEntry.getKey().triggerWithArg(isAvailable); } listenerEntry.setValue(isAvailable); } } } // dispatch all destroyed listeners registered for the specified interface AND remove the // cache entry private void dispatchDestroyedListeners(String name, int type) { if (VDBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name); 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; } for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) { listener.trigger(); } entry.destroyedListeners.clear(); // for insurance (though cache entry is removed) mInterfaceInfoCache.remove(Pair.create(name, type)); } } // dispatch all destroyed listeners registered to all interfaces private void dispatchAllDestroyedListeners() { if (VDBG) Log.d(TAG, "dispatchAllDestroyedListeners"); synchronized (mLock) { Iterator, InterfaceCacheEntry>> it = mInterfaceInfoCache.entrySet().iterator(); while (it.hasNext()) { InterfaceCacheEntry entry = it.next().getValue(); for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) { listener.trigger(); } entry.destroyedListeners.clear(); // for insurance (though cache entry is removed) it.remove(); } } } 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(); } void trigger() { if (mHandler != null) { mHandler.post(() -> { action(); }); } else { action(); } } void triggerWithArg(boolean arg) { if (mHandler != null) { mHandler.post(() -> { actionWithArg(arg); }); } else { actionWithArg(arg); } } protected void action() {} protected void actionWithArg(boolean arg) {} ListenerProxy(LISTENER listener, Handler handler, String tag) { mListener = listener; mHandler = handler; } } private class InterfaceDestroyedListenerProxy extends ListenerProxy { private final String mIfaceName; InterfaceDestroyedListenerProxy(@NonNull String ifaceName, InterfaceDestroyedListener destroyedListener, Handler handler) { super(destroyedListener, handler, "InterfaceDestroyedListenerProxy"); mIfaceName = ifaceName; } @Override protected void action() { mListener.onDestroyed(mIfaceName); } } private class InterfaceAvailableForRequestListenerProxy extends ListenerProxy { InterfaceAvailableForRequestListenerProxy( InterfaceAvailableForRequestListener destroyedListener, Handler handler) { super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy"); } @Override protected void actionWithArg(boolean isAvailable) { mListener.onAvailabilityChanged(isAvailable); } } // 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) { MutableInt typeResp = new MutableInt(-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; } /** * Dump the internal state of the class. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HalDeviceManager:"); pw.println(" mServiceManager: " + mServiceManager); pw.println(" mWifi: " + mWifi); pw.println(" mManagerStatusListeners: " + mManagerStatusListeners); pw.println(" mInterfaceAvailableForRequestListeners: " + mInterfaceAvailableForRequestListeners); pw.println(" mInterfaceInfoCache: " + mInterfaceInfoCache); } }