/* * Copyright (C) 2016 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 android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED; import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_LOCAL_ONLY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SCAN_ONLY; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED; import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_TRANSIENT; import static com.android.server.wifi.ActiveModeManager.ROLE_SOFTAP_TETHERED; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_NATIVE_SUPPORTED_STA_BANDS; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.location.LocationManager; import android.net.Network; import android.net.wifi.ISubsystemRestartCallback; import android.net.wifi.IWifiConnectedNetworkScorer; import android.net.wifi.IWifiNetworkStateChangedListener; import android.net.wifi.SoftApCapability; import android.net.wifi.SoftApConfiguration; import android.net.wifi.SoftApState; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.DeviceMobilityState; import android.net.wifi.WifiScanner; import android.os.BatteryStatsManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; import android.util.LocalLog; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IState; import com.android.internal.util.Preconditions; import com.android.internal.util.Protocol; import com.android.internal.util.StateMachine; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.ActiveModeManager.ClientConnectivityRole; import com.android.server.wifi.ActiveModeManager.ClientInternetConnectivityRole; import com.android.server.wifi.ActiveModeManager.ClientRole; import com.android.server.wifi.ActiveModeManager.SoftApRole; import com.android.server.wifi.util.ApConfigUtil; import com.android.server.wifi.util.LastCallerInfoManager; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.WifiPermissionsUtil; import com.android.wifi.resources.R; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; /** * This class provides the implementation for different WiFi operating modes. */ public class ActiveModeWarden { private static final String TAG = "WifiActiveModeWarden"; private static final String STATE_MACHINE_EXITED_STATE_NAME = "STATE_MACHINE_EXITED"; public static final WorkSource INTERNAL_REQUESTOR_WS = new WorkSource(Process.WIFI_UID); // Holder for active mode managers private final Set mClientModeManagers = new ArraySet<>(); private final Set mSoftApManagers = new ArraySet<>(); private final Set mCallbacks = new ArraySet<>(); private final Set mPrimaryChangedCallbacks = new ArraySet<>(); // DefaultModeManager used to service API calls when there are no active client mode managers. private final DefaultClientModeManager mDefaultClientModeManager; private final WifiInjector mWifiInjector; private final Looper mLooper; private final Handler mHandler; private final Context mContext; private final WifiDiagnostics mWifiDiagnostics; private final WifiSettingsStore mSettingsStore; private final FrameworkFacade mFacade; private final WifiPermissionsUtil mWifiPermissionsUtil; private final BatteryStatsManager mBatteryStatsManager; private final ScanRequestProxy mScanRequestProxy; private final WifiNative mWifiNative; private final WifiController mWifiController; private final Graveyard mGraveyard; private final WifiMetrics mWifiMetrics; private final ExternalScoreUpdateObserverProxy mExternalScoreUpdateObserverProxy; private final DppManager mDppManager; private final UserManager mUserManager; private final LastCallerInfoManager mLastCallerInfoManager; private final WifiGlobals mWifiGlobals; private WifiServiceImpl.SoftApCallbackInternal mSoftApCallback; private WifiServiceImpl.SoftApCallbackInternal mLohsCallback; private final RemoteCallbackList mRestartCallbacks = new RemoteCallbackList<>(); private final RemoteCallbackList mWifiNetworkStateChangedListeners = new RemoteCallbackList<>(); private boolean mIsMultiplePrimaryBugreportTaken = false; private boolean mIsShuttingdown = false; private boolean mVerboseLoggingEnabled = false; private boolean mAllowRootToGetLocalOnlyCmm = true; private @DeviceMobilityState int mDeviceMobilityState = WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN; /** Cache to store the external scorer for primary and secondary (MBB) client mode manager. */ @Nullable private Pair mClientModeManagerScorer; private int mScorerUid; @Nullable private ConcreteClientModeManager mLastPrimaryClientModeManager = null; @Nullable private WorkSource mLastPrimaryClientModeManagerRequestorWs = null; @Nullable private WorkSource mLastScanOnlyClientModeManagerRequestorWs = null; private AtomicLong mSupportedFeatureSet = new AtomicLong(0); private AtomicInteger mBandsSupported = new AtomicInteger(0); // Mutex lock between service Api binder thread and Wifi main thread private final Object mServiceApiLock = new Object(); @GuardedBy("mServiceApiLock") private Network mCurrentNetwork; @GuardedBy("mServiceApiLock") private WifiInfo mCurrentConnectionInfo = new WifiInfo(); @GuardedBy("mServiceApiLock") private final ArraySet mRequestWs = new ArraySet<>(); /** * One of {@link WifiManager#WIFI_STATE_DISABLED}, * {@link WifiManager#WIFI_STATE_DISABLING}, * {@link WifiManager#WIFI_STATE_ENABLED}, * {@link WifiManager#WIFI_STATE_ENABLING}, * {@link WifiManager#WIFI_STATE_UNKNOWN} */ private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED); private ContentObserver mSatelliteModeContentObserver; /** * Method that allows the active ClientModeManager to set the wifi state that is * retrieved by API calls. Only primary ClientModeManager should call this method when state * changes * @param newState new state to set, invalid states are ignored. */ public void setWifiStateForApiCalls(int newState) { switch (newState) { case WIFI_STATE_DISABLING: case WIFI_STATE_DISABLED: case WIFI_STATE_ENABLING: case WIFI_STATE_ENABLED: case WIFI_STATE_UNKNOWN: if (mVerboseLoggingEnabled) { Log.d(TAG, "setting wifi state to: " + newState); } mWifiState.set(newState); break; default: Log.d(TAG, "attempted to set an invalid state: " + newState); break; } } /** * Get the request WorkSource for secondary CMM * * @return the WorkSources of the current secondary CMMs */ public Set getSecondaryRequestWs() { synchronized (mServiceApiLock) { return new ArraySet<>(mRequestWs); } } private String getWifiStateName() { switch (mWifiState.get()) { case WIFI_STATE_DISABLING: return "disabling"; case WIFI_STATE_DISABLED: return "disabled"; case WIFI_STATE_ENABLING: return "enabling"; case WIFI_STATE_ENABLED: return "enabled"; case WIFI_STATE_UNKNOWN: return "unknown state"; default: return "[invalid state]"; } } /** * Method used by WifiServiceImpl to get the current state of Wifi for API calls. * The Wifi state is a global state of the device, which equals to the state of the primary STA. * This method must be thread safe. */ public int getWifiState() { return mWifiState.get(); } /** * Notify changes in PowerManager#isDeviceIdleMode */ public void onIdleModeChanged(boolean isIdle) { // only client mode managers need to get notified for now to consider enabling/disabling // firmware roaming for (ClientModeManager cmm : mClientModeManagers) { cmm.onIdleModeChanged(isIdle); } } /** * Called from WifiServiceImpl to register a callback for notifications from SoftApManager */ public void registerSoftApCallback(@NonNull WifiServiceImpl.SoftApCallbackInternal callback) { mSoftApCallback = callback; } /** * Called from WifiServiceImpl to register a callback for notifications from SoftApManager * for local-only hotspot. */ public void registerLohsCallback(@NonNull WifiServiceImpl.SoftApCallbackInternal callback) { mLohsCallback = callback; } /** * Callbacks for indicating any mode manager changes to the rest of the system. */ public interface ModeChangeCallback { /** * Invoked when new mode manager is added. * * @param activeModeManager Instance of {@link ActiveModeManager}. */ void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager); /** * Invoked when a mode manager is removed. * * @param activeModeManager Instance of {@link ActiveModeManager}. */ void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager); /** * Invoked when an existing mode manager's role is changed. * * @param activeModeManager Instance of {@link ActiveModeManager}. */ void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager); } /** Called when the primary ClientModeManager changes. */ public interface PrimaryClientModeManagerChangedCallback { /** * Note: The current implementation for changing primary CMM is not atomic (due to setRole() * needing to go through StateMachine, which is async). Thus, when the primary CMM changes, * the sequence of calls looks like this: * 1. onChange(prevPrimaryCmm, null) * 2. onChange(null, newPrimaryCmm) * Nevertheless, at run time, these two calls should occur in rapid succession. * * @param prevPrimaryClientModeManager the previous primary ClientModeManager, or null if * there was no previous primary (e.g. Wifi was off). * @param newPrimaryClientModeManager the new primary ClientModeManager, or null if there is * no longer a primary (e.g. Wifi was turned off). */ void onChange( @Nullable ConcreteClientModeManager prevPrimaryClientModeManager, @Nullable ConcreteClientModeManager newPrimaryClientModeManager); } /** * Keep stopped {@link ActiveModeManager} instances so that they can be dumped to aid debugging. * * TODO(b/160283853): Find a smarter way to evict old ActiveModeManagers */ private static class Graveyard { private static final int INSTANCES_TO_KEEP = 3; private final ArrayDeque mClientModeManagers = new ArrayDeque<>(); private final ArrayDeque mSoftApManagers = new ArrayDeque<>(); /** * Add this stopped {@link ConcreteClientModeManager} to the graveyard, and evict the oldest * ClientModeManager if the graveyard is full. */ void inter(ConcreteClientModeManager clientModeManager) { if (mClientModeManagers.size() == INSTANCES_TO_KEEP) { mClientModeManagers.removeFirst(); } mClientModeManagers.addLast(clientModeManager); } /** * Add this stopped {@link SoftApManager} to the graveyard, and evict the oldest * SoftApManager if the graveyard is full. */ void inter(SoftApManager softApManager) { if (mSoftApManagers.size() == INSTANCES_TO_KEEP) { mSoftApManagers.removeFirst(); } mSoftApManagers.addLast(softApManager); } /** Dump the contents of the graveyard. */ void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Dump of ActiveModeWarden.Graveyard"); pw.println("Stopped ClientModeManagers: " + mClientModeManagers.size() + " total"); for (ConcreteClientModeManager clientModeManager : mClientModeManagers) { clientModeManager.dump(fd, pw, args); } pw.println("Stopped SoftApManagers: " + mSoftApManagers.size() + " total"); for (SoftApManager softApManager : mSoftApManagers) { softApManager.dump(fd, pw, args); } pw.println(); } } ActiveModeWarden(WifiInjector wifiInjector, Looper looper, WifiNative wifiNative, DefaultClientModeManager defaultClientModeManager, BatteryStatsManager batteryStatsManager, WifiDiagnostics wifiDiagnostics, Context context, WifiSettingsStore settingsStore, FrameworkFacade facade, WifiPermissionsUtil wifiPermissionsUtil, WifiMetrics wifiMetrics, ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy, DppManager dppManager, WifiGlobals wifiGlobals) { mWifiInjector = wifiInjector; mLooper = looper; mHandler = new Handler(looper); mContext = context; mWifiDiagnostics = wifiDiagnostics; mSettingsStore = settingsStore; mFacade = facade; mWifiPermissionsUtil = wifiPermissionsUtil; mDefaultClientModeManager = defaultClientModeManager; mBatteryStatsManager = batteryStatsManager; mScanRequestProxy = wifiInjector.getScanRequestProxy(); mWifiNative = wifiNative; mWifiMetrics = wifiMetrics; mWifiController = new WifiController(); mExternalScoreUpdateObserverProxy = externalScoreUpdateObserverProxy; mDppManager = dppManager; mGraveyard = new Graveyard(); mUserManager = mWifiInjector.getUserManager(); mLastCallerInfoManager = mWifiInjector.getLastCallerInfoManager(); mWifiGlobals = wifiGlobals; wifiNative.registerStatusListener(isReady -> { if (!isReady && !mIsShuttingdown) { Log.e(TAG, "One of the native daemons died. Triggering recovery"); mWifiInjector.getWifiConfigManager().writeDataToStorage(); wifiDiagnostics.triggerBugReportDataCapture( WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE); // immediately trigger SelfRecovery if we receive a notice about an // underlying daemon failure // Note: SelfRecovery has a circular dependency with ActiveModeWarden and is // instantiated after ActiveModeWarden, so use WifiInjector to get the instance // instead of directly passing in SelfRecovery in the constructor. mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); } }); registerPrimaryClientModeManagerChangedCallback( (prevPrimaryClientModeManager, newPrimaryClientModeManager) -> { // TODO (b/181363901): We can always propagate the external scorer to all // ClientModeImpl instances. WifiScoreReport already handles skipping external // scorer notification for local only & restricted STA + STA use-cases. For MBB // use-case, we may want the external scorer to be notified. if (prevPrimaryClientModeManager != null) { prevPrimaryClientModeManager.clearWifiConnectedNetworkScorer(); } if (newPrimaryClientModeManager != null && mClientModeManagerScorer != null) { newPrimaryClientModeManager.setWifiConnectedNetworkScorer( mClientModeManagerScorer.first, mClientModeManagerScorer.second, mScorerUid); } }); } private void invokeOnAddedCallbacks(@NonNull ActiveModeManager activeModeManager) { if (mVerboseLoggingEnabled) { Log.v(TAG, "ModeManager added " + activeModeManager); } for (ModeChangeCallback callback : mCallbacks) { callback.onActiveModeManagerAdded(activeModeManager); } } private void invokeOnRemovedCallbacks(@NonNull ActiveModeManager activeModeManager) { if (mVerboseLoggingEnabled) { Log.v(TAG, "ModeManager removed " + activeModeManager); } for (ModeChangeCallback callback : mCallbacks) { callback.onActiveModeManagerRemoved(activeModeManager); } } private void invokeOnRoleChangedCallbacks(@NonNull ActiveModeManager activeModeManager) { if (mVerboseLoggingEnabled) { Log.v(TAG, "ModeManager role changed " + activeModeManager); } for (ModeChangeCallback callback : mCallbacks) { callback.onActiveModeManagerRoleChanged(activeModeManager); } } private void invokeOnPrimaryClientModeManagerChangedCallbacks( @Nullable ConcreteClientModeManager prevPrimaryClientModeManager, @Nullable ConcreteClientModeManager newPrimaryClientModeManager) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Primary ClientModeManager changed from " + prevPrimaryClientModeManager + " to " + newPrimaryClientModeManager); } for (PrimaryClientModeManagerChangedCallback callback : mPrimaryChangedCallbacks) { callback.onChange(prevPrimaryClientModeManager, newPrimaryClientModeManager); } } /** * Used for testing with wifi shell command. If enabled the root will be able to request for a * secondary local-only CMM when commands like add-request is used. If disabled, add-request * will fallback to using the primary CMM. */ public void allowRootToGetLocalOnlyCmm(boolean enabled) { mAllowRootToGetLocalOnlyCmm = enabled; } /** * Enable verbose logging. */ public void enableVerboseLogging(boolean verbose) { mVerboseLoggingEnabled = verbose; for (ActiveModeManager modeManager : getActiveModeManagers()) { modeManager.enableVerboseLogging(verbose); } } /** * See {@link android.net.wifi.WifiManager#setWifiConnectedNetworkScorer(Executor, * WifiManager.WifiConnectedNetworkScorer)} */ public boolean setWifiConnectedNetworkScorer(IBinder binder, IWifiConnectedNetworkScorer scorer, int callerUid) { try { scorer.onSetScoreUpdateObserver(mExternalScoreUpdateObserverProxy); } catch (RemoteException e) { Log.e(TAG, "Unable to set score update observer " + scorer, e); return false; } mClientModeManagerScorer = Pair.create(binder, scorer); mScorerUid = callerUid; return getPrimaryClientModeManager().setWifiConnectedNetworkScorer(binder, scorer, callerUid); } /** * See {@link WifiManager#clearWifiConnectedNetworkScorer()} */ public void clearWifiConnectedNetworkScorer() { mClientModeManagerScorer = null; mScorerUid = Process.WIFI_UID; getPrimaryClientModeManager().clearWifiConnectedNetworkScorer(); } /** * Register for mode change callbacks. */ public void registerModeChangeCallback(@NonNull ModeChangeCallback callback) { if (callback == null) { Log.wtf(TAG, "Cannot register a null ModeChangeCallback"); return; } mCallbacks.add(callback); } /** * Unregister mode change callback. */ public void unregisterModeChangeCallback(@NonNull ModeChangeCallback callback) { if (callback == null) { Log.wtf(TAG, "Cannot unregister a null ModeChangeCallback"); return; } mCallbacks.remove(callback); } /** Register for primary ClientModeManager changed callbacks. */ public void registerPrimaryClientModeManagerChangedCallback( @NonNull PrimaryClientModeManagerChangedCallback callback) { if (callback == null) { Log.wtf(TAG, "Cannot register a null PrimaryClientModeManagerChangedCallback"); return; } mPrimaryChangedCallbacks.add(callback); // If there is already a primary CMM when registering, send a callback with the info. ConcreteClientModeManager cm = getPrimaryClientModeManagerNullable(); if (cm != null) callback.onChange(null, cm); } /** Unregister for primary ClientModeManager changed callbacks. */ public void unregisterPrimaryClientModeManagerChangedCallback( @NonNull PrimaryClientModeManagerChangedCallback callback) { if (callback == null) { Log.wtf(TAG, "Cannot unregister a null PrimaryClientModeManagerChangedCallback"); return; } mPrimaryChangedCallbacks.remove(callback); } /** * Notify that device is shutting down * Keep it simple and don't add collection access codes * to avoid concurrentModificationException when it is directly called from a different thread */ public void notifyShuttingDown() { mIsShuttingdown = true; } /** @return Returns whether device is shutting down */ public boolean isShuttingDown() { return mIsShuttingdown; } /** * @return Returns whether we can create more client mode managers or not. */ public boolean canRequestMoreClientModeManagersInRole(@NonNull WorkSource requestorWs, @NonNull ClientRole clientRole, boolean didUserApprove) { WorkSource ifCreatorWs = new WorkSource(requestorWs); if (didUserApprove) { // If user select to connect from the UI, promote the priority ifCreatorWs.add(mFacade.getSettingsWorkSource(mContext)); } if (!mWifiNative.isItPossibleToCreateStaIface(ifCreatorWs)) { return false; } if (clientRole == ROLE_CLIENT_LOCAL_ONLY) { if (!mContext.getResources().getBoolean( R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled)) { return false; } final int uid = requestorWs.getUid(0); final String packageName = requestorWs.getPackageName(0); // For peer to peer use-case, only allow secondary STA if the app is targeting S SDK // or is a system app to provide backward compatibility. return mWifiPermissionsUtil.isSystem(packageName, uid) || !mWifiPermissionsUtil.isTargetSdkLessThan( packageName, Build.VERSION_CODES.S, uid); } if (clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT) { return mContext.getResources().getBoolean( R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled); } if (clientRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) { return mContext.getResources().getBoolean( R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled) || mContext.getResources().getBoolean( R.bool.config_wifiMultiStaMultiInternetConcurrencyEnabled); } Log.e(TAG, "Unrecognized role=" + clientRole); return false; } /** * @return Returns whether we can create more SoftAp managers or not. */ public boolean canRequestMoreSoftApManagers(@NonNull WorkSource requestorWs) { return mWifiNative.isItPossibleToCreateApIface(requestorWs); } /** * @return Returns whether the device can support at least two concurrent client mode managers * and the local only use-case is enabled. */ public boolean isStaStaConcurrencySupportedForLocalOnlyConnections() { return mWifiNative.isStaStaConcurrencySupported() && mContext.getResources().getBoolean( R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled); } /** * @return Returns whether the device can support at least two concurrent client mode managers * and the mbb wifi switching is enabled. */ public boolean isStaStaConcurrencySupportedForMbb() { return mWifiNative.isStaStaConcurrencySupported() && mContext.getResources().getBoolean( R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled); } /** * @return Returns whether the device can support at least two concurrent client mode managers * and the restricted use-case is enabled. */ public boolean isStaStaConcurrencySupportedForRestrictedConnections() { return mWifiNative.isStaStaConcurrencySupported() && mContext.getResources().getBoolean( R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled); } /** * @return Returns whether the device can support at least two concurrent client mode managers * and the multi internet use-case is enabled. */ public boolean isStaStaConcurrencySupportedForMultiInternet() { return mWifiNative.isStaStaConcurrencySupported() && mContext.getResources().getBoolean( R.bool.config_wifiMultiStaMultiInternetConcurrencyEnabled); } /** Begin listening to broadcasts and start the internal state machine. */ public void start() { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Location mode has been toggled... trigger with the scan change // update to make sure we are in the correct mode scanAlwaysModeChanged(); } }, new IntentFilter(LocationManager.MODE_CHANGED_ACTION)); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean airplaneModeUpdated = mSettingsStore.updateAirplaneModeTracker(); boolean userRestrictionSet = SdkLevel.isAtLeastT() && mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_CHANGE_WIFI_STATE, UserHandle.getUserHandleForUid(Process.SYSTEM_UID)); if (!userRestrictionSet && airplaneModeUpdated) { mSettingsStore.handleAirplaneModeToggled(); airplaneModeToggled(); } } }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean emergencyMode = intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false); emergencyCallbackModeChanged(emergencyMode); } }, new IntentFilter(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)); boolean trackEmergencyCallState = mContext.getResources().getBoolean( R.bool.config_wifi_turn_off_during_emergency_call); if (trackEmergencyCallState) { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { boolean inCall = intent.getBooleanExtra( TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false); emergencyCallStateChanged(inCall); } }, new IntentFilter(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED)); } mWifiGlobals.setD2dStaConcurrencySupported( mWifiNative.isP2pStaConcurrencySupported() || mWifiNative.isNanStaConcurrencySupported()); // Initialize the supported feature set. setSupportedFeatureSet(mWifiNative.getSupportedFeatureSet(null), mWifiNative.isStaApConcurrencySupported(), mWifiNative.isStaStaConcurrencySupported()); mSatelliteModeContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { handleSatelliteModeChange(); } }; mFacade.registerContentObserver( mContext, Settings.Global.getUriFor(WifiSettingsStore.SETTINGS_SATELLITE_MODE_RADIOS), false, mSatelliteModeContentObserver); mFacade.registerContentObserver( mContext, Settings.Global.getUriFor(WifiSettingsStore.SETTINGS_SATELLITE_MODE_ENABLED), false, mSatelliteModeContentObserver); mWifiController.start(); } /** Disable Wifi for recovery purposes. */ public void recoveryDisableWifi() { mWifiController.sendMessage(WifiController.CMD_RECOVERY_DISABLE_WIFI); } /** * Restart Wifi for recovery purposes. * @param reason One of {@link SelfRecovery.RecoveryReason} */ public void recoveryRestartWifi(@SelfRecovery.RecoveryReason int reason, boolean requestBugReport) { mWifiController.sendMessage(WifiController.CMD_RECOVERY_RESTART_WIFI, reason, requestBugReport ? 1 : 0, SelfRecovery.getRecoveryReasonAsString(reason)); } /** * register a callback to monitor the progress of Wi-Fi subsystem operation (started/finished) * - started via {@link #recoveryRestartWifi(int, String, boolean)}. */ public boolean registerSubsystemRestartCallback(ISubsystemRestartCallback callback) { return mRestartCallbacks.register(callback); } /** * unregister a callback to monitor the progress of Wi-Fi subsystem operation (started/finished) * - started via {@link #recoveryRestartWifi(int, String, boolean)}. Callback is registered via * {@link #registerSubsystemRestartCallback(ISubsystemRestartCallback)}. */ public boolean unregisterSubsystemRestartCallback(ISubsystemRestartCallback callback) { return mRestartCallbacks.unregister(callback); } /** * Add a listener to get network state change updates. */ public boolean addWifiNetworkStateChangedListener(IWifiNetworkStateChangedListener listener) { return mWifiNetworkStateChangedListeners.register(listener); } /** * Remove a listener for getting network state change updates. */ public boolean removeWifiNetworkStateChangedListener( IWifiNetworkStateChangedListener listener) { return mWifiNetworkStateChangedListeners.unregister(listener); } /** * Report network state changes to registered listeners. */ public void onNetworkStateChanged(int cmmRole, int state) { int numCallbacks = mWifiNetworkStateChangedListeners.beginBroadcast(); if (mVerboseLoggingEnabled) { Log.i(TAG, "Sending onWifiNetworkStateChanged cmmRole=" + cmmRole + " state=" + state); } for (int i = 0; i < numCallbacks; i++) { try { mWifiNetworkStateChangedListeners.getBroadcastItem(i) .onWifiNetworkStateChanged(cmmRole, state); } catch (RemoteException e) { Log.e(TAG, "Failure calling onWifiNetworkStateChanged" + e); } } mWifiNetworkStateChangedListeners.finishBroadcast(); } /** Wifi has been toggled. */ public void wifiToggled(WorkSource requestorWs) { mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED, requestorWs); } /** Airplane Mode has been toggled. */ public void airplaneModeToggled() { mWifiController.sendMessage(WifiController.CMD_AIRPLANE_TOGGLED); } /** Starts SoftAp. */ public void startSoftAp(SoftApModeConfiguration softApConfig, WorkSource requestorWs) { mWifiController.sendMessage(WifiController.CMD_SET_AP, 1, 0, Pair.create(softApConfig, requestorWs)); } /** Stop SoftAp. */ public void stopSoftAp(int mode) { mWifiController.sendMessage(WifiController.CMD_SET_AP, 0, mode); } /** Update SoftAp Capability. */ public void updateSoftApCapability(SoftApCapability capability, int ipMode) { mWifiController.sendMessage(WifiController.CMD_UPDATE_AP_CAPABILITY, ipMode, 0, capability); } /** Update SoftAp Configuration. */ public void updateSoftApConfiguration(SoftApConfiguration config) { mWifiController.sendMessage(WifiController.CMD_UPDATE_AP_CONFIG, config); } /** Emergency Callback Mode has changed. */ public void emergencyCallbackModeChanged(boolean isInEmergencyCallbackMode) { mWifiController.sendMessage( WifiController.CMD_EMERGENCY_MODE_CHANGED, isInEmergencyCallbackMode ? 1 : 0); } /** Emergency Call state has changed. */ public void emergencyCallStateChanged(boolean isInEmergencyCall) { mWifiController.sendMessage( WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED, isInEmergencyCall ? 1 : 0); } /** Scan always mode has changed. */ public void scanAlwaysModeChanged() { mWifiController.sendMessage( WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED, // Scan only mode change is not considered a direct user interaction since user // is not explicitly turning on wifi scanning (side-effect of location toggle). // So, use the lowest priority internal requestor worksource to ensure that this // is treated with the lowest priority. INTERNAL_REQUESTOR_WS); } /** emergency scan progress indication. */ public void setEmergencyScanRequestInProgress(boolean inProgress) { mWifiController.sendMessage( WifiController.CMD_EMERGENCY_SCAN_STATE_CHANGED, inProgress ? 1 : 0, 0, // Emergency scans should have the highest priority, so use settings worksource. mFacade.getSettingsWorkSource(mContext)); } /** * Listener to request a ModeManager instance for a particular operation. */ public interface ExternalClientModeManagerRequestListener { /** * Returns an instance of ClientModeManager or null if the request failed (when wifi is * off). */ void onAnswer(@Nullable ClientModeManager modeManager); } private static class AdditionalClientModeManagerRequestInfo { @NonNull public final ExternalClientModeManagerRequestListener listener; @NonNull public final WorkSource requestorWs; @NonNull public final ClientConnectivityRole clientRole; @NonNull public final String ssid; @Nullable public final String bssid; public final boolean didUserApprove; AdditionalClientModeManagerRequestInfo( @NonNull ExternalClientModeManagerRequestListener listener, @NonNull WorkSource requestorWs, @NonNull ClientConnectivityRole clientRole, @NonNull String ssid, // For some use-cases, bssid is selected by firmware. @Nullable String bssid, boolean didUserApprove) { this.listener = listener; this.requestorWs = requestorWs; this.clientRole = clientRole; this.ssid = ssid; this.bssid = bssid; this.didUserApprove = didUserApprove; } } /** * Request a local only client manager. * @param listener used to receive the requested ClientModeManager. Will receive: * 1. null - if Wifi is toggled off * 2. The primary ClientModeManager - if a new ClientModeManager cannot be * created. * 3. The new ClientModeManager - if it was created successfully. * @param requestorWs the WorkSource for this request * @param didUserApprove if user explicitly approve on this request */ public void requestLocalOnlyClientModeManager( @NonNull ExternalClientModeManagerRequestListener listener, @NonNull WorkSource requestorWs, @NonNull String ssid, @NonNull String bssid, boolean didUserApprove) { if (listener == null) { Log.wtf(TAG, "Cannot provide a null ExternalClientModeManagerRequestListener"); return; } if (requestorWs == null) { Log.wtf(TAG, "Cannot provide a null WorkSource"); return; } mWifiController.sendMessage( WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, new AdditionalClientModeManagerRequestInfo(listener, requestorWs, ROLE_CLIENT_LOCAL_ONLY, ssid, bssid, didUserApprove)); } /** * Request a secondary long lived client manager. * * @param listener used to receive the requested ClientModeManager. Will receive: * 1. null - if Wifi is toggled off * 2. The primary ClientModeManager - if a new ClientModeManager cannot be * created. * 3. The new ClientModeManager - if it was created successfully. * @param requestorWs the WorkSource for this request */ public void requestSecondaryLongLivedClientModeManager( @NonNull ExternalClientModeManagerRequestListener listener, @NonNull WorkSource requestorWs, @NonNull String ssid, @Nullable String bssid) { if (listener == null) { Log.wtf(TAG, "Cannot provide a null ExternalClientModeManagerRequestListener"); return; } if (requestorWs == null) { Log.wtf(TAG, "Cannot provide a null WorkSource"); return; } mWifiController.sendMessage( WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, new AdditionalClientModeManagerRequestInfo(listener, requestorWs, ROLE_CLIENT_SECONDARY_LONG_LIVED, ssid, bssid, false)); } /** * Request a secondary transient client manager. * * @param listener used to receive the requested ClientModeManager. Will receive: * 1. null - if Wifi is toggled off. * 2. An existing secondary transient ClientModeManager - if it already exists. * 3. A new secondary transient ClientModeManager - if one doesn't exist and one * was created successfully. * 4. The primary ClientModeManager - if a new ClientModeManager cannot be * created. * @param requestorWs the WorkSource for this request */ public void requestSecondaryTransientClientModeManager( @NonNull ExternalClientModeManagerRequestListener listener, @NonNull WorkSource requestorWs, @NonNull String ssid, @Nullable String bssid) { if (listener == null) { Log.wtf(TAG, "Cannot provide a null ExternalClientModeManagerRequestListener"); return; } if (requestorWs == null) { Log.wtf(TAG, "Cannot provide a null WorkSource"); return; } mWifiController.sendMessage( WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, new AdditionalClientModeManagerRequestInfo(listener, requestorWs, ROLE_CLIENT_SECONDARY_TRANSIENT, ssid, bssid, false)); } /** * Checks if a CMM can be started for MBB. */ public boolean canRequestSecondaryTransientClientModeManager() { return canRequestMoreClientModeManagersInRole(INTERNAL_REQUESTOR_WS, ROLE_CLIENT_SECONDARY_TRANSIENT, false); } /** * Remove the provided client manager. */ public void removeClientModeManager(ClientModeManager clientModeManager) { mWifiController.sendMessage( WifiController.CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER, clientModeManager); } /** * Check whether we have a primary client mode manager (indicates wifi toggle on). */ public boolean hasPrimaryClientModeManager() { return getClientModeManagerInRole(ROLE_CLIENT_PRIMARY) != null; } /** * Checks whether there exists a primary or scan only mode manager. * @return */ private boolean hasPrimaryOrScanOnlyModeManager() { return getClientModeManagerInRole(ROLE_CLIENT_PRIMARY) != null || getClientModeManagerInRole(ROLE_CLIENT_SCAN_ONLY) != null || getClientModeManagerTransitioningIntoRole(ROLE_CLIENT_PRIMARY) != null || getClientModeManagerTransitioningIntoRole(ROLE_CLIENT_SCAN_ONLY) != null; } /** * Returns primary client mode manager if any, else returns null * This mode manager can be the default route on the device & will handle all external API * calls. * @return Instance of {@link ConcreteClientModeManager} or null. */ @Nullable public ConcreteClientModeManager getPrimaryClientModeManagerNullable() { return getClientModeManagerInRole(ROLE_CLIENT_PRIMARY); } /** * Returns primary client mode manager if any, else returns an instance of * {@link ClientModeManager}. * This mode manager can be the default route on the device & will handle all external API * calls. * @return Instance of {@link ClientModeManager}. */ @NonNull public ClientModeManager getPrimaryClientModeManager() { ClientModeManager cm = getPrimaryClientModeManagerNullable(); if (cm != null) return cm; // If there is no primary client manager, return the default one. return mDefaultClientModeManager; } /** * Returns all instances of ClientModeManager in * {@link ActiveModeManager.ClientInternetConnectivityRole} roles. * @return List of {@link ClientModeManager}. */ @NonNull public List getInternetConnectivityClientModeManagers() { List modeManagers = new ArrayList<>(); for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getRole() instanceof ClientInternetConnectivityRole) { modeManagers.add(manager); } } return modeManagers; } /** Stop all secondary transient ClientModeManagers. */ public void stopAllClientModeManagersInRole(ClientRole role) { // there should only be at most one Make Before Break CMM, but check all of them to be safe. for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getRole() == role) { stopAdditionalClientModeManager(manager); } } } @NonNull public List getClientModeManagers() { return new ArrayList<>(mClientModeManagers); } /** * Returns scan only client mode manager, if any. * This mode manager will only allow scanning. * @return Instance of {@link ClientModeManager} or null if none present. */ @Nullable public ClientModeManager getScanOnlyClientModeManager() { return getClientModeManagerInRole(ROLE_CLIENT_SCAN_ONLY); } /** * Returns tethered softap manager, if any. * @return Instance of {@link SoftApManager} or null if none present. */ @Nullable public SoftApManager getTetheredSoftApManager() { return getSoftApManagerInRole(ROLE_SOFTAP_TETHERED); } /** * Returns LOHS softap manager, if any. * @return Instance of {@link SoftApManager} or null if none present. */ @Nullable public SoftApManager getLocalOnlySoftApManager() { return getSoftApManagerInRole(ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY); } private boolean hasAnyModeManager() { return !mClientModeManagers.isEmpty() || !mSoftApManagers.isEmpty(); } private boolean hasAnyClientModeManager() { return !mClientModeManagers.isEmpty(); } private boolean hasAnyClientModeManagerInConnectivityRole() { for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getRole() instanceof ClientConnectivityRole) return true; } return false; } private boolean hasAnySoftApManager() { return !mSoftApManagers.isEmpty(); } /** * @return true if all the client mode managers are in scan only role, * false if there are no client mode managers present or if any of them are not in scan only * role. */ private boolean areAllClientModeManagersInScanOnlyRole() { if (mClientModeManagers.isEmpty()) return false; for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getRole() != ROLE_CLIENT_SCAN_ONLY) return false; } return true; } /** Get any client mode manager in the given role, or null if none was found. */ @Nullable public ConcreteClientModeManager getClientModeManagerInRole(ClientRole role) { for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getRole() == role) return manager; } return null; } /** Get any client mode manager in the given target role, or null if none was found. */ @Nullable public ConcreteClientModeManager getClientModeManagerTransitioningIntoRole(ClientRole role) { for (ConcreteClientModeManager manager : mClientModeManagers) { if (manager.getTargetRole() == role) return manager; } return null; } /** Get all client mode managers in the specified roles. */ @NonNull public List getClientModeManagersInRoles(ClientRole... roles) { Set rolesSet = Set.of(roles); List result = new ArrayList<>(); for (ConcreteClientModeManager manager : mClientModeManagers) { ClientRole role = manager.getRole(); if (role != null && rolesSet.contains(role)) { result.add(manager); } } return result; } @Nullable private SoftApManager getSoftApManagerInRole(SoftApRole role) { for (SoftApManager manager : mSoftApManagers) { if (manager.getRole() == role) return manager; } return null; } private SoftApRole getRoleForSoftApIpMode(int ipMode) { return ipMode == IFACE_IP_MODE_TETHERED ? ROLE_SOFTAP_TETHERED : ActiveModeManager.ROLE_SOFTAP_LOCAL_ONLY; } /** * Method to enable soft ap for wifi hotspot. * * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if * the persisted config is to be used) and the target operating mode (ex, * {@link WifiManager#IFACE_IP_MODE_TETHERED} {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY}). * * @param softApConfig SoftApModeConfiguration for the hostapd softap */ private void startSoftApModeManager( @NonNull SoftApModeConfiguration softApConfig, @NonNull WorkSource requestorWs) { Log.d(TAG, "Starting SoftApModeManager config = " + softApConfig.getSoftApConfiguration()); Preconditions.checkState(softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY || softApConfig.getTargetMode() == IFACE_IP_MODE_TETHERED); WifiServiceImpl.SoftApCallbackInternal callback = softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY ? mLohsCallback : mSoftApCallback; SoftApManager manager = mWifiInjector.makeSoftApManager( new SoftApListener(), callback, softApConfig, requestorWs, getRoleForSoftApIpMode(softApConfig.getTargetMode()), mVerboseLoggingEnabled); mSoftApManagers.add(manager); } /** * Method to stop all soft ap for the specified mode. * * This method will stop any active softAp mode managers. * * @param ipMode the operating mode of APs to bring down (ex, * {@link WifiManager#IFACE_IP_MODE_TETHERED} or * {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY}). * Use {@link WifiManager#IFACE_IP_MODE_UNSPECIFIED} to stop all APs. */ private void stopSoftApModeManagers(int ipMode) { Log.d(TAG, "Shutting down all softap mode managers in mode " + ipMode); for (SoftApManager softApManager : mSoftApManagers) { if (ipMode == WifiManager.IFACE_IP_MODE_UNSPECIFIED || getRoleForSoftApIpMode(ipMode) == softApManager.getRole()) { softApManager.stop(); } } } private void updateCapabilityToSoftApModeManager(SoftApCapability capability, int ipMode) { for (SoftApManager softApManager : mSoftApManagers) { if (ipMode == softApManager.getSoftApModeConfiguration().getTargetMode()) { softApManager.updateCapability(capability); } } } private void updateConfigurationToSoftApModeManager(SoftApConfiguration config) { for (SoftApManager softApManager : mSoftApManagers) { softApManager.updateConfiguration(config); } } /** * Method to enable a new primary client mode manager in scan only mode. */ private boolean startScanOnlyClientModeManager(WorkSource requestorWs) { if (hasPrimaryOrScanOnlyModeManager()) { Log.e(TAG, "Unexpected state - scan only CMM should not be started when a primary " + "or scan only CMM is already present."); if (!mIsMultiplePrimaryBugreportTaken) { mIsMultiplePrimaryBugreportTaken = true; mWifiDiagnostics.takeBugReport("Wi-Fi ActiveModeWarden bugreport", "Trying to start scan only mode manager when one already exists."); } return false; } Log.d(TAG, "Starting primary ClientModeManager in scan only mode"); ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager( new ClientListener(), requestorWs, ROLE_CLIENT_SCAN_ONLY, mVerboseLoggingEnabled); mClientModeManagers.add(manager); mLastScanOnlyClientModeManagerRequestorWs = requestorWs; return true; } /** * Method to enable a new primary client mode manager in connect mode. */ private boolean startPrimaryClientModeManager(WorkSource requestorWs) { if (hasPrimaryOrScanOnlyModeManager()) { Log.e(TAG, "Unexpected state - primary CMM should not be started when a primary " + "or scan only CMM is already present."); if (!mIsMultiplePrimaryBugreportTaken) { mIsMultiplePrimaryBugreportTaken = true; mWifiDiagnostics.takeBugReport("Wi-Fi ActiveModeWarden bugreport", "Trying to start primary mode manager when one already exists."); } return false; } Log.d(TAG, "Starting primary ClientModeManager in connect mode"); ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager( new ClientListener(), requestorWs, ROLE_CLIENT_PRIMARY, mVerboseLoggingEnabled); mClientModeManagers.add(manager); mLastPrimaryClientModeManagerRequestorWs = requestorWs; return true; } /** * Method to enable a new primary client mode manager. */ private boolean startPrimaryOrScanOnlyClientModeManager(WorkSource requestorWs) { ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager(); if (role == ROLE_CLIENT_PRIMARY) { return startPrimaryClientModeManager(requestorWs); } else if (role == ROLE_CLIENT_SCAN_ONLY) { return startScanOnlyClientModeManager(requestorWs); } else { return false; } } private List getClientModeManagersPrimaryLast() { List primaries = new ArrayList<>(); List others = new ArrayList<>(); for (ConcreteClientModeManager clientModeManager : mClientModeManagers) { if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY) { primaries.add(clientModeManager); } else { others.add(clientModeManager); } } if (primaries.size() > 1) { Log.wtf(TAG, "More than 1 primary CMM detected when turning off Wi-Fi"); mWifiDiagnostics.takeBugReport("Wi-Fi ActiveModeWarden bugreport", "Multiple primary CMMs detected when turning off Wi-Fi."); } List result = new ArrayList<>(); result.addAll(others); result.addAll(primaries); return result; } /** * Method to stop all client mode mangers. */ private void stopAllClientModeManagers() { Log.d(TAG, "Shutting down all client mode managers"); for (ConcreteClientModeManager clientModeManager : getClientModeManagersPrimaryLast()) { clientModeManager.stop(); } } /** * Method to switch all primary client mode manager mode of operation to ScanOnly mode. */ private void switchAllPrimaryClientModeManagersToScanOnlyMode(@NonNull WorkSource requestorWs) { Log.d(TAG, "Switching all primary client mode managers to scan only mode"); for (ConcreteClientModeManager clientModeManager : mClientModeManagers) { if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY) { continue; } clientModeManager.setRole(ROLE_CLIENT_SCAN_ONLY, requestorWs); } } private void stopSecondaryClientModeManagers() { stopAllClientModeManagersInRole(ROLE_CLIENT_LOCAL_ONLY); stopAllClientModeManagersInRole(ROLE_CLIENT_SECONDARY_TRANSIENT); stopAllClientModeManagersInRole(ROLE_CLIENT_SECONDARY_LONG_LIVED); } /** * Method to switch all client mode manager mode of operation (from ScanOnly To Connect & * vice-versa) based on the toggle state. */ private boolean switchAllPrimaryOrScanOnlyClientModeManagers() { Log.d(TAG, "Switching all client mode managers"); for (ConcreteClientModeManager clientModeManager : mClientModeManagers) { if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY && clientModeManager.getRole() != ROLE_CLIENT_SCAN_ONLY) { continue; } if (!switchPrimaryOrScanOnlyClientModeManagerRole(clientModeManager)) { return false; } } return true; } private ActiveModeManager.ClientRole getRoleForPrimaryOrScanOnlyClientModeManager() { if (mSettingsStore.isWifiToggleEnabled()) { return ROLE_CLIENT_PRIMARY; } else if (mWifiController.shouldEnableScanOnlyMode()) { return ROLE_CLIENT_SCAN_ONLY; } else { Log.e(TAG, "Something is wrong, no client mode toggles enabled"); return null; } } /** * Method to switch a client mode manager mode of operation (from ScanOnly To Connect & * vice-versa) based on the toggle state. */ private boolean switchPrimaryOrScanOnlyClientModeManagerRole( @NonNull ConcreteClientModeManager modeManager) { ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager(); final WorkSource lastRequestorWs; if (role == ROLE_CLIENT_PRIMARY) { lastRequestorWs = mLastPrimaryClientModeManagerRequestorWs; } else if (role == ROLE_CLIENT_SCAN_ONLY) { lastRequestorWs = mLastScanOnlyClientModeManagerRequestorWs; } else { return false; } modeManager.setRole(role, lastRequestorWs); return true; } /** * Method to start a new client mode manager. */ private boolean startAdditionalClientModeManager( ClientConnectivityRole role, @NonNull ExternalClientModeManagerRequestListener externalRequestListener, @NonNull WorkSource requestorWs) { Log.d(TAG, "Starting additional ClientModeManager in role: " + role); ClientListener listener = new ClientListener(externalRequestListener); ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager( listener, requestorWs, role, mVerboseLoggingEnabled); mClientModeManagers.add(manager); if (ROLE_CLIENT_SECONDARY_LONG_LIVED.equals(role) || ROLE_CLIENT_LOCAL_ONLY.equals(role)) { synchronized (mServiceApiLock) { mRequestWs.add(new WorkSource(requestorWs)); } } return true; } /** * Method to switch role for an existing non-primary client mode manager. */ private boolean switchRoleForAdditionalClientModeManager( @NonNull ConcreteClientModeManager manager, @NonNull ClientConnectivityRole role, @NonNull ExternalClientModeManagerRequestListener externalRequestListener, @NonNull WorkSource requestorWs) { Log.d(TAG, "Switching role for additional ClientModeManager to role: " + role); ClientListener listener = new ClientListener(externalRequestListener); synchronized (mServiceApiLock) { mRequestWs.remove(manager.getRequestorWs()); } if (ROLE_CLIENT_SECONDARY_LONG_LIVED.equals(role) || ROLE_CLIENT_LOCAL_ONLY.equals(role)) { synchronized (mServiceApiLock) { mRequestWs.add(new WorkSource(requestorWs)); } } manager.setRole(role, requestorWs, listener); return true; } /** * Method to stop client mode manager. */ private void stopAdditionalClientModeManager(ClientModeManager clientModeManager) { if (clientModeManager instanceof DefaultClientModeManager || clientModeManager.getRole() == ROLE_CLIENT_PRIMARY || clientModeManager.getRole() == ROLE_CLIENT_SCAN_ONLY) return; Log.d(TAG, "Shutting down additional client mode manager: " + clientModeManager); clientModeManager.stop(); } /** * Method to stop all active modes, for example, when toggling airplane mode. */ private void shutdownWifi() { Log.d(TAG, "Shutting down all mode managers"); for (ActiveModeManager manager : getActiveModeManagers()) { manager.stop(); } } /** * Dump current state for active mode managers. * * Must be called from the main Wifi thread. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Dump of " + TAG); pw.println("Current wifi mode: " + getCurrentMode()); pw.println("Wi-Fi is " + getWifiStateName()); pw.println("NumActiveModeManagers: " + getActiveModeManagerCount()); pw.println("mIsMultiplePrimaryBugreportTaken: " + mIsMultiplePrimaryBugreportTaken); mWifiController.dump(fd, pw, args); for (ActiveModeManager manager : getActiveModeManagers()) { manager.dump(fd, pw, args); } mGraveyard.dump(fd, pw, args); boolean isStaStaConcurrencySupported = mWifiNative.isStaStaConcurrencySupported(); pw.println("STA + STA Concurrency Supported: " + isStaStaConcurrencySupported); if (isStaStaConcurrencySupported) { pw.println(" MBB use-case enabled: " + mContext.getResources().getBoolean( R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled)); pw.println(" Local only use-case enabled: " + mContext.getResources().getBoolean( R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled)); pw.println(" Restricted use-case enabled: " + mContext.getResources().getBoolean( R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled)); pw.println(" Multi internet use-case enabled: " + mContext.getResources().getBoolean( R.bool.config_wifiMultiStaMultiInternetConcurrencyEnabled)); } pw.println("STA + AP Concurrency Supported: " + mWifiNative.isStaApConcurrencySupported()); mWifiInjector.getHalDeviceManager().dump(fd, pw, args); pw.println("Wifi handler thread overruns"); mWifiInjector.getWifiHandlerLocalLog().dump(fd, pw, args); } @VisibleForTesting String getCurrentMode() { IState state = mWifiController.getCurrentState(); return state == null ? STATE_MACHINE_EXITED_STATE_NAME : state.getName(); } @VisibleForTesting Collection getActiveModeManagers() { ArrayList activeModeManagers = new ArrayList<>(); activeModeManagers.addAll(mSoftApManagers); activeModeManagers.addAll(getClientModeManagersPrimaryLast()); return activeModeManagers; } private int getActiveModeManagerCount() { return mSoftApManagers.size() + mClientModeManagers.size(); } @VisibleForTesting boolean isInEmergencyMode() { IState state = mWifiController.getCurrentState(); return ((WifiController.BaseState) state).isInEmergencyMode(); } private void updateBatteryStats() { updateBatteryStatsWifiState(hasAnyModeManager()); if (areAllClientModeManagersInScanOnlyRole()) { updateBatteryStatsScanModeActive(); } } private class SoftApListener implements ActiveModeManager.Listener { @Override public void onStarted(SoftApManager softApManager) { updateBatteryStats(); invokeOnAddedCallbacks(softApManager); } @Override public void onRoleChanged(SoftApManager softApManager) { Log.w(TAG, "Role switched received on SoftApManager unexpectedly"); } @Override public void onStopped(SoftApManager softApManager) { mSoftApManagers.remove(softApManager); mGraveyard.inter(softApManager); updateBatteryStats(); mWifiController.sendMessage(WifiController.CMD_AP_STOPPED); invokeOnRemovedCallbacks(softApManager); } @Override public void onStartFailure(SoftApManager softApManager) { mSoftApManagers.remove(softApManager); mGraveyard.inter(softApManager); updateBatteryStats(); mWifiController.sendMessage(WifiController.CMD_AP_START_FAILURE); // onStartFailure can be called when switching between roles. So, remove // update listeners. Log.e(TAG, "SoftApManager start failed!" + softApManager); invokeOnRemovedCallbacks(softApManager); } } private class ClientListener implements ActiveModeManager.Listener { @Nullable private ExternalClientModeManagerRequestListener mExternalRequestListener; // one shot ClientListener() { this(null); } ClientListener( @Nullable ExternalClientModeManagerRequestListener externalRequestListener) { mExternalRequestListener = externalRequestListener; } @WifiNative.MultiStaUseCase private int getMultiStatUseCase() { // Note: The use-case setting finds the first non-primary client mode manager to set // the use-case to HAL. This does not extend to 3 STA concurrency when there are // 2 secondary STA client mode managers. for (ClientModeManager cmm : getClientModeManagers()) { ClientRole clientRole = cmm.getRole(); if (clientRole == ROLE_CLIENT_LOCAL_ONLY || clientRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) { return WifiNative.DUAL_STA_NON_TRANSIENT_UNBIASED; } else if (clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT) { return WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY; } } // if single STA, a safe default is PREFER_PRIMARY return WifiNative.DUAL_STA_TRANSIENT_PREFER_PRIMARY; } /** * Hardware needs to be configured for STA + STA before sending the callbacks to clients * letting them know that CM is ready for use. */ private void configureHwForMultiStaIfNecessary() { mWifiNative.setMultiStaUseCase(getMultiStatUseCase()); String primaryIfaceName = getPrimaryClientModeManager().getInterfaceName(); // if no primary exists (occurs briefly during Make Before Break), don't update the // primary and keep the previous primary. Only update WifiNative when the new primary is // activated. if (primaryIfaceName != null) { mWifiNative.setMultiStaPrimaryConnection(primaryIfaceName); } } private void onStartedOrRoleChanged(ConcreteClientModeManager clientModeManager) { updateClientScanMode(); updateBatteryStats(); configureHwForMultiStaIfNecessary(); if (mExternalRequestListener != null) { mExternalRequestListener.onAnswer(clientModeManager); mExternalRequestListener = null; // reset after one shot. } // Report to SarManager reportWifiStateToSarManager(); } private void reportWifiStateToSarManager() { if (areAllClientModeManagersInScanOnlyRole()) { // Inform sar manager that scan only is being enabled mWifiInjector.getSarManager().setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); } else { // Inform sar manager that scan only is being disabled mWifiInjector.getSarManager().setScanOnlyWifiState(WifiManager.WIFI_STATE_DISABLED); } if (hasAnyClientModeManagerInConnectivityRole()) { // Inform sar manager that wifi is Enabled mWifiInjector.getSarManager().setClientWifiState(WifiManager.WIFI_STATE_ENABLED); } else { // Inform sar manager that wifi is being disabled mWifiInjector.getSarManager().setClientWifiState(WifiManager.WIFI_STATE_DISABLED); } } private void onPrimaryChangedDueToStartedOrRoleChanged( ConcreteClientModeManager clientModeManager) { if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY && clientModeManager == mLastPrimaryClientModeManager) { // CMM was primary, but is no longer primary invokeOnPrimaryClientModeManagerChangedCallbacks(clientModeManager, null); mLastPrimaryClientModeManager = null; } else if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY && clientModeManager != mLastPrimaryClientModeManager) { // CMM is primary, but wasn't primary before invokeOnPrimaryClientModeManagerChangedCallbacks( mLastPrimaryClientModeManager, clientModeManager); mLastPrimaryClientModeManager = clientModeManager; setCurrentNetwork(clientModeManager.getCurrentNetwork()); } setSupportedFeatureSet( // If primary doesn't exist, DefaultClientModeManager getInterfaceName name // returns null. mWifiNative.getSupportedFeatureSet( getPrimaryClientModeManager().getInterfaceName()), mWifiNative.isStaApConcurrencySupported(), mWifiNative.isStaStaConcurrencySupported()); if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY) { int band = mWifiNative.getSupportedBandsForSta( clientModeManager.getInterfaceName()); if (band == WifiScanner.WIFI_BAND_UNSPECIFIED) band = getStaBandsFromConfigStore(); setBandSupported(band); } } @Override public void onStarted(@NonNull ConcreteClientModeManager clientModeManager) { onStartedOrRoleChanged(clientModeManager); invokeOnAddedCallbacks(clientModeManager); // invoke "added" callbacks before primary changed onPrimaryChangedDueToStartedOrRoleChanged(clientModeManager); } @Override public void onRoleChanged(@NonNull ConcreteClientModeManager clientModeManager) { onStartedOrRoleChanged(clientModeManager); invokeOnRoleChangedCallbacks(clientModeManager); onPrimaryChangedDueToStartedOrRoleChanged(clientModeManager); } private void onStoppedOrStartFailure(ConcreteClientModeManager clientModeManager) { mClientModeManagers.remove(clientModeManager); if (ROLE_CLIENT_SECONDARY_LONG_LIVED.equals(clientModeManager.getPreviousRole()) || ROLE_CLIENT_LOCAL_ONLY.equals(clientModeManager.getPreviousRole())) { synchronized (mServiceApiLock) { mRequestWs.remove(clientModeManager.getRequestorWs()); } } mGraveyard.inter(clientModeManager); updateClientScanMode(); updateBatteryStats(); if (clientModeManager == mLastPrimaryClientModeManager) { // CMM was primary, but was stopped invokeOnPrimaryClientModeManagerChangedCallbacks( mLastPrimaryClientModeManager, null); mLastPrimaryClientModeManager = null; setSupportedFeatureSet(mWifiNative.getSupportedFeatureSet(null), mWifiNative.isStaApConcurrencySupported(), mWifiNative.isStaStaConcurrencySupported()); setBandSupported(getStaBandsFromConfigStore()); } // invoke "removed" callbacks after primary changed invokeOnRemovedCallbacks(clientModeManager); // Report to SarManager reportWifiStateToSarManager(); } @Override public void onStopped(@NonNull ConcreteClientModeManager clientModeManager) { onStoppedOrStartFailure(clientModeManager); mWifiController.sendMessage(WifiController.CMD_STA_STOPPED); } @Override public void onStartFailure(@NonNull ConcreteClientModeManager clientModeManager) { Log.e(TAG, "ClientModeManager start failed!" + clientModeManager); // onStartFailure can be called when switching between roles. So, remove // update listeners. onStoppedOrStartFailure(clientModeManager); mWifiController.sendMessage(WifiController.CMD_STA_START_FAILURE); } } // Update the scan state based on all active mode managers. private void updateClientScanMode() { boolean scanEnabled = hasAnyClientModeManager(); boolean scanningForHiddenNetworksEnabled; if (mContext.getResources().getBoolean(R.bool.config_wifiScanHiddenNetworksScanOnlyMode)) { scanningForHiddenNetworksEnabled = hasAnyClientModeManager(); } else { scanningForHiddenNetworksEnabled = hasAnyClientModeManagerInConnectivityRole(); } mScanRequestProxy.enableScanning(scanEnabled, scanningForHiddenNetworksEnabled); } /** * Helper method to report wifi state as on/off (doesn't matter which mode). * * @param enabled boolean indicating that some mode has been turned on or off */ private void updateBatteryStatsWifiState(boolean enabled) { if (enabled) { if (getActiveModeManagerCount() == 1) { // only report wifi on if we haven't already mBatteryStatsManager.reportWifiOn(); } } else { if (getActiveModeManagerCount() == 0) { // only report if we don't have any active modes mBatteryStatsManager.reportWifiOff(); } } } private void updateBatteryStatsScanModeActive() { mBatteryStatsManager.reportWifiState(BatteryStatsManager.WIFI_STATE_OFF_SCANNING, null); } /** * Called to pull metrics from ActiveModeWarden to WifiMetrics when a dump is triggered, as * opposed to the more common push metrics which are reported to WifiMetrics as soon as they * occur. */ public void updateMetrics() { mWifiMetrics.setIsMakeBeforeBreakSupported(isStaStaConcurrencySupportedForMbb()); } /** * During Wifi off -> on transition, there is a race condition between country code update, * single scan triggered by App based ACTION_WIFI_SCAN_AVAILABILITY_CHANGED. The single scan * might fail if country code is updated while the scan is ongoing. * To mitigate that issue, send ACTION_WIFI_SCAN_AVAILABILITY_CHANGED again when the country * code update is completed. * * @param newCountryCode the new country code, null when there is no active mode enabled. */ public void updateClientScanModeAfterCountryCodeUpdate(@Nullable String newCountryCode) { // Handle country code changed only during Wifi off -> on transition. if (newCountryCode != null) { updateClientScanMode(); } } /** * WifiController is the class used to manage wifi state for various operating * modes (normal, airplane, wifi hotspot, etc.). */ private class WifiController extends StateMachine { private static final String TAG = "WifiController"; // Maximum limit to use for timeout delay if the value from overlay setting is too large. private static final int MAX_RECOVERY_TIMEOUT_DELAY_MS = 4000; private static final int BASE = Protocol.BASE_WIFI_CONTROLLER; static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1; static final int CMD_EMERGENCY_SCAN_STATE_CHANGED = BASE + 2; static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7; static final int CMD_WIFI_TOGGLED = BASE + 8; static final int CMD_AIRPLANE_TOGGLED = BASE + 9; static final int CMD_SET_AP = BASE + 10; static final int CMD_EMERGENCY_CALL_STATE_CHANGED = BASE + 14; static final int CMD_AP_STOPPED = BASE + 15; static final int CMD_STA_START_FAILURE = BASE + 16; // Command used to trigger a wifi stack restart when in active mode static final int CMD_RECOVERY_RESTART_WIFI = BASE + 17; // Internal command used to complete wifi stack restart private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18; // Command to disable wifi when SelfRecovery is throttled or otherwise not doing full // recovery static final int CMD_RECOVERY_DISABLE_WIFI = BASE + 19; static final int CMD_STA_STOPPED = BASE + 20; static final int CMD_DEFERRED_RECOVERY_RESTART_WIFI = BASE + 22; static final int CMD_AP_START_FAILURE = BASE + 23; static final int CMD_UPDATE_AP_CAPABILITY = BASE + 24; static final int CMD_UPDATE_AP_CONFIG = BASE + 25; static final int CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER = BASE + 26; static final int CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER = BASE + 27; static final int CMD_SATELLITE_MODE_CHANGED = BASE + 28; private final EnabledState mEnabledState; private final DisabledState mDisabledState; private boolean mIsInEmergencyCall = false; private boolean mIsInEmergencyCallbackMode = false; private boolean mIsEmergencyScanInProgress = false; WifiController() { super(TAG, mLooper); final int threshold = mContext.getResources().getInteger( R.integer.config_wifiConfigurationWifiRunnerThresholdInMs); DefaultState defaultState = new DefaultState(threshold); mEnabledState = new EnabledState(threshold); mDisabledState = new DisabledState(threshold); addState(defaultState); { addState(mDisabledState, defaultState); addState(mEnabledState, defaultState); } setLogRecSize(100); setLogOnlyTransitions(false); } /** * Return the additional string to be logged by LogRec. * * @param msg that was processed * @return information to be logged as a String */ @Override protected String getLogRecString(Message msg) { StringBuilder sb = new StringBuilder(); sb.append(msg.arg1) .append(" ").append(msg.arg2) .append(" num ClientModeManagers:").append(mClientModeManagers.size()) .append(" num SoftApManagers:").append(mSoftApManagers.size()); if (msg.obj != null) { sb.append(" ").append(msg.obj); } return sb.toString(); } @Override protected String getWhatToString(int what) { switch (what) { case CMD_AIRPLANE_TOGGLED: return "CMD_AIRPLANE_TOGGLED"; case CMD_AP_START_FAILURE: return "CMD_AP_START_FAILURE"; case CMD_AP_STOPPED: return "CMD_AP_STOPPED"; case CMD_DEFERRED_RECOVERY_RESTART_WIFI: return "CMD_DEFERRED_RECOVERY_RESTART_WIFI"; case CMD_EMERGENCY_CALL_STATE_CHANGED: return "CMD_EMERGENCY_CALL_STATE_CHANGED"; case CMD_EMERGENCY_MODE_CHANGED: return "CMD_EMERGENCY_MODE_CHANGED"; case CMD_RECOVERY_DISABLE_WIFI: return "CMD_RECOVERY_DISABLE_WIFI"; case CMD_RECOVERY_RESTART_WIFI: return "CMD_RECOVERY_RESTART_WIFI"; case CMD_RECOVERY_RESTART_WIFI_CONTINUE: return "CMD_RECOVERY_RESTART_WIFI_CONTINUE"; case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER: return "CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER"; case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER: return "CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER"; case CMD_EMERGENCY_SCAN_STATE_CHANGED: return "CMD_EMERGENCY_SCAN_STATE_CHANGED"; case CMD_SCAN_ALWAYS_MODE_CHANGED: return "CMD_SCAN_ALWAYS_MODE_CHANGED"; case CMD_SET_AP: return "CMD_SET_AP"; case CMD_STA_START_FAILURE: return "CMD_STA_START_FAILURE"; case CMD_STA_STOPPED: return "CMD_STA_STOPPED"; case CMD_UPDATE_AP_CAPABILITY: return "CMD_UPDATE_AP_CAPABILITY"; case CMD_UPDATE_AP_CONFIG: return "CMD_UPDATE_AP_CONFIG"; case CMD_WIFI_TOGGLED: return "CMD_WIFI_TOGGLED"; case CMD_SATELLITE_MODE_CHANGED: return "CMD_SATELLITE_MODE_CHANGED"; case RunnerState.STATE_ENTER_CMD: return "Enter"; case RunnerState.STATE_EXIT_CMD: return "Exit"; default: return "what:" + what; } } @Override public void start() { boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn(); boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled(); boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable(); boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled(); boolean isSatelliteModeOn = mSettingsStore.isSatelliteModeOn(); log("isAirplaneModeOn = " + isAirplaneModeOn + ", isWifiEnabled = " + isWifiEnabled + ", isScanningAvailable = " + isScanningAlwaysAvailable + ", isLocationModeActive = " + isLocationModeActive + ", isSatelliteModeOn = " + isSatelliteModeOn); // Initialize these values at bootup to defaults, will be overridden by API calls // for further toggles. mLastPrimaryClientModeManagerRequestorWs = mFacade.getSettingsWorkSource(mContext); mLastScanOnlyClientModeManagerRequestorWs = INTERNAL_REQUESTOR_WS; ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager(); if (role == ROLE_CLIENT_PRIMARY) { startPrimaryClientModeManager(mLastPrimaryClientModeManagerRequestorWs); setInitialState(mEnabledState); } else if (role == ROLE_CLIENT_SCAN_ONLY) { startScanOnlyClientModeManager(mLastScanOnlyClientModeManagerRequestorWs); setInitialState(mEnabledState); } else { setInitialState(mDisabledState); } mWifiMetrics.noteWifiEnabledDuringBoot(mSettingsStore.isWifiToggleEnabled()); // Initialize the lower layers before we start. mWifiNative.initialize(); super.start(); } private int readWifiRecoveryDelay() { int recoveryDelayMillis = mContext.getResources().getInteger( R.integer.config_wifi_framework_recovery_timeout_delay); if (recoveryDelayMillis > MAX_RECOVERY_TIMEOUT_DELAY_MS) { recoveryDelayMillis = MAX_RECOVERY_TIMEOUT_DELAY_MS; Log.w(TAG, "Overriding timeout delay with maximum limit value"); } return recoveryDelayMillis; } abstract class BaseState extends RunnerState { BaseState(int threshold, @NonNull LocalLog localLog) { super(threshold, localLog); } @VisibleForTesting boolean isInEmergencyMode() { return mIsInEmergencyCall || mIsInEmergencyCallbackMode; } /** Device is in emergency mode & carrier config requires wifi off in emergency mode */ private boolean isInEmergencyModeWhichRequiresWifiDisable() { return isInEmergencyMode() && mFacade.getConfigWiFiDisableInECBM(mContext); } private void updateEmergencyMode(Message msg) { if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED) { mIsInEmergencyCall = msg.arg1 == 1; } else if (msg.what == CMD_EMERGENCY_MODE_CHANGED) { mIsInEmergencyCallbackMode = msg.arg1 == 1; } } private void enterEmergencyMode() { stopSoftApModeManagers(WifiManager.IFACE_IP_MODE_UNSPECIFIED); boolean configWiFiDisableInECBM = mFacade.getConfigWiFiDisableInECBM(mContext); log("Entering emergency callback mode, " + "CarrierConfigManager.KEY_CONFIG_WIFI_DISABLE_IN_ECBM: " + configWiFiDisableInECBM); if (!mIsEmergencyScanInProgress) { if (configWiFiDisableInECBM) { shutdownWifi(); } } else { if (configWiFiDisableInECBM) { switchAllPrimaryClientModeManagersToScanOnlyMode( mFacade.getSettingsWorkSource(mContext)); } } } private void exitEmergencyMode() { log("Exiting emergency callback mode"); // may be in DisabledState or EnabledState (depending on whether Wifi was shut down // in enterEmergencyMode() or not based on getConfigWiFiDisableInECBM). // Let CMD_WIFI_TOGGLED handling decide what the next state should be, or if we're // already in the correct state. // Assumes user toggled it on from settings before. wifiToggled(mFacade.getSettingsWorkSource(mContext)); } private boolean processMessageInEmergencyMode(Message msg) { // In emergency mode: Some messages need special handling in this mode, // all others are dropped. switch (msg.what) { case CMD_STA_STOPPED: case CMD_AP_STOPPED: log("Processing message in Emergency Callback Mode: " + msg); if (!hasAnyModeManager()) { log("No active mode managers, return to DisabledState."); transitionTo(mDisabledState); } break; case CMD_SET_AP: // arg1 == 1 => enable AP if (msg.arg1 == 1) { log("AP cannot be started in Emergency Callback Mode: " + msg); // SoftAP was disabled upon entering emergency mode. It also cannot // be re-enabled during emergency mode. Drop the message and invoke // the failure callback. Pair softApConfigAndWs = (Pair) msg.obj; SoftApModeConfiguration softApConfig = softApConfigAndWs.first; WifiServiceImpl.SoftApCallbackInternal callback = softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY ? mLohsCallback : mSoftApCallback; // need to notify SoftApCallback that start/stop AP failed callback.onStateChanged(new SoftApState( WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL, softApConfig.getTetheringRequest(), null /* iface */)); } break; default: log("Dropping message in emergency callback mode: " + msg); break; } return HANDLED; } private void handleEmergencyModeStateChange(Message msg) { boolean wasInEmergencyMode = isInEmergencyMode(); updateEmergencyMode(msg); boolean isInEmergencyMode = isInEmergencyMode(); if (!wasInEmergencyMode && isInEmergencyMode) { enterEmergencyMode(); } else if (wasInEmergencyMode && !isInEmergencyMode) { exitEmergencyMode(); } } private void handleEmergencyScanStateChange(Message msg) { final boolean scanInProgress = msg.arg1 == 1; final WorkSource requestorWs = (WorkSource) msg.obj; log("Processing scan state change: " + scanInProgress); mIsEmergencyScanInProgress = scanInProgress; if (isInEmergencyModeWhichRequiresWifiDisable()) { // If wifi was disabled because of emergency mode // (getConfigWiFiDisableInECBM == true), don't use the // generic method to handle toggle change since that may put wifi in // connectivity mode (since wifi toggle may actually be on underneath) if (getCurrentState() == mDisabledState && scanInProgress) { // go to scan only mode. startScanOnlyClientModeManager(requestorWs); transitionTo(mEnabledState); } else if (getCurrentState() == mEnabledState && !scanInProgress) { // shut down to go back to previous state. stopAllClientModeManagers(); } } else { if (getCurrentState() == mDisabledState) { handleStaToggleChangeInDisabledState(requestorWs); } else if (getCurrentState() == mEnabledState) { handleStaToggleChangeInEnabledState(requestorWs); } } } @Override public void enterImpl() { } @Override public void exitImpl() { } @Override public String getMessageLogRec(int what) { return ActiveModeWarden.class.getSimpleName() + "." + DefaultState.class.getSimpleName() + "." + getWhatToString(what); } @Override public final boolean processMessageImpl(Message msg) { // potentially enter emergency mode if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED || msg.what == CMD_EMERGENCY_MODE_CHANGED) { handleEmergencyModeStateChange(msg); return HANDLED; } else if (msg.what == CMD_EMERGENCY_SCAN_STATE_CHANGED) { // emergency scans need to be allowed even in emergency mode. handleEmergencyScanStateChange(msg); return HANDLED; } else if (isInEmergencyMode()) { return processMessageInEmergencyMode(msg); } else { // not in emergency mode, process messages normally return processMessageFiltered(msg); } } protected abstract boolean processMessageFiltered(Message msg); } class DefaultState extends RunnerState { DefaultState(int threshold) { super(threshold, mWifiInjector.getWifiHandlerLocalLog()); } @Override public String getMessageLogRec(int what) { return ActiveModeWarden.class.getSimpleName() + "." + DefaultState.class.getSimpleName() + "." + getWhatToString(what); } @Override public void enterImpl() { } @Override public void exitImpl() { } private void checkAndHandleAirplaneModeState(String loggingPackageName) { if (mSettingsStore.isAirplaneModeOn()) { log("Airplane mode toggled"); if (!mSettingsStore.shouldWifiRemainEnabledWhenApmEnabled()) { log("Wifi disabled on APM, disable wifi"); shutdownWifi(); // onStopped will move the state machine to "DisabledState". mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(), Process.WIFI_UID, -1, loggingPackageName, false); } } else { log("Airplane mode disabled, determine next state"); if (shouldEnableSta()) { startPrimaryOrScanOnlyClientModeManager( // Assumes user toggled it on from settings before. mFacade.getSettingsWorkSource(mContext)); transitionTo(mEnabledState); mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(), Process.WIFI_UID, -1, loggingPackageName, true); } // wifi should remain disabled, do not need to transition } } @Override public boolean processMessageImpl(Message msg) { switch (msg.what) { case CMD_SCAN_ALWAYS_MODE_CHANGED: case CMD_EMERGENCY_SCAN_STATE_CHANGED: case CMD_WIFI_TOGGLED: case CMD_STA_STOPPED: case CMD_STA_START_FAILURE: case CMD_AP_STOPPED: case CMD_AP_START_FAILURE: case CMD_RECOVERY_RESTART_WIFI: case CMD_RECOVERY_RESTART_WIFI_CONTINUE: case CMD_DEFERRED_RECOVERY_RESTART_WIFI: case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER: break; case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER: AdditionalClientModeManagerRequestInfo requestInfo = (AdditionalClientModeManagerRequestInfo) msg.obj; requestInfo.listener.onAnswer(null); break; case CMD_RECOVERY_DISABLE_WIFI: log("Recovery has been throttled, disable wifi"); shutdownWifi(); // onStopped will move the state machine to "DisabledState". break; case CMD_AIRPLANE_TOGGLED: if (mSettingsStore.isSatelliteModeOn()) { log("Satellite mode is on - return"); break; } checkAndHandleAirplaneModeState("android_apm"); break; case CMD_UPDATE_AP_CAPABILITY: updateCapabilityToSoftApModeManager((SoftApCapability) msg.obj, msg.arg1); break; case CMD_UPDATE_AP_CONFIG: updateConfigurationToSoftApModeManager((SoftApConfiguration) msg.obj); break; case CMD_SATELLITE_MODE_CHANGED: if (mSettingsStore.isSatelliteModeOn()) { log("Satellite mode is on, disable wifi"); shutdownWifi(); mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(), Process.WIFI_UID, -1, "satellite_mode", false); } else { log("Satellite mode is off, determine next stage"); checkAndHandleAirplaneModeState("satellite_mode"); } break; default: throw new RuntimeException("WifiController.handleMessage " + msg.what); } return HANDLED; } } private boolean shouldEnableScanOnlyMode() { return (mWifiPermissionsUtil.isLocationModeEnabled() && mSettingsStore.isScanAlwaysAvailable()) || mIsEmergencyScanInProgress; } private boolean shouldEnableSta() { return (mSettingsStore.isWifiToggleEnabled() || shouldEnableScanOnlyMode()) && !mSettingsStore.isSatelliteModeOn(); } private void handleStaToggleChangeInDisabledState(WorkSource requestorWs) { if (shouldEnableSta()) { startPrimaryOrScanOnlyClientModeManager(requestorWs); transitionTo(mEnabledState); } } private void handleStaToggleChangeInEnabledState(WorkSource requestorWs) { if (shouldEnableSta()) { if (hasPrimaryOrScanOnlyModeManager()) { if (!mSettingsStore.isWifiToggleEnabled()) { // Wifi is turned off, so we should stop all the secondary CMMs which are // currently all for connectivity purpose. It's important to stops the // secondary CMMs before switch state of the primary CMM so features using // those secondary CMMs knows to abort properly, and won't react in strange // ways to the primary switching to scan only mode later. stopSecondaryClientModeManagers(); mWifiInjector.getWifiConnectivityManager().resetOnWifiDisable(); } switchAllPrimaryOrScanOnlyClientModeManagers(); } else { startPrimaryOrScanOnlyClientModeManager(requestorWs); } } else { stopAllClientModeManagers(); mWifiInjector.getWifiConnectivityManager().resetOnWifiDisable(); } } class DisabledState extends BaseState { DisabledState(int threshold) { super(threshold, mWifiInjector.getWifiHandlerLocalLog()); } @Override public void enterImpl() { log("DisabledState.enter()"); super.enterImpl(); if (hasAnyModeManager()) { Log.e(TAG, "Entered DisabledState, but has active mode managers"); } } @Override public void exitImpl() { log("DisabledState.exit()"); super.exitImpl(); } @Override public boolean processMessageFiltered(Message msg) { switch (msg.what) { case CMD_WIFI_TOGGLED: case CMD_SCAN_ALWAYS_MODE_CHANGED: handleStaToggleChangeInDisabledState((WorkSource) msg.obj); break; case CMD_SET_AP: // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here if (msg.arg1 == 1) { Pair softApConfigAndWs = (Pair) msg.obj; startSoftApModeManager( softApConfigAndWs.first, softApConfigAndWs.second); transitionTo(mEnabledState); } break; case CMD_RECOVERY_RESTART_WIFI: log("Recovery triggered, already in disabled state"); sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE, Collections.emptyList(), readWifiRecoveryDelay()); break; case CMD_DEFERRED_RECOVERY_RESTART_WIFI: // wait mRecoveryDelayMillis for letting driver clean reset. sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE, msg.obj, readWifiRecoveryDelay()); break; case CMD_RECOVERY_RESTART_WIFI_CONTINUE: log("Recovery in progress, start wifi"); List modeManagersBeforeRecovery = (List) msg.obj; // No user controlled mode managers before recovery, so check if wifi // was toggled on. if (modeManagersBeforeRecovery.isEmpty()) { if (shouldEnableSta()) { startPrimaryOrScanOnlyClientModeManager( // Assumes user toggled it on from settings before. mFacade.getSettingsWorkSource(mContext)); transitionTo(mEnabledState); } break; } for (ActiveModeManager activeModeManager : modeManagersBeforeRecovery) { if (activeModeManager instanceof ConcreteClientModeManager) { startPrimaryOrScanOnlyClientModeManager( activeModeManager.getRequestorWs()); } else if (activeModeManager instanceof SoftApManager) { SoftApManager softApManager = (SoftApManager) activeModeManager; startSoftApModeManager( softApManager.getSoftApModeConfiguration(), softApManager.getRequestorWs()); } } transitionTo(mEnabledState); int numCallbacks = mRestartCallbacks.beginBroadcast(); for (int i = 0; i < numCallbacks; i++) { try { mRestartCallbacks.getBroadcastItem(i).onSubsystemRestarted(); } catch (RemoteException e) { Log.e(TAG, "Failure calling onSubsystemRestarted" + e); } } mRestartCallbacks.finishBroadcast(); mWifiInjector.getSelfRecovery().onRecoveryCompleted(); break; default: return NOT_HANDLED; } return HANDLED; } } class EnabledState extends BaseState { private boolean mIsDisablingDueToAirplaneMode; EnabledState(int threshold) { super(threshold, mWifiInjector.getWifiHandlerLocalLog()); } @Override public void enterImpl() { log("EnabledState.enter()"); super.enterImpl(); if (!hasAnyModeManager()) { Log.e(TAG, "Entered EnabledState, but no active mode managers"); } mIsDisablingDueToAirplaneMode = false; } @Override public void exitImpl() { log("EnabledState.exit()"); if (hasAnyModeManager()) { Log.e(TAG, "Exiting EnabledState, but has active mode managers"); } super.exitImpl(); } @Nullable private ConcreteClientModeManager findAnyClientModeManagerConnectingOrConnectedToBssid( @NonNull String ssid, @Nullable String bssid) { if (bssid == null) { return null; } for (ConcreteClientModeManager cmm : mClientModeManagers) { if (isClientModeManagerConnectedOrConnectingToBssid(cmm, ssid, bssid)) { return cmm; } } return null; } private void handleAdditionalClientModeManagerRequest( @NonNull AdditionalClientModeManagerRequestInfo requestInfo) { if (mWifiState.get() == WIFI_STATE_DISABLING || mWifiState.get() == WIFI_STATE_DISABLED) { // Do no allow getting secondary CMM when wifi is being disabled or disabled. requestInfo.listener.onAnswer(null); return; } ClientModeManager primaryManager = getPrimaryClientModeManagerNullable(); // TODO(b/228529090): Remove this special code once root cause is resolved. // Special case for holders with ENTER_CAR_MODE_PRIORITIZED. Only give them the // primary STA to avoid the device getting into STA+STA state. // In STA+STA wifi scans will result in high latency in the secondary STA. if (requestInfo.clientRole == ROLE_CLIENT_LOCAL_ONLY && requestInfo.requestorWs != null) { WorkSource workSource = requestInfo.requestorWs; for (int i = 0; i < workSource.size(); i++) { int curUid = workSource.getUid(i); if (mAllowRootToGetLocalOnlyCmm && curUid == 0) { // 0 is root UID. continue; } if (curUid != Process.SYSTEM_UID && mWifiPermissionsUtil.checkEnterCarModePrioritized(curUid)) { requestInfo.listener.onAnswer(primaryManager); if (mVerboseLoggingEnabled) { Log.w(TAG, "Uid " + curUid + " has car mode permission - disabling STA+STA"); } return; } } } if (requestInfo.clientRole == ROLE_CLIENT_SECONDARY_TRANSIENT && mDppManager.isSessionInProgress()) { // When MBB is triggered, we could end up switching the primary interface // after completion. So if we have any DPP session in progress, they will fail // when the previous primary iface is removed after MBB completion. Log.v(TAG, "DPP session in progress, fallback to single STA behavior " + "using primary ClientModeManager=" + primaryManager); requestInfo.listener.onAnswer(primaryManager); return; } ConcreteClientModeManager cmmForSameBssid = findAnyClientModeManagerConnectingOrConnectedToBssid( requestInfo.ssid, requestInfo.bssid); if (cmmForSameBssid != null) { // Can't allow 2 client mode managers triggering connection to same bssid. Log.v(TAG, "Already connected to bssid=" + requestInfo.bssid + " on ClientModeManager=" + cmmForSameBssid); if (cmmForSameBssid.getRole() == ROLE_CLIENT_PRIMARY) { // fallback to single STA behavior. requestInfo.listener.onAnswer(cmmForSameBssid); return; } // The CMM having BSSID conflict is exactly the one being requested. // Simply return the CMM in this case. The requestor will be responsible to // make sure it does not trigger the connection again when already connected. if (cmmForSameBssid.getRole() == requestInfo.clientRole) { requestInfo.listener.onAnswer(cmmForSameBssid); return; } // Existing secondary CMM connected to the same ssid/bssid. if (!canRequestMoreClientModeManagersInRole(requestInfo.requestorWs, requestInfo.clientRole, requestInfo.didUserApprove)) { Log.e(TAG, "New request cannot override existing request on " + "ClientModeManager=" + cmmForSameBssid); // If the new request does not have priority over the existing request, // reject it since we cannot have 2 CMM's connected to same ssid/bssid. requestInfo.listener.onAnswer(null); return; } // If the new request has a higher priority over the existing one, change it's // role and send it to the new client. // Switch role for non primary CMM & wait for it to complete before // handing it to the requestor. switchRoleForAdditionalClientModeManager( cmmForSameBssid, requestInfo.clientRole, requestInfo.listener, requestInfo.requestorWs); return; } ClientModeManager cmmForSameRole = getClientModeManagerInRole(requestInfo.clientRole); if (cmmForSameRole != null) { // Already have a client mode manager in the requested role. // Note: This logic results in the framework not supporting more than 1 CMM in // the same role concurrently. There is no use-case for that currently & // none of the clients (i.e WifiNetworkFactory, WifiConnectivityManager, etc) // are ready to support that either. If this assumption changes in the future // when the device supports 3 STA's for example, change this logic! Log.v(TAG, "Already exists ClientModeManager for role: " + cmmForSameRole); requestInfo.listener.onAnswer(cmmForSameRole); return; } if (canRequestMoreClientModeManagersInRole(requestInfo.requestorWs, requestInfo.clientRole, requestInfo.didUserApprove)) { // Can create an additional client mode manager. Log.v(TAG, "Starting a new ClientModeManager"); WorkSource ifCreatorWs = new WorkSource(requestInfo.requestorWs); if (requestInfo.didUserApprove) { // If user select to connect from the UI, promote the priority ifCreatorWs.add(mFacade.getSettingsWorkSource(mContext)); } startAdditionalClientModeManager(requestInfo.clientRole, requestInfo.listener, ifCreatorWs); return; } // fallback decision if (requestInfo.clientRole == ROLE_CLIENT_LOCAL_ONLY && isStaStaConcurrencySupportedForLocalOnlyConnections() && !mWifiPermissionsUtil.isTargetSdkLessThan( requestInfo.requestorWs.getPackageName(0), Build.VERSION_CODES.S, requestInfo.requestorWs.getUid(0))) { Log.d(TAG, "Will not fall back to single STA for a local-only connection when " + "STA+STA is supported (unless for a pre-S legacy app). " + " Priority inversion."); requestInfo.listener.onAnswer(null); return; } // Fall back to single STA behavior. Log.v(TAG, "Falling back to single STA behavior using primary ClientModeManager=" + primaryManager); requestInfo.listener.onAnswer(primaryManager); } @Override public boolean processMessageFiltered(Message msg) { switch (msg.what) { case CMD_WIFI_TOGGLED: case CMD_SCAN_ALWAYS_MODE_CHANGED: handleStaToggleChangeInEnabledState((WorkSource) msg.obj); break; case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER: handleAdditionalClientModeManagerRequest( (AdditionalClientModeManagerRequestInfo) msg.obj); break; case CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER: stopAdditionalClientModeManager((ClientModeManager) msg.obj); break; case CMD_SET_AP: // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here if (msg.arg1 == 1) { Pair softApConfigAndWs = (Pair) msg.obj; startSoftApModeManager( softApConfigAndWs.first, softApConfigAndWs.second); } else { stopSoftApModeManagers(msg.arg2); } break; case CMD_AIRPLANE_TOGGLED: // airplane mode toggled on is handled in the default state if (mSettingsStore.isAirplaneModeOn()) { mIsDisablingDueToAirplaneMode = true; return NOT_HANDLED; } else { if (mIsDisablingDueToAirplaneMode) { // Previous airplane mode toggle on is being processed, defer the // message toggle off until previous processing is completed. // Once previous airplane mode toggle is complete, we should // transition to DisabledState. There, we will process the deferred // airplane mode toggle message to disable airplane mode. deferMessage(msg); } else { if (!hasPrimaryOrScanOnlyModeManager()) { // SoftAp was enabled during airplane mode and caused // WifiController to be in EnabledState without // a primary client mode manager. // Defer to the default state to handle the airplane mode toggle // which may result in enabling wifi if necessary. log("airplane mode toggled - and no primary manager"); return NOT_HANDLED; } // when airplane mode is toggled off, but wifi is on, we can keep it // on log("airplane mode toggled - and airplane mode is off. return " + "handled"); } return HANDLED; } case CMD_SATELLITE_MODE_CHANGED: if (mSettingsStore.isSatelliteModeOn()) { log("Satellite mode is on, disable wifi"); shutdownWifi(); mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(), Process.WIFI_UID, -1, "satellite_mode", false); } else { if (!hasPrimaryOrScanOnlyModeManager()) { // Enabling SoftAp while wifi is off could result in // ActiveModeWarden being in enabledState without a CMM. // Defer to the default state in this case to handle the satellite // mode state change which may result in enabling wifi if necessary. log("Satellite mode is off in enabled state - " + "and no primary manager"); return NOT_HANDLED; } log("Satellite mode is off in enabled state. Return handled"); } break; case CMD_AP_STOPPED: case CMD_AP_START_FAILURE: if (hasAnyModeManager()) { log("AP disabled, remain in EnabledState."); break; } if (msg.what == CMD_AP_STOPPED) { mWifiInjector.getSelfRecovery().onWifiStopped(); if (mWifiInjector.getSelfRecovery().isRecoveryInProgress()) { // Recovery in progress, transit to disabled state. transitionTo(mDisabledState); break; } } if (shouldEnableSta()) { log("SoftAp disabled, start client mode"); startPrimaryOrScanOnlyClientModeManager( // Assumes user toggled it on from settings before. mFacade.getSettingsWorkSource(mContext)); } else { log("SoftAp mode disabled, return to DisabledState"); transitionTo(mDisabledState); } break; case CMD_STA_START_FAILURE: case CMD_STA_STOPPED: // Client mode stopped. Head to Disabled to wait for next command if there // is no active mode manager. if (!hasAnyModeManager()) { mWifiInjector.getSelfRecovery().onWifiStopped(); log("STA disabled, return to DisabledState."); transitionTo(mDisabledState); } else { log("STA disabled, remain in EnabledState."); } break; case CMD_DEFERRED_RECOVERY_RESTART_WIFI: // Wifi shutdown is not completed yet, still in enabled state. // Defer the message and wait for entering disabled state. deferMessage(msg); break; case CMD_RECOVERY_RESTART_WIFI: { final String bugTitle; final String bugDetail = (String) msg.obj; if (TextUtils.isEmpty(bugDetail)) { bugTitle = "Wi-Fi BugReport"; } else { bugTitle = "Wi-Fi BugReport: " + bugDetail; } log("Recovery triggered, disable wifi"); boolean bugReportRequested = msg.arg2 != 0; if (bugReportRequested) { mHandler.post(() -> mWifiDiagnostics.takeBugReport(bugTitle, bugDetail)); } // Store all instances of tethered SAP + scan only/primary STA mode managers List modeManagersBeforeRecovery = Stream.concat( mClientModeManagers.stream() .filter(m -> ROLE_CLIENT_SCAN_ONLY.equals(m.getRole()) || ROLE_CLIENT_PRIMARY.equals(m.getRole())), mSoftApManagers.stream() .filter(m -> ROLE_SOFTAP_TETHERED.equals(m.getRole()))) .collect(Collectors.toList()); deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI, modeManagersBeforeRecovery)); int numCallbacks = mRestartCallbacks.beginBroadcast(); for (int i = 0; i < numCallbacks; i++) { try { mRestartCallbacks.getBroadcastItem(i).onSubsystemRestarting(); } catch (RemoteException e) { Log.e(TAG, "Failure calling onSubsystemRestarting" + e); } } mRestartCallbacks.finishBroadcast(); shutdownWifi(); // onStopped will move the state machine to "DisabledState". break; } default: return NOT_HANDLED; } return HANDLED; } } } private static T coalesce(T a, T b) { return a != null ? a : b; } /** * Check if CMM is connecting or connected to target BSSID and SSID */ public static boolean isClientModeManagerConnectedOrConnectingToBssid( @NonNull ClientModeManager clientModeManager, @NonNull String ssid, @NonNull String bssid) { WifiConfiguration connectedOrConnectingWifiConfiguration = coalesce( clientModeManager.getConnectingWifiConfiguration(), clientModeManager.getConnectedWifiConfiguration()); String connectedOrConnectingBssid = coalesce( clientModeManager.getConnectingBssid(), clientModeManager.getConnectedBssid()); String connectedOrConnectingSsid = connectedOrConnectingWifiConfiguration == null ? null : connectedOrConnectingWifiConfiguration.SSID; Log.v(TAG, connectedOrConnectingBssid + " " + connectedOrConnectingSsid); return Objects.equals(ssid, connectedOrConnectingSsid) && (Objects.equals(bssid, connectedOrConnectingBssid) || clientModeManager.isAffiliatedLinkBssid(NativeUtil.getMacAddressOrNull(bssid))); } /** * Set the current supported Wifi feature set, called from primary client mode manager. * @param supportedFeatureSet supported Wifi feature set * @param isStaApConcurrencySupported true if Sta+Ap concurrency supported * @param isStaStaConcurrencySupported true if Sta+Sta concurrency supported */ private void setSupportedFeatureSet(long supportedFeatureSet, boolean isStaApConcurrencySupported, boolean isStaStaConcurrencySupported) { long concurrencyFeatureSet = 0L; if (isStaApConcurrencySupported) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_AP_STA; } if (isStaStaConcurrencySupported) { if (mContext.getResources().getBoolean( R.bool.config_wifiMultiStaLocalOnlyConcurrencyEnabled)) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_LOCAL_ONLY; } if (mContext.getResources().getBoolean( R.bool.config_wifiMultiStaNetworkSwitchingMakeBeforeBreakEnabled)) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MBB; } if (mContext.getResources().getBoolean( R.bool.config_wifiMultiStaRestrictedConcurrencyEnabled)) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_RESTRICTED; } if (mContext.getResources().getBoolean( R.bool.config_wifiMultiStaMultiInternetConcurrencyEnabled)) { concurrencyFeatureSet |= WifiManager.WIFI_FEATURE_ADDITIONAL_STA_MULTI_INTERNET; } } long additionalFeatureSet = 0L; long excludedFeatureSet = 0L; // Mask the feature set against system properties. if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)) { // flags filled in by vendor HAL, remove if overlay disables it. excludedFeatureSet |= (WifiManager.WIFI_FEATURE_D2D_RTT | WifiManager.WIFI_FEATURE_D2AP_RTT); } if (!mContext.getResources().getBoolean( R.bool.config_wifi_p2p_mac_randomization_supported)) { // flags filled in by vendor HAL, remove if overlay disables it. excludedFeatureSet |= WifiManager.WIFI_FEATURE_P2P_RAND_MAC; } if (mContext.getResources().getBoolean( R.bool.config_wifi_connected_mac_randomization_supported)) { // no corresponding flags in vendor HAL, set if overlay enables it. additionalFeatureSet |= WifiManager.WIFI_FEATURE_CONNECTED_RAND_MAC; } if (ApConfigUtil.isApMacRandomizationSupported(mContext)) { // no corresponding flags in vendor HAL, set if overlay enables it. additionalFeatureSet |= WifiManager.WIFI_FEATURE_AP_RAND_MAC; } if (ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)) { // The bridged mode requires the kernel network modules support. // It doesn't relate the vendor HAL, set if overlay enables it. additionalFeatureSet |= WifiManager.WIFI_FEATURE_BRIDGED_AP; } if (ApConfigUtil.isStaWithBridgedModeSupported(mContext, mWifiNative)) { // The bridged mode requires the kernel network modules support. // It doesn't relate the vendor HAL, set if overlay enables it. additionalFeatureSet |= WifiManager.WIFI_FEATURE_STA_BRIDGED_AP; } if (mWifiGlobals.isWepSupported()) { additionalFeatureSet |= WifiManager.WIFI_FEATURE_WEP; } if (!mWifiGlobals.isWpaPersonalDeprecated()) { // The WPA didn't be deprecated, set it. additionalFeatureSet |= WifiManager.WIFI_FEATURE_WPA_PERSONAL; } if (mWifiGlobals.isD2dSupportedWhenInfraStaDisabled()) { additionalFeatureSet |= WifiManager.WIFI_FEATURE_D2D_WHEN_INFRA_STA_DISABLED; } mSupportedFeatureSet.set( (supportedFeatureSet | concurrencyFeatureSet | additionalFeatureSet) & ~excludedFeatureSet); if (mVerboseLoggingEnabled) { Log.d(TAG, "setSupportedFeatureSet 0x" + Long.toHexString(mSupportedFeatureSet.get())); } } /** * Get the current supported Wifi feature set. * @return supported Wifi feature set */ public long getSupportedFeatureSet() { return mSupportedFeatureSet.get(); } /** * Check if a band is supported as STA * @param band Wifi band * @return true if supported */ public boolean isBandSupportedForSta(@WifiScanner.WifiBand int band) { return (mBandsSupported.get() & band) != 0; } private void setBandSupported(@WifiScanner.WifiBand int bands) { mBandsSupported.set(bands); saveStaBandsToConfigStoreIfNecessary(bands); if (mVerboseLoggingEnabled) { Log.d(TAG, "setBandSupported 0x" + Long.toHexString(mBandsSupported.get())); } } /** * Get the current default Wifi network. * @return the default Wifi network */ public Network getCurrentNetwork() { synchronized (mServiceApiLock) { return mCurrentNetwork; } } /** * Set the current default Wifi network. Called from ClientModeImpl. * @param network the default Wifi network */ protected void setCurrentNetwork(Network network) { synchronized (mServiceApiLock) { mCurrentNetwork = network; } } /** * Get the current Wifi network connection info. * @return the default Wifi network connection info */ public @NonNull WifiInfo getConnectionInfo() { synchronized (mServiceApiLock) { return new WifiInfo(mCurrentConnectionInfo); } } /** * Update the current connection information. */ public void updateCurrentConnectionInfo() { synchronized (mServiceApiLock) { mCurrentConnectionInfo = getPrimaryClientModeManager().getConnectionInfo(); } } /** * Save the supported bands for STA from WiFi HAL to config store. * @param bands bands supported */ private void saveStaBandsToConfigStoreIfNecessary(int bands) { if (bands != getStaBandsFromConfigStore()) { mWifiInjector.getSettingsConfigStore().put(WIFI_NATIVE_SUPPORTED_STA_BANDS, bands); Log.i(TAG, "Supported STA bands is updated in config store: " + bands); } } /** * Get the supported STA bands from cache/config store * @return bands supported */ private int getStaBandsFromConfigStore() { return mWifiInjector.getSettingsConfigStore().get(WIFI_NATIVE_SUPPORTED_STA_BANDS); } /** * Save the device mobility state when it updates. If the primary client mode manager is * non-null, pass the mobility state to clientModeImpl and update the RSSI polling * interval accordingly. */ public void setDeviceMobilityState(@DeviceMobilityState int newState) { mDeviceMobilityState = newState; ClientModeManager cm = getPrimaryClientModeManagerNullable(); if (cm != null) { cm.onDeviceMobilityStateUpdated(newState); } } /** * Get the current device mobility state */ public int getDeviceMobilityState() { return mDeviceMobilityState; } @VisibleForTesting public void handleSatelliteModeChange() { mSettingsStore.updateSatelliteModeTracker(); mWifiController.sendMessage(WifiController.CMD_SATELLITE_MODE_CHANGED); } }