blob: 3c61217d7f0df574326348dc6ea0d656bc3e5af4 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import 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<Integer> getSupportedIfaceTypes() {
return getSupportedIfaceTypesInternal(null);
}
/**
* Return the set of supported interface types for the specified Wi-Fi chip.
*
* @return A set of IfaceTypes constants (possibly empty, e.g. on error).
*/
public Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
return getSupportedIfaceTypesInternal(chip);
}
// interface-specific behavior
/**
* Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
* needed and permitted by priority.
*
* @param 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 "<null>";
}
Mutable<String> nameResp = new Mutable<>();
try {
iface.getName((WifiStatus status, String name) -> {
if (status.code == WifiStatusCode.SUCCESS) {
nameResp.value = name;
} else {
Log.e(TAG, "Error on getName: " + statusString(status));
}
});
} catch (RemoteException e) {
Log.e(TAG, "Exception on getName: " + e);
}
return nameResp.value;
}
/**
* Called when 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<IWifiRttController> 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<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
private final SparseArray<Map<InterfaceAvailableForRequestListenerProxy, Boolean>>
mInterfaceAvailableForRequestListeners = new SparseArray<>();
private final SparseArray<IWifiChipEventCallback.Stub> 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<Pair<String, Integer>, InterfaceCacheEntry> mInterfaceInfoCache =
new HashMap<>();
private class InterfaceCacheEntry {
public IWifiChip chip;
public int chipId;
public String name;
public int type;
public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
public long creationTime;
public 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<IWifiChip.ChipMode> 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<ArrayList<Integer>> chipIdsResp = new Mutable<>();
// get all chip IDs
mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
chipIdsResp.value = chipIds;
} else {
Log.e(TAG, "getChipIds failed: " + statusString(status));
}
});
if (!statusOk.value) {
return;
}
Log.d(TAG, "getChipIds=" + chipIdsResp.value);
if (chipIdsResp.value.size() == 0) {
Log.e(TAG, "Should have at least 1 chip!");
return;
}
// register a callback for each chip
Mutable<IWifiChip> chipResp = new Mutable<>();
for (Integer chipId: chipIdsResp.value) {
mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
statusOk.value = 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<Byte> data) throws RemoteException {
Log.d(TAG, "onDebugRingBufferDataAvailable");
}
@Override
public void onDebugErrorAlert(int errorCode,
ArrayList<Byte> debugData)
throws RemoteException {
Log.d(TAG, "onDebugErrorAlert");
}
};
mDebugCallbacks.put(chipId, callback); // store to prevent GC: needed by HIDL
WifiStatus status = chipResp.value.registerEventCallback(callback);
if (status.code != WifiStatusCode.SUCCESS) {
Log.e(TAG, "registerEventCallback failed: " + statusString(status));
continue; // still try next one?
}
}
} catch (RemoteException e) {
Log.e(TAG, "initIWifiChipDebugListeners: exception: " + e);
return;
}
}
}
/**
* 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<ArrayList<Integer>> chipIdsResp = new Mutable<>();
// get all chip IDs
mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
chipIdsResp.value = chipIds;
} else {
Log.e(TAG, "getChipIds failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
if (VDBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
if (chipIdsResp.value.size() == 0) {
Log.e(TAG, "Should have at least 1 chip!");
return null;
}
int chipInfoIndex = 0;
WifiChipInfo[] chipsInfo = new WifiChipInfo[chipIdsResp.value.size()];
Mutable<IWifiChip> chipResp = new Mutable<>();
for (Integer chipId: chipIdsResp.value) {
mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
chipResp.value = chip;
} else {
Log.e(TAG, "getChip failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
Mutable<ArrayList<IWifiChip.ChipMode>> availableModesResp = new Mutable<>();
chipResp.value.getAvailableModes(
(WifiStatus status, ArrayList<IWifiChip.ChipMode> 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<ArrayList<String>> ifaceNamesResp = new Mutable<>();
MutableInt ifaceIndex = new MutableInt(0);
chipResp.value.getStaIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getStaIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] staIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getStaIface(ifaceName,
(WifiStatus status, IWifiStaIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
staIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getStaIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
ifaceIndex.value = 0;
chipResp.value.getApIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getApIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] apIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getApIface(ifaceName,
(WifiStatus status, IWifiApIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
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<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getP2pIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] p2pIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getP2pIface(ifaceName,
(WifiStatus status, IWifiP2pIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
p2pIfaces[ifaceIndex.value++] = ifaceInfo;
} else {
Log.e(TAG, "getP2pIface failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
}
ifaceIndex.value = 0;
chipResp.value.getNanIfaceNames(
(WifiStatus status, ArrayList<String> ifnames) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
ifaceNamesResp.value = ifnames;
} else {
Log.e(TAG, "getNanIfaceNames failed: " + statusString(status));
}
});
if (!statusOk.value) {
return null;
}
WifiIfaceInfo[] nanIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
for (String ifaceName: ifaceNamesResp.value) {
chipResp.value.getNanIface(ifaceName,
(WifiStatus status, IWifiNanIface iface) -> {
statusOk.value = status.code == WifiStatusCode.SUCCESS;
if (statusOk.value) {
WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
ifaceInfo.name = ifaceName;
ifaceInfo.iface = iface;
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<ManagerStatusListener> {
ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
super(statusListener, handler, "ManagerStatusListenerProxy");
}
@Override
protected void action() {
mListener.onStatusChanged();
}
}
Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
Set<Integer> 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<WifiIfaceInfo> 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<WifiIfaceInfo> 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<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
WifiIfaceInfo[] interfaces) {
if (VDBG) {
Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
+ ", interfaces=" + Arrays.toString(interfaces));
}
boolean lookupError = false;
LongSparseArray<WifiIfaceInfo> orderedListLowPriority = new LongSparseArray<>();
LongSparseArray<WifiIfaceInfo> 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<WifiIfaceInfo> 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<WifiStatus> statusResp = new Mutable<>();
Mutable<IWifiIface> 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<InterfaceAvailableForRequestListenerProxy, Boolean> 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<InterfaceAvailableForRequestListenerProxy, Boolean> 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<Map.Entry<Pair<String, Integer>, 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<LISTENER> {
protected LISTENER mListener;
private Handler mHandler;
// override equals & hash to make sure that the container HashSet is unique with respect to
// the contained listener
@Override
public boolean equals(Object obj) {
return mListener == ((ListenerProxy<LISTENER>) obj).mListener;
}
@Override
public int hashCode() {
return mListener.hashCode();
}
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<InterfaceDestroyedListener> {
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<InterfaceAvailableForRequestListener> {
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);
}
}