blob: 89cbc24fc8b48054be2e625c53d24720125c8d23 [file] [log] [blame]
/*
* 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 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 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.location.LocationManager;
import android.net.wifi.IWifiConnectedNetworkScorer;
import android.net.wifi.SoftApCapability;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.os.BatteryStatsManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.WorkSource;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
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.State;
import com.android.internal.util.StateMachine;
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.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.concurrent.Executor;
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";
// Holder for active mode managers
private final ArraySet<ConcreteClientModeManager> mClientModeManagers = new ArraySet<>();
private final ArraySet<SoftApManager> mSoftApManagers = new ArraySet<>();
private final ArraySet<ModeChangeCallback> mCallbacks = 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 WifiManager.SoftApCallback mSoftApCallback;
private WifiManager.SoftApCallback mLohsCallback;
private boolean mIsShuttingdown = false;
private boolean mVerboseLoggingEnabled = false;
/** Cache to store the external scorer for primary and secondary (MBB) client mode manager. */
@Nullable private Pair<IBinder, IWifiConnectedNetworkScorer> mClientModeManagerScorer;
/**
* Called from WifiServiceImpl to register a callback for notifications from SoftApManager
*/
public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
mSoftApCallback = callback;
}
/**
* Called from WifiServiceImpl to register a callback for notifications from SoftApManager
* for local-only hotspot.
*/
public void registerLohsCallback(@NonNull WifiManager.SoftApCallback 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);
}
/**
* 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<ConcreteClientModeManager> mClientModeManagers =
new ArrayDeque<>();
private final ArrayDeque<SoftApManager> 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) {
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;
mWifiController = new WifiController();
mGraveyard = new Graveyard();
wifiNative.registerStatusListener(isReady -> {
if (!isReady && !mIsShuttingdown) {
mHandler.post(() -> {
Log.e(TAG, "One of the native daemons died. Triggering recovery");
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);
});
}
});
}
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);
}
}
/**
* 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) {
mClientModeManagerScorer = Pair.create(binder, scorer);
return getPrimaryClientModeManager().setWifiConnectedNetworkScorer(binder, scorer);
}
/**
* See {@link WifiManager#clearWifiConnectedNetworkScorer()}
*/
public void clearWifiConnectedNetworkScorer() {
mClientModeManagerScorer = null;
getPrimaryClientModeManager().clearWifiConnectedNetworkScorer();
}
/**
* Register for mode change callbacks.
*/
public void registerModeChangeCallback(@NonNull ModeChangeCallback callback) {
mCallbacks.add(Objects.requireNonNull(callback));
}
/**
* Unregister mode change callback.
*/
public void unregisterModeChangeCallback(@NonNull ModeChangeCallback callback) {
mCallbacks.remove(Objects.requireNonNull(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 we can create more client mode managers or not.
*/
public boolean canRequestMoreClientModeManagers(@NonNull WorkSource requestorWs) {
return mWifiNative.isItPossibleToCreateStaIface(requestorWs);
}
/**
* @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 one concurrent client mode manager &
* softap manager.
*/
public boolean isStaApConcurrencySupported() {
return mWifiNative.isStaApConcurrencySupported();
}
/**
* @return Returns whether the device can support at least two concurrent client mode managers.
*/
public boolean isStaStaConcurrencySupported() {
return mWifiNative.isStaStaConcurrencySupported();
}
/** 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(mFacade.getSettingsWorkSource(mContext));
}
}, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (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));
}
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) {
mWifiController.sendMessage(WifiController.CMD_RECOVERY_RESTART_WIFI, reason);
}
/** 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) {
mWifiController.sendMessage(WifiController.CMD_UPDATE_AP_CAPABILITY, capability);
}
/** Update SoftAp Configuration. */
public void updateSoftApConfiguration(SoftApConfiguration config) {
mWifiController.sendMessage(WifiController.CMD_UPDATE_AP_CONFIG, config);
}
/** Emergency Callback Mode has changed. */
private void emergencyCallbackModeChanged(boolean isInEmergencyCallbackMode) {
mWifiController.sendMessage(
WifiController.CMD_EMERGENCY_MODE_CHANGED, isInEmergencyCallbackMode ? 1 : 0);
}
/** Emergency Call state has changed. */
private void emergencyCallStateChanged(boolean isInEmergencyCall) {
mWifiController.sendMessage(
WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED, isInEmergencyCall ? 1 : 0);
}
/** Scan always mode has changed. */
public void scanAlwaysModeChanged(WorkSource requestorWs) {
mWifiController.sendMessage(WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED, requestorWs);
}
/**
* 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 {
public final ExternalClientModeManagerRequestListener listener;
public final WorkSource requestorWs;
public final ClientConnectivityRole clientRole;
AdditionalClientModeManagerRequestInfo(
ExternalClientModeManagerRequestListener listener, WorkSource requestorWs,
ClientConnectivityRole clientRole) {
this.listener = listener;
this.requestorWs = requestorWs;
this.clientRole = clientRole;
}
}
/**
* Request a new local only client manager.
*/
public void requestLocalOnlyClientModeManager(
@NonNull ExternalClientModeManagerRequestListener listener,
@NonNull WorkSource requestorWs) {
mWifiController.sendMessage(
WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
new AdditionalClientModeManagerRequestInfo(
Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
ROLE_CLIENT_LOCAL_ONLY));
}
/**
* Request a new secondary long lived client manager.
*/
public void requestSecondaryLongLivedClientModeManager(
@NonNull ExternalClientModeManagerRequestListener listener,
@NonNull WorkSource requestorWs) {
mWifiController.sendMessage(
WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
new AdditionalClientModeManagerRequestInfo(
Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
ROLE_CLIENT_SECONDARY_LONG_LIVED));
}
/**
* Request a new secondary transient client manager.
*/
public void requestSecondaryTransientClientModeManager(
@NonNull ExternalClientModeManagerRequestListener listener,
@NonNull WorkSource requestorWs) {
mWifiController.sendMessage(
WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER,
new AdditionalClientModeManagerRequestInfo(
Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs),
ROLE_CLIENT_SECONDARY_TRANSIENT));
}
/**
* Remove the provided client manager.
*/
public void removeClientModeManager(ClientModeManager clientModeManager) {
mWifiController.sendMessage(
WifiController.CMD_REMOVE_ADDITIONAL_CLIENT_MODE_MANAGER, clientModeManager);
}
/**
* 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 = getClientModeManagerInRole(ROLE_CLIENT_PRIMARY);
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#CLIENT_INTERNET_CONNECTIVITY_ROLES} roles.
* @return List of {@link ClientModeManager}.
*/
@NonNull
public List<ClientModeManager> getInternetConnectivityClientModeManagers() {
List<ClientModeManager> modeManagers = new ArrayList<>();
for (ConcreteClientModeManager manager : mClientModeManagers) {
if (manager.getRole() instanceof ClientInternetConnectivityRole) {
modeManagers.add(manager);
}
}
return modeManagers;
}
@NonNull
public List<ClientModeManager> 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;
}
@Nullable
private ClientModeManager getClientModeManagerInRole(ClientRole role) {
for (ConcreteClientModeManager manager : mClientModeManagers) {
if (manager.getRole() == role) return manager;
}
return null;
}
@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);
WifiManager.SoftApCallback callback =
softApConfig.getTargetMode() == IFACE_IP_MODE_LOCAL_ONLY
? mLohsCallback : mSoftApCallback;
SoftApListener listener = new SoftApListener();
SoftApManager manager = mWifiInjector.makeSoftApManager(listener, callback, softApConfig);
listener.softApManager = manager;
manager.start(requestorWs, getRoleForSoftApIpMode(softApConfig.getTargetMode()));
manager.enableVerboseLogging(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) {
for (SoftApManager softApManager : mSoftApManagers) {
softApManager.updateCapability(capability);
}
}
private void updateConfigurationToSoftApModeManager(SoftApConfiguration config) {
for (SoftApManager softApManager : mSoftApManagers) {
softApManager.updateConfiguration(config);
}
}
/**
* Method to enable a new primary client mode manager.
*/
private boolean startPrimaryOrScanOnlyClientModeManager(WorkSource requestorWs) {
Log.d(TAG, "Starting primary ClientModeManager");
ClientListener listener = new ClientListener();
ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(listener);
listener.clientModeManager = manager;
ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();
if (role == null) return false;
manager.start(requestorWs, role);
manager.enableVerboseLogging(mVerboseLoggingEnabled);
if (mClientModeManagerScorer != null) {
// TODO (b/160346062): Clear the connected scorer from this mode manager when
// we switch it out of primary role for the MBB use-case.
// Also vice versa, we need to set the scorer on the new primary mode manager.
manager.setWifiConnectedNetworkScorer(
mClientModeManagerScorer.first, mClientModeManagerScorer.second);
}
mClientModeManagers.add(manager);
return true;
}
/**
* Method to stop all client mode mangers.
*/
private void stopAllClientModeManagers() {
Log.d(TAG, "Shutting down all client mode managers");
for (ConcreteClientModeManager clientModeManager : mClientModeManagers) {
clientModeManager.stop();
}
}
/**
* 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 (checkScanOnlyModeAvailable()) {
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();
if (role == null) return false;
modeManager.setRole(role);
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);
listener.clientModeManager = manager;
manager.start(requestorWs, role);
manager.enableVerboseLogging(mVerboseLoggingEnabled);
mClientModeManagers.add(manager);
return true;
}
/**
* Method to stop client mode manger.
*/
private void stopAdditionalClientModeManager(ClientModeManager clientModeManager) {
if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY
|| clientModeManager.getRole() == ROLE_CLIENT_SCAN_ONLY) return;
Log.d(TAG, "Shutting down additional client mode manager in role:"
+ clientModeManager.getRole());
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("NumActiveModeManagers: " + getActiveModeManagerCount());
mWifiController.dump(fd, pw, args);
for (ActiveModeManager manager : getActiveModeManagers()) {
manager.dump(fd, pw, args);
}
mGraveyard.dump(fd, pw, args);
}
@VisibleForTesting
String getCurrentMode() {
IState state = mWifiController.getCurrentState();
return state == null ? STATE_MACHINE_EXITED_STATE_NAME : state.getName();
}
@VisibleForTesting
Collection<ActiveModeManager> getActiveModeManagers() {
ArrayList<ActiveModeManager> activeModeManagers = new ArrayList<>();
activeModeManagers.addAll(mClientModeManagers);
activeModeManagers.addAll(mSoftApManagers);
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 {
public SoftApManager softApManager;
@Override
public void onStarted() {
updateBatteryStats();
invokeOnAddedCallbacks(softApManager);
}
@Override
public void onRoleChanged() {
Log.w(TAG, "Role switched received on SoftApManager unexpectedly");
}
@Override
public void onStopped() {
mSoftApManagers.remove(softApManager);
mGraveyard.inter(softApManager);
updateBatteryStats();
mWifiController.sendMessage(WifiController.CMD_AP_STOPPED);
invokeOnRemovedCallbacks(softApManager);
}
@Override
public void onStartFailure() {
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 {
private final ExternalClientModeManagerRequestListener mExternalRequestListener;
public ConcreteClientModeManager clientModeManager;
ClientListener() {
this(null);
}
ClientListener(
@Nullable ExternalClientModeManagerRequestListener externalRequestListener) {
mExternalRequestListener = externalRequestListener;
}
@Override
public void onStarted() {
updateClientScanMode();
updateBatteryStats();
if (mExternalRequestListener != null) {
mExternalRequestListener.onAnswer(clientModeManager);
}
invokeOnAddedCallbacks(clientModeManager);
}
@Override
public void onRoleChanged() {
updateClientScanMode();
updateBatteryStats();
invokeOnRoleChangedCallbacks(clientModeManager);
}
@Override
public void onStopped() {
mClientModeManagers.remove(clientModeManager);
mGraveyard.inter(clientModeManager);
updateClientScanMode();
updateBatteryStats();
mWifiController.sendMessage(WifiController.CMD_STA_STOPPED);
invokeOnRemovedCallbacks(clientModeManager);
}
@Override
public void onStartFailure() {
mClientModeManagers.remove(clientModeManager);
mGraveyard.inter(clientModeManager);
updateClientScanMode();
updateBatteryStats();
mWifiController.sendMessage(WifiController.CMD_STA_START_FAILURE);
// onStartFailure can be called when switching between roles. So, remove
// update listeners.
Log.e(TAG, "ClientModeManager start failed!" + clientModeManager);
invokeOnRemovedCallbacks(clientModeManager);
}
}
// 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);
}
private boolean checkScanOnlyModeAvailable() {
return mWifiPermissionsUtil.isLocationModeEnabled()
&& mSettingsStore.isScanAlwaysAvailable();
}
/**
* 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_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;
private final EnabledState mEnabledState = new EnabledState();
private final DisabledState mDisabledState = new DisabledState();
private boolean mIsInEmergencyCall = false;
private boolean mIsInEmergencyCallbackMode = false;
WifiController() {
super(TAG, mLooper);
DefaultState defaultState = new DefaultState();
addState(defaultState); {
addState(mDisabledState, defaultState);
addState(mEnabledState, defaultState);
}
setLogRecSize(100);
setLogOnlyTransitions(false);
}
@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_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";
default:
return "what:" + what;
}
}
@Override
public void start() {
boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled();
log("isAirplaneModeOn = " + isAirplaneModeOn
+ ", isWifiEnabled = " + isWifiEnabled
+ ", isScanningAvailable = " + isScanningAlwaysAvailable
+ ", isLocationModeActive = " + isLocationModeActive);
if (shouldEnableSta()) {
// Assumes user toggled it on from settings before.
startPrimaryOrScanOnlyClientModeManager(mFacade.getSettingsWorkSource(mContext));
setInitialState(mEnabledState);
} else {
setInitialState(mDisabledState);
}
// 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 State {
@VisibleForTesting
boolean isInEmergencyMode() {
return mIsInEmergencyCall || mIsInEmergencyCallbackMode;
}
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("WifiController msg getConfigWiFiDisableInECBM " + configWiFiDisableInECBM);
if (configWiFiDisableInECBM) {
shutdownWifi();
}
}
private void exitEmergencyMode() {
if (shouldEnableSta()) {
startPrimaryOrScanOnlyClientModeManager(
// Assumes user toggled it on from settings before.
mFacade.getSettingsWorkSource(mContext));
transitionTo(mEnabledState);
} else {
transitionTo(mDisabledState);
}
}
@Override
public final boolean processMessage(Message msg) {
// potentially enter emergency mode
if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED
|| msg.what == CMD_EMERGENCY_MODE_CHANGED) {
boolean wasInEmergencyMode = isInEmergencyMode();
updateEmergencyMode(msg);
boolean isInEmergencyMode = isInEmergencyMode();
if (!wasInEmergencyMode && isInEmergencyMode) {
enterEmergencyMode();
} else if (wasInEmergencyMode && !isInEmergencyMode) {
exitEmergencyMode();
}
return HANDLED;
} else if (isInEmergencyMode()) {
// already in emergency mode, drop all messages other than mode stop messages
// triggered by emergency mode start.
if (msg.what == CMD_STA_STOPPED || msg.what == CMD_AP_STOPPED) {
if (!hasAnyModeManager()) {
log("No active mode managers, return to DisabledState.");
transitionTo(mDisabledState);
}
}
return HANDLED;
}
// not in emergency mode, process messages normally
return processMessageFiltered(msg);
}
protected abstract boolean processMessageFiltered(Message msg);
}
class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_SCAN_ALWAYS_MODE_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.isAirplaneModeOn()) {
log("Airplane mode toggled, shutdown all modes");
shutdownWifi();
// onStopped will move the state machine to "DisabledState".
} else {
log("Airplane mode disabled, determine next state");
if (shouldEnableSta()) {
startPrimaryOrScanOnlyClientModeManager(
// Assumes user toggled it on from settings before.
mFacade.getSettingsWorkSource(mContext));
transitionTo(mEnabledState);
}
// wifi should remain disabled, do not need to transition
}
break;
case CMD_UPDATE_AP_CAPABILITY:
updateCapabilityToSoftApModeManager((SoftApCapability) msg.obj);
break;
case CMD_UPDATE_AP_CONFIG:
updateConfigurationToSoftApModeManager((SoftApConfiguration) msg.obj);
break;
default:
throw new RuntimeException("WifiController.handleMessage " + msg.what);
}
return HANDLED;
}
}
private boolean shouldEnableSta() {
return mSettingsStore.isWifiToggleEnabled() || checkScanOnlyModeAvailable();
}
class DisabledState extends BaseState {
@Override
public void enter() {
log("DisabledState.enter()");
super.enter();
if (hasAnyModeManager()) {
Log.e(TAG, "Entered DisabledState, but has active mode managers");
}
}
@Override
public void exit() {
log("DisabledState.exit()");
super.exit();
}
@Override
public boolean processMessageFiltered(Message msg) {
switch (msg.what) {
case CMD_WIFI_TOGGLED:
case CMD_SCAN_ALWAYS_MODE_CHANGED:
if (shouldEnableSta()) {
startPrimaryOrScanOnlyClientModeManager((WorkSource) msg.obj);
transitionTo(mEnabledState);
}
break;
case CMD_SET_AP:
// note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
if (msg.arg1 == 1) {
Pair<SoftApModeConfiguration, WorkSource> softApConfigAndWs =
(Pair) msg.obj;
startSoftApModeManager(
softApConfigAndWs.first, softApConfigAndWs.second);
transitionTo(mEnabledState);
}
break;
case CMD_RECOVERY_RESTART_WIFI:
log("Recovery triggered, already in disabled state");
// intentional fallthrough
case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
// wait mRecoveryDelayMillis for letting driver clean reset.
sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE,
// msg.obj == null if recovery is triggered in disabled state
// (i.e intentional fallthrough from above case statement).
msg.obj == null ? Collections.emptyList() : msg.obj,
readWifiRecoveryDelay());
break;
case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
log("Recovery in progress, start wifi");
List<ActiveModeManager> 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);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class EnabledState extends BaseState {
private boolean mIsDisablingDueToAirplaneMode;
@Override
public void enter() {
log("EnabledState.enter()");
super.enter();
if (!hasAnyModeManager()) {
Log.e(TAG, "Entered EnabledState, but no active mode managers");
}
mIsDisablingDueToAirplaneMode = false;
}
@Override
public void exit() {
log("EnabledState.exit()");
if (hasAnyModeManager()) {
Log.e(TAG, "Existing EnabledState, but has active mode managers");
}
super.exit();
}
@Override
public boolean processMessageFiltered(Message msg) {
switch (msg.what) {
case CMD_WIFI_TOGGLED:
case CMD_SCAN_ALWAYS_MODE_CHANGED:
if (shouldEnableSta()) {
if (hasAnyClientModeManager()) {
switchAllPrimaryOrScanOnlyClientModeManagers();
} else {
startPrimaryOrScanOnlyClientModeManager((WorkSource) msg.obj);
}
} else {
stopAllClientModeManagers();
}
break;
case CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER:
AdditionalClientModeManagerRequestInfo requestInfo =
(AdditionalClientModeManagerRequestInfo) msg.obj;
if (canRequestMoreClientModeManagers(requestInfo.requestorWs)) {
// Can create an additional client mode manager.
startAdditionalClientModeManager(
requestInfo.clientRole,
requestInfo.listener, requestInfo.requestorWs);
} else {
requestInfo.listener.onAnswer(getPrimaryClientModeManager());
}
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<SoftApModeConfiguration, WorkSource> 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 {
// 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_AP_STOPPED:
case CMD_AP_START_FAILURE:
if (!hasAnyModeManager()) {
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);
}
} else {
log("AP disabled, remain in EnabledState.");
}
break;
case CMD_STA_START_FAILURE:
case CMD_STA_STOPPED:
// Client mode stopped. Head to Disabled to wait for next command if there
// no active mode managers.
if (!hasAnyModeManager()) {
log("STA disabled, return to DisabledState.");
transitionTo(mDisabledState);
} else {
log("STA disabled, remain in EnabledState.");
}
break;
case CMD_RECOVERY_RESTART_WIFI:
final String bugTitle;
final String bugDetail;
if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
bugTitle = "Wi-Fi BugReport: " + bugDetail;
} else {
bugDetail = "";
bugTitle = "Wi-Fi BugReport";
}
log("Recovery triggered, disable wifi");
if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
mHandler.post(() ->
mWifiDiagnostics.takeBugReport(bugTitle, bugDetail));
}
// Store all instances of tethered SAP + scan only/primary STA mode managers
List<ActiveModeManager> 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));
shutdownWifi();
// onStopped will move the state machine to "DisabledState".
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
}
}