| /* |
| * 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.ISubsystemRestartCallback; |
| import android.net.wifi.IWifiConnectedNetworkScorer; |
| import android.net.wifi.SoftApCapability; |
| import android.net.wifi.SoftApConfiguration; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiManager; |
| 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.WorkSource; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| 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.Set; |
| 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"; |
| public static final WorkSource INTERNAL_REQUESTOR_WS = new WorkSource(Process.WIFI_UID); |
| |
| // Holder for active mode managers |
| private final Set<ConcreteClientModeManager> mClientModeManagers = new ArraySet<>(); |
| private final Set<SoftApManager> mSoftApManagers = new ArraySet<>(); |
| |
| private final Set<ModeChangeCallback> mCallbacks = new ArraySet<>(); |
| private final Set<PrimaryClientModeManagerChangedCallback> 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 WifiServiceImpl.SoftApCallbackInternal mSoftApCallback; |
| private WifiServiceImpl.SoftApCallbackInternal mLohsCallback; |
| |
| private final RemoteCallbackList<ISubsystemRestartCallback> mRestartCallbacks = |
| new RemoteCallbackList<>(); |
| |
| 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; |
| |
| @Nullable |
| private ConcreteClientModeManager mLastPrimaryClientModeManager = null; |
| |
| @Nullable |
| private WorkSource mLastPrimaryClientModeManagerRequestorWs = null; |
| @Nullable |
| private WorkSource mLastScanOnlyClientModeManagerRequestorWs = null; |
| |
| /** |
| * 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<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, |
| WifiMetrics wifiMetrics, |
| ExternalScoreUpdateObserverProxy externalScoreUpdateObserverProxy, |
| DppManager dppManager) { |
| 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(); |
| |
| 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); |
| }); |
| } |
| }); |
| |
| 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); |
| } |
| }); |
| } |
| |
| 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); |
| } |
| } |
| |
| /** |
| * 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) { |
| 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); |
| 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)); |
| } |
| |
| /** Register for primary ClientModeManager changed callbacks. */ |
| public void registerPrimaryClientModeManagerChangedCallback( |
| @NonNull PrimaryClientModeManagerChangedCallback callback) { |
| mPrimaryChangedCallbacks.add(Objects.requireNonNull(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) { |
| mPrimaryChangedCallbacks.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 canRequestMoreClientModeManagersInRole(@NonNull WorkSource requestorWs, |
| @NonNull ClientRole clientRole) { |
| if (!mWifiNative.isItPossibleToCreateStaIface(requestorWs)) { |
| 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); |
| } |
| 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 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 |
| * 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); |
| } |
| |
| /** 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) { |
| 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, |
| @Nullable String reasonDetail, boolean requestBugReport) { |
| mWifiController.sendMessage(WifiController.CMD_RECOVERY_RESTART_WIFI, reason, |
| requestBugReport ? 1 : 0, reasonDetail); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** 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. */ |
| 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; |
| |
| 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) { |
| this.listener = listener; |
| this.requestorWs = requestorWs; |
| this.clientRole = clientRole; |
| this.ssid = ssid; |
| this.bssid = bssid; |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| public void requestLocalOnlyClientModeManager( |
| @NonNull ExternalClientModeManagerRequestListener listener, |
| @NonNull WorkSource requestorWs, @NonNull String ssid, @NonNull String bssid) { |
| mWifiController.sendMessage( |
| WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, |
| new AdditionalClientModeManagerRequestInfo( |
| Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs), |
| ROLE_CLIENT_LOCAL_ONLY, ssid, bssid)); |
| } |
| |
| /** |
| * 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) { |
| mWifiController.sendMessage( |
| WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, |
| new AdditionalClientModeManagerRequestInfo( |
| Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs), |
| ROLE_CLIENT_SECONDARY_LONG_LIVED, ssid, bssid)); |
| } |
| |
| /** |
| * 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) { |
| mWifiController.sendMessage( |
| WifiController.CMD_REQUEST_ADDITIONAL_CLIENT_MODE_MANAGER, |
| new AdditionalClientModeManagerRequestInfo( |
| Objects.requireNonNull(listener), Objects.requireNonNull(requestorWs), |
| ROLE_CLIENT_SECONDARY_TRANSIENT, ssid, bssid)); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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<ClientModeManager> getInternetConnectivityClientModeManagers() { |
| List<ClientModeManager> 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<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; |
| } |
| |
| /** 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 all client mode managers in the specified roles. */ |
| @NonNull |
| public List<ConcreteClientModeManager> getClientModeManagersInRoles(ClientRole... roles) { |
| Set<ClientRole> rolesSet = Set.of(roles); |
| List<ConcreteClientModeManager> 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) { |
| 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 in scan only mode. |
| */ |
| private boolean startScanOnlyClientModeManager(WorkSource requestorWs) { |
| 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) { |
| 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; |
| } |
| } |
| |
| /** |
| * 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 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); |
| } |
| } |
| |
| /** |
| * 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); |
| 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); |
| manager.setRole(role, requestorWs, listener); |
| 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: " + 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("NumActiveModeManagers: " + getActiveModeManagerCount()); |
| 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("STA + AP Concurrency Supported: " + isStaApConcurrencySupported()); |
| mWifiInjector.getHalDeviceManager().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<SoftApManager> { |
| @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<ConcreteClientModeManager> { |
| @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; |
| } |
| } |
| |
| @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); |
| mGraveyard.inter(clientModeManager); |
| updateClientScanMode(); |
| updateBatteryStats(); |
| if (clientModeManager == mLastPrimaryClientModeManager) { |
| // CMM was primary, but was stopped |
| invokeOnPrimaryClientModeManagerChangedCallbacks( |
| mLastPrimaryClientModeManager, null); |
| mLastPrimaryClientModeManager = null; |
| } |
| // 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()); |
| } |
| |
| /** |
| * 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; |
| |
| private final EnabledState mEnabledState = new EnabledState(); |
| private final DisabledState mDisabledState = new DisabledState(); |
| |
| private boolean mIsInEmergencyCall = false; |
| private boolean mIsInEmergencyCallbackMode = false; |
| private boolean mIsEmergencyScanInProgress = 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_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"; |
| 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); |
| |
| // 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 State { |
| @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<SoftApModeConfiguration, WorkSource> softApConfigAndWs = |
| (Pair<SoftApModeConfiguration, WorkSource>) 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(WifiManager.WIFI_AP_STATE_FAILED, |
| WifiManager.SAP_START_FAILURE_GENERAL); |
| } |
| 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 final boolean processMessage(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 State { |
| @Override |
| public boolean processMessage(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.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 shouldEnableScanOnlyMode() { |
| return (mWifiPermissionsUtil.isLocationModeEnabled() |
| && mSettingsStore.isScanAlwaysAvailable()) |
| || mIsEmergencyScanInProgress; |
| } |
| |
| private boolean shouldEnableSta() { |
| return mSettingsStore.isWifiToggleEnabled() || shouldEnableScanOnlyMode(); |
| } |
| |
| private void handleStaToggleChangeInDisabledState(WorkSource requestorWs) { |
| if (shouldEnableSta()) { |
| startPrimaryOrScanOnlyClientModeManager(requestorWs); |
| transitionTo(mEnabledState); |
| } |
| } |
| |
| private void handleStaToggleChangeInEnabledState(WorkSource requestorWs) { |
| if (shouldEnableSta()) { |
| if (hasAnyClientModeManager()) { |
| switchAllPrimaryOrScanOnlyClientModeManagers(); |
| } else { |
| startPrimaryOrScanOnlyClientModeManager(requestorWs); |
| } |
| } else { |
| stopAllClientModeManagers(); |
| } |
| } |
| |
| 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: |
| 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<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"); |
| 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<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); |
| 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(); |
| 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, "Exiting EnabledState, but has active mode managers"); |
| } |
| super.exit(); |
| } |
| |
| private 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; |
| return Objects.equals(ssid, connectedOrConnectingSsid) |
| && Objects.equals(bssid, connectedOrConnectingBssid); |
| } |
| |
| @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) { |
| ClientModeManager primaryManager = getPrimaryClientModeManager(); |
| 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; |
| } |
| // Existing secondary CMM connected to the same ssid/bssid. |
| if (!canRequestMoreClientModeManagersInRole( |
| requestInfo.requestorWs, requestInfo.clientRole)) { |
| 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)) { |
| // Can create an additional client mode manager. |
| Log.v(TAG, "Starting a new ClientModeManager"); |
| startAdditionalClientModeManager( |
| requestInfo.clientRole, |
| requestInfo.listener, requestInfo.requestorWs); |
| 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<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 = (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<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)); |
| 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> T coalesce(T a, T b) { |
| return a != null ? a : b; |
| } |
| } |