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