1469 lines
58 KiB
Java
1469 lines
58 KiB
Java
/*
|
|
* 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.WIFI_STATE_ENABLED;
|
|
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.ConnectivityManager.NetworkCallback;
|
|
import android.net.DhcpResultsParcelable;
|
|
import android.net.Network;
|
|
import android.net.NetworkCapabilities;
|
|
import android.net.NetworkRequest;
|
|
import android.net.wifi.IWifiConnectedNetworkScorer;
|
|
import android.net.wifi.WifiAnnotations;
|
|
import android.net.wifi.WifiConfiguration;
|
|
import android.net.wifi.WifiInfo;
|
|
import android.net.wifi.WifiManager;
|
|
import android.net.wifi.hotspot2.IProvisioningCallback;
|
|
import android.net.wifi.hotspot2.OsuProvider;
|
|
import android.net.wifi.nl80211.DeviceWiphyCapabilities;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.PersistableBundle;
|
|
import android.os.UserHandle;
|
|
import android.os.WorkSource;
|
|
import android.telephony.AccessNetworkConstants;
|
|
import android.telephony.CarrierConfigManager;
|
|
import android.telephony.SubscriptionInfo;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.telephony.ims.ImsException;
|
|
import android.telephony.ims.ImsMmTelManager;
|
|
import android.telephony.ims.ImsReasonInfo;
|
|
import android.telephony.ims.RegistrationManager;
|
|
import android.telephony.ims.feature.MmTelFeature;
|
|
import android.telephony.ims.stub.ImsRegistrationImplBase;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.util.IState;
|
|
import com.android.internal.util.State;
|
|
import com.android.internal.util.StateMachine;
|
|
import com.android.modules.utils.HandlerExecutor;
|
|
import com.android.server.wifi.WifiNative.InterfaceCallback;
|
|
import com.android.server.wifi.WifiNative.RxFateReport;
|
|
import com.android.server.wifi.WifiNative.TxFateReport;
|
|
import com.android.server.wifi.util.ActionListenerWrapper;
|
|
import com.android.server.wifi.util.StateMachineObituary;
|
|
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.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Manage WiFi in Client Mode where we connect to configured networks and in Scan Only Mode where
|
|
* we do not connect to configured networks but do perform scanning.
|
|
*
|
|
* An instance of this class is active to manage each client interface. This is in contrast to
|
|
* {@link DefaultClientModeManager} which handles calls when no client interfaces are active.
|
|
*
|
|
* This class will dynamically instantiate {@link ClientModeImpl} when it enters client mode, and
|
|
* tear it down when it exits client mode. No instance of ClientModeImpl will be active in
|
|
* scan-only mode, instead {@link ScanOnlyModeImpl} will be used to respond to calls.
|
|
*
|
|
* <pre>
|
|
* ActiveModeWarden
|
|
* / \
|
|
* / \
|
|
* ConcreteClientModeManager DefaultClientModeManager
|
|
* (Client Mode + Scan Only Mode) (Wifi off)
|
|
* / \
|
|
* / \
|
|
* ClientModeImpl ScanOnlyModeImpl
|
|
* </pre>
|
|
*/
|
|
public class ConcreteClientModeManager implements ClientModeManager {
|
|
private static final String TAG = "WifiClientModeManager";
|
|
|
|
private final ClientModeStateMachine mStateMachine;
|
|
|
|
private final Context mContext;
|
|
private final Clock mClock;
|
|
private final WifiNative mWifiNative;
|
|
private final WifiMetrics mWifiMetrics;
|
|
private final WakeupController mWakeupController;
|
|
private final WifiInjector mWifiInjector;
|
|
private final SelfRecovery mSelfRecovery;
|
|
private final WifiGlobals mWifiGlobals;
|
|
private final DefaultClientModeManager mDefaultClientModeManager;
|
|
private final ClientModeManagerBroadcastQueue mBroadcastQueue;
|
|
private final long mId;
|
|
private final Graveyard mGraveyard = new Graveyard();
|
|
|
|
private String mClientInterfaceName;
|
|
private boolean mIfaceIsUp = false;
|
|
private boolean mShouldReduceNetworkScore = false;
|
|
private final DeferStopHandler mDeferStopHandler;
|
|
@Nullable
|
|
private ClientRole mRole = null;
|
|
@Nullable
|
|
private ClientRole mPreviousRole = null;
|
|
private long mLastRoleChangeSinceBootMs = 0;
|
|
@Nullable
|
|
private WorkSource mRequestorWs = null;
|
|
@NonNull
|
|
private Listener<ConcreteClientModeManager> mModeListener;
|
|
/** Caches the latest role change request. This is needed for the IMS dereg delay */
|
|
@Nullable
|
|
private RoleChangeInfo mTargetRoleChangeInfo;
|
|
private boolean mVerboseLoggingEnabled = false;
|
|
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
|
private boolean mWifiStateChangeBroadcastEnabled = true;
|
|
private boolean mSecondaryInternet = false;
|
|
private boolean mIsDbs = false;
|
|
/**
|
|
* mClientModeImpl is only non-null when in {@link ClientModeStateMachine.ConnectModeState} -
|
|
* it will be null in all other states
|
|
*/
|
|
@Nullable
|
|
private ClientModeImpl mClientModeImpl = null;
|
|
|
|
@Nullable
|
|
private ScanOnlyModeImpl mScanOnlyModeImpl = null;
|
|
|
|
private boolean mIsStopped = true;
|
|
|
|
ConcreteClientModeManager(
|
|
Context context, @NonNull Looper looper, Clock clock,
|
|
WifiNative wifiNative, @NonNull Listener<ConcreteClientModeManager> listener,
|
|
WifiMetrics wifiMetrics,
|
|
WakeupController wakeupController, WifiInjector wifiInjector,
|
|
SelfRecovery selfRecovery, WifiGlobals wifiGlobals,
|
|
DefaultClientModeManager defaultClientModeManager, long id,
|
|
@NonNull WorkSource requestorWs, @NonNull ClientRole role,
|
|
@NonNull ClientModeManagerBroadcastQueue broadcastQueue,
|
|
boolean verboseLoggingEnabled) {
|
|
mContext = context;
|
|
mClock = clock;
|
|
mWifiNative = wifiNative;
|
|
mModeListener = listener;
|
|
mWifiMetrics = wifiMetrics;
|
|
mWakeupController = wakeupController;
|
|
mWifiInjector = wifiInjector;
|
|
mStateMachine = new ClientModeStateMachine(looper);
|
|
mDeferStopHandler = new DeferStopHandler(looper);
|
|
mSelfRecovery = selfRecovery;
|
|
mWifiGlobals = wifiGlobals;
|
|
mDefaultClientModeManager = defaultClientModeManager;
|
|
mId = id;
|
|
mTargetRoleChangeInfo = new RoleChangeInfo(role, requestorWs, listener);
|
|
mBroadcastQueue = broadcastQueue;
|
|
enableVerboseLogging(verboseLoggingEnabled);
|
|
mStateMachine.sendMessage(ClientModeStateMachine.CMD_START, mTargetRoleChangeInfo);
|
|
}
|
|
|
|
private String getTag() {
|
|
return TAG + "[" + (mClientInterfaceName == null ? "unknown" : mClientInterfaceName) + "]";
|
|
}
|
|
|
|
/**
|
|
* Sets whether to send WIFI_STATE_CHANGED broadcast for this ClientModeManager.
|
|
* @param enabled
|
|
*/
|
|
public void setWifiStateChangeBroadcastEnabled(boolean enabled) {
|
|
mWifiStateChangeBroadcastEnabled = enabled;
|
|
}
|
|
|
|
/**
|
|
* Sets whether this ClientModeManager is for secondary STA with internet.
|
|
* @param secondaryInternet whether the ClientModeManager is for secondary internet.
|
|
*/
|
|
public void setSecondaryInternet(boolean secondaryInternet) {
|
|
// TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
|
|
if (mRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
|
|
mSecondaryInternet = secondaryInternet;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets whether this ClientModeManager is for DBS AP multi internet.
|
|
* @param isDbs whether the ClientModeManager is connecting to to the same SSID as primary.
|
|
*/
|
|
public void setSecondaryInternetDbsAp(boolean isDbs) {
|
|
// TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
|
|
if (mRole == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
|
|
mIsDbs = isDbs;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether this ClientModeManager is for secondary STA with internet.
|
|
* @return true if it is for secondary STA with internet.
|
|
*/
|
|
public boolean isSecondaryInternet() {
|
|
return mSecondaryInternet;
|
|
}
|
|
|
|
/**
|
|
* Returns whether this ClientModeManager is for DBS AP multi internet.
|
|
* @return true if the ClientModeManager is connecting to to the same SSID as primary.
|
|
*/
|
|
public boolean isSecondaryInternetDbsAp() {
|
|
if (!isSecondaryInternet()) {
|
|
Log.wtf(TAG, "isSecondaryInternetDbsAp called while not secondary internet!?");
|
|
(new Throwable()).printStackTrace();
|
|
}
|
|
return mIsDbs;
|
|
}
|
|
|
|
/**
|
|
* Disconnect from any currently connected networks and stop client mode.
|
|
*/
|
|
@Override
|
|
public void stop() {
|
|
Log.d(getTag(), " currentstate: " + getCurrentStateName());
|
|
mTargetRoleChangeInfo = null;
|
|
if (mIfaceIsUp) {
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
|
|
WifiManager.WIFI_STATE_ENABLED);
|
|
} else {
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
|
|
WifiManager.WIFI_STATE_ENABLING);
|
|
}
|
|
mDeferStopHandler.start(getWifiOffDeferringTimeMs());
|
|
}
|
|
|
|
private class DeferStopHandler extends Handler {
|
|
private boolean mIsDeferring = false;
|
|
private ImsMmTelManager mImsMmTelManager = null;
|
|
private Looper mLooper = null;
|
|
private final Runnable mRunnable = () -> continueToStopWifi();
|
|
private int mMaximumDeferringTimeMillis = 0;
|
|
private long mDeferringStartTimeMillis = 0;
|
|
private ConnectivityManager mConnectivityManager = null;
|
|
private List<ImsNetworkCallback> mImsNetworks = new ArrayList<>();
|
|
private boolean mIsImsNetworkUnregistered = false;
|
|
|
|
private final RegistrationManager.RegistrationCallback mImsRegistrationCallback =
|
|
new RegistrationManager.RegistrationCallback() {
|
|
@Override
|
|
public void onRegistered(int imsRadioTech) {
|
|
Log.d(getTag(), "on IMS registered on type " + imsRadioTech);
|
|
if (!mIsDeferring) return;
|
|
|
|
if (imsRadioTech != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
|
|
continueToStopWifi();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onUnregistered(ImsReasonInfo imsReasonInfo) {
|
|
Log.d(getTag(), "on IMS unregistered");
|
|
mIsImsNetworkUnregistered = true;
|
|
checkAndContinueToStopWifi();
|
|
}
|
|
};
|
|
|
|
private final class ImsNetworkCallback extends NetworkCallback {
|
|
private final int mNetworkType;
|
|
private int mRegisteredImsNetworkCount = 0;
|
|
|
|
/**
|
|
* Constructor for ImsNetworkCallback.
|
|
*
|
|
* @param type One of android.net.NetworkCapabilities.NetCapability.
|
|
*/
|
|
ImsNetworkCallback(int type) {
|
|
mNetworkType = type;
|
|
}
|
|
|
|
@Override
|
|
public void onAvailable(Network network) {
|
|
synchronized (this) {
|
|
Log.d(getTag(), "IMS network available: " + network
|
|
+ ", type: " + mNetworkType);
|
|
mRegisteredImsNetworkCount++;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onLost(Network network) {
|
|
synchronized (this) {
|
|
Log.d(getTag(), "IMS network lost: " + network
|
|
+ " ,isDeferring: " + mIsDeferring
|
|
+ " ,registered IMS network count: " + mRegisteredImsNetworkCount
|
|
+ ", type: " + mNetworkType);
|
|
mRegisteredImsNetworkCount--;
|
|
if (mIsDeferring && mRegisteredImsNetworkCount <= 0) {
|
|
mRegisteredImsNetworkCount = 0;
|
|
checkAndContinueToStopWifi();
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isNetworkLost() {
|
|
return 0 == mRegisteredImsNetworkCount;
|
|
}
|
|
}
|
|
|
|
DeferStopHandler(Looper looper) {
|
|
super(looper);
|
|
mLooper = looper;
|
|
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
|
}
|
|
|
|
public void start(int delayMs) {
|
|
if (mIsDeferring) return;
|
|
|
|
mMaximumDeferringTimeMillis = delayMs;
|
|
mDeferringStartTimeMillis = mClock.getElapsedSinceBootMillis();
|
|
// Most cases don't need delay, check it first to avoid unnecessary work.
|
|
if (delayMs == 0) {
|
|
continueToStopWifi();
|
|
return;
|
|
}
|
|
|
|
mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(mActiveSubId);
|
|
if (mImsMmTelManager == null || !postDelayed(mRunnable, delayMs)) {
|
|
// if no delay or failed to add runnable, stop Wifi immediately.
|
|
continueToStopWifi();
|
|
return;
|
|
}
|
|
|
|
mIsDeferring = true;
|
|
Log.d(getTag(), "Start DeferWifiOff handler with deferring time "
|
|
+ delayMs + " ms for subId: " + mActiveSubId);
|
|
try {
|
|
mImsMmTelManager.registerImsRegistrationCallback(
|
|
new HandlerExecutor(new Handler(mLooper)),
|
|
mImsRegistrationCallback);
|
|
} catch (RuntimeException | ImsException e) {
|
|
Log.e(getTag(), "registerImsRegistrationCallback failed", e);
|
|
continueToStopWifi();
|
|
return;
|
|
}
|
|
|
|
registerImsNetworkCallback(NetworkCapabilities.NET_CAPABILITY_IMS);
|
|
registerImsNetworkCallback(NetworkCapabilities.NET_CAPABILITY_EIMS);
|
|
}
|
|
|
|
private void registerImsNetworkCallback(int imsType) {
|
|
NetworkRequest imsRequest = new NetworkRequest.Builder()
|
|
.addCapability(imsType)
|
|
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
|
.build();
|
|
ImsNetworkCallback imsCallback = new ImsNetworkCallback(imsType);
|
|
mConnectivityManager.registerNetworkCallback(imsRequest, imsCallback,
|
|
new Handler(mLooper));
|
|
mImsNetworks.add(imsCallback);
|
|
}
|
|
|
|
private void checkAndContinueToStopWifi() {
|
|
if (!mIsImsNetworkUnregistered) return;
|
|
|
|
for (ImsNetworkCallback c: mImsNetworks) {
|
|
if (!c.isNetworkLost()) return;
|
|
}
|
|
|
|
// Add delay for targets where IMS PDN down at modem takes additional delay.
|
|
int delay = mContext.getResources()
|
|
.getInteger(R.integer.config_wifiDelayDisconnectOnImsLostMs);
|
|
if (delay == 0 || !postDelayed(mRunnable, delay)) {
|
|
continueToStopWifi();
|
|
}
|
|
}
|
|
|
|
private void continueToStopWifi() {
|
|
Log.d(getTag(), "The target role change info " + mTargetRoleChangeInfo);
|
|
|
|
int deferringDurationMillis =
|
|
(int) (mClock.getElapsedSinceBootMillis() - mDeferringStartTimeMillis);
|
|
boolean isTimedOut = mMaximumDeferringTimeMillis > 0
|
|
&& deferringDurationMillis >= mMaximumDeferringTimeMillis;
|
|
if (mTargetRoleChangeInfo == null) {
|
|
Log.d(getTag(), "Continue to stop wifi");
|
|
mStateMachine.captureObituaryAndQuitNow();
|
|
mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
|
|
} else if (mTargetRoleChangeInfo.role == ROLE_CLIENT_SCAN_ONLY) {
|
|
if (!mWifiNative.switchClientInterfaceToScanMode(
|
|
mClientInterfaceName, mTargetRoleChangeInfo.requestorWs)) {
|
|
mModeListener.onStartFailure(ConcreteClientModeManager.this);
|
|
} else {
|
|
mStateMachine.sendMessage(
|
|
ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE,
|
|
mTargetRoleChangeInfo);
|
|
mWifiMetrics.noteWifiOff(mIsDeferring, isTimedOut, deferringDurationMillis);
|
|
}
|
|
} else {
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_ENABLED,
|
|
WifiManager.WIFI_STATE_DISABLING);
|
|
}
|
|
|
|
if (!mIsDeferring) return;
|
|
|
|
Log.d(getTag(), "Stop DeferWifiOff handler.");
|
|
removeCallbacks(mRunnable);
|
|
if (mImsMmTelManager != null) {
|
|
try {
|
|
mImsMmTelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
|
|
} catch (RuntimeException e) {
|
|
Log.e(getTag(), "unregisterImsRegistrationCallback failed", e);
|
|
}
|
|
}
|
|
|
|
if (mConnectivityManager != null && mImsNetworks.size() > 0) {
|
|
for (ImsNetworkCallback c: mImsNetworks) {
|
|
mConnectivityManager.unregisterNetworkCallback(c);
|
|
}
|
|
mImsNetworks.clear();
|
|
}
|
|
|
|
mIsDeferring = false;
|
|
mIsImsNetworkUnregistered = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get deferring time before turning off WiFi.
|
|
*/
|
|
private int getWifiOffDeferringTimeMs() {
|
|
SubscriptionManager subscriptionManager =
|
|
mContext.getSystemService(SubscriptionManager.class);
|
|
if (subscriptionManager == null) {
|
|
Log.d(getTag(), "SubscriptionManager not found");
|
|
return 0;
|
|
}
|
|
|
|
List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
|
|
if (subInfoList == null) {
|
|
Log.d(getTag(), "Active SubscriptionInfo list not found");
|
|
return 0;
|
|
}
|
|
|
|
// Get the maximum delay for the active subscription latched on IWLAN.
|
|
int maxDelay = 0;
|
|
for (SubscriptionInfo subInfo : subInfoList) {
|
|
int curDelay = getWifiOffDeferringTimeMs(subInfo.getSubscriptionId());
|
|
if (curDelay > maxDelay) {
|
|
maxDelay = curDelay;
|
|
mActiveSubId = subInfo.getSubscriptionId();
|
|
}
|
|
}
|
|
return maxDelay;
|
|
}
|
|
|
|
private int getWifiOffDeferringTimeMs(int subId) {
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
Log.d(getTag(), "Invalid Subscription ID: " + subId);
|
|
return 0;
|
|
}
|
|
|
|
ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
|
|
// If no wifi calling, no delay
|
|
try {
|
|
if (!imsMmTelManager.isAvailable(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)) {
|
|
Log.d(getTag(), "IMS not registered over IWLAN for subId: " + subId);
|
|
return 0;
|
|
}
|
|
} catch (RuntimeException ex) {
|
|
Log.e(TAG, "IMS Manager is not available.", ex);
|
|
return 0;
|
|
}
|
|
|
|
CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
|
|
PersistableBundle config = configManager.getConfigForSubId(subId);
|
|
return (config != null)
|
|
? config.getInt(CarrierConfigManager.Ims.KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT)
|
|
: 0;
|
|
}
|
|
|
|
@Override
|
|
@Nullable public ClientRole getRole() {
|
|
return mRole;
|
|
}
|
|
|
|
/**
|
|
* Get the role this ClientModeManager is expected to become.
|
|
*/
|
|
@Nullable public ClientRole getTargetRole() {
|
|
return mTargetRoleChangeInfo == null ? null : mTargetRoleChangeInfo.role;
|
|
}
|
|
|
|
@Override
|
|
@Nullable public ClientRole getPreviousRole() {
|
|
return mPreviousRole;
|
|
}
|
|
|
|
@Override
|
|
public long getLastRoleChangeSinceBootMs() {
|
|
return mLastRoleChangeSinceBootMs;
|
|
}
|
|
|
|
/**
|
|
* Class to hold info needed for role change.
|
|
*/
|
|
private static class RoleChangeInfo {
|
|
@Nullable public final ClientRole role;
|
|
@Nullable public final WorkSource requestorWs;
|
|
@Nullable public final Listener<ConcreteClientModeManager> modeListener;
|
|
|
|
RoleChangeInfo(@Nullable ClientRole role) {
|
|
this(role, null, null);
|
|
}
|
|
|
|
RoleChangeInfo(@Nullable ClientRole role, @Nullable WorkSource requestorWs,
|
|
@Nullable Listener<ConcreteClientModeManager> modeListener) {
|
|
this.role = role;
|
|
this.requestorWs = requestorWs;
|
|
this.modeListener = modeListener;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Role: " + role + ", RequestorWs: " + requestorWs
|
|
+ ", ModeListener: " + modeListener;
|
|
}
|
|
}
|
|
|
|
/** Set the role of this ClientModeManager */
|
|
public void setRole(@NonNull ClientRole role, @NonNull WorkSource requestorWs) {
|
|
setRole(role, requestorWs, null);
|
|
}
|
|
|
|
/** Set the role of this ClientModeManager */
|
|
public void setRole(@NonNull ClientRole role, @NonNull WorkSource requestorWs,
|
|
@Nullable Listener<ConcreteClientModeManager> modeListener) {
|
|
mTargetRoleChangeInfo = new RoleChangeInfo(role, requestorWs, modeListener);
|
|
if (role == ROLE_CLIENT_SCAN_ONLY) {
|
|
// Switch client mode manager to scan only mode.
|
|
mStateMachine.sendMessage(
|
|
ClientModeStateMachine.CMD_SWITCH_TO_SCAN_ONLY_MODE);
|
|
} else {
|
|
// Switch client mode manager to connect mode.
|
|
mStateMachine.sendMessage(
|
|
ClientModeStateMachine.CMD_SWITCH_TO_CONNECT_MODE,
|
|
mTargetRoleChangeInfo);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getInterfaceName() {
|
|
return mClientInterfaceName;
|
|
}
|
|
|
|
@Override
|
|
public WorkSource getRequestorWs() {
|
|
return mRequestorWs;
|
|
}
|
|
|
|
/**
|
|
* Keep stopped {@link ClientModeImpl} instances so that they can be dumped to aid debugging.
|
|
*
|
|
* TODO(b/160283853): Find a smarter way to evict old ClientModeImpls
|
|
*/
|
|
private static class Graveyard {
|
|
private static final int INSTANCES_TO_KEEP = 3;
|
|
|
|
private final ArrayDeque<ClientModeImpl> mClientModeImpls = new ArrayDeque<>();
|
|
|
|
/**
|
|
* Add this stopped {@link ClientModeImpl} to the graveyard, and evict the oldest
|
|
* ClientModeImpl if the graveyard is full.
|
|
*/
|
|
void inter(ClientModeImpl clientModeImpl) {
|
|
if (mClientModeImpls.size() == INSTANCES_TO_KEEP) {
|
|
mClientModeImpls.removeFirst();
|
|
}
|
|
mClientModeImpls.addLast(clientModeImpl);
|
|
}
|
|
|
|
/** Dump the contents of the graveyard. */
|
|
void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
pw.println("Dump of ConcreteClientModeManager.Graveyard");
|
|
pw.println("Stopped ClientModeImpls: " + mClientModeImpls.size() + " total");
|
|
for (ClientModeImpl clientModeImpl : mClientModeImpls) {
|
|
clientModeImpl.dump(fd, pw, args);
|
|
}
|
|
pw.println();
|
|
}
|
|
|
|
boolean hasAllClientModeImplsQuit() {
|
|
for (ClientModeImpl cmi : mClientModeImpls) {
|
|
if (!cmi.hasQuit()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dump info about this ClientMode manager.
|
|
*/
|
|
@Override
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
pw.println("Dump of ClientModeManager id=" + mId);
|
|
pw.println("current StateMachine mode: " + getCurrentStateName());
|
|
pw.println("mRole: " + mRole);
|
|
pw.println("mPreviousRole: " + mPreviousRole);
|
|
pw.println("mTargetRoleChangeInfo: " + mTargetRoleChangeInfo);
|
|
pw.println("mClientInterfaceName: " + mClientInterfaceName);
|
|
pw.println("mIfaceIsUp: " + mIfaceIsUp);
|
|
pw.println("mSecondaryInternet: " + mSecondaryInternet);
|
|
pw.println("mIsDbs: " + mIsDbs);
|
|
mStateMachine.dump(fd, pw, args);
|
|
pw.println();
|
|
if (mClientModeImpl == null) {
|
|
pw.println("No active ClientModeImpl instance");
|
|
} else {
|
|
mClientModeImpl.dump(fd, pw, args);
|
|
}
|
|
mGraveyard.dump(fd, pw, args);
|
|
pw.println();
|
|
}
|
|
|
|
private String getCurrentStateName() {
|
|
IState currentState = mStateMachine.getCurrentState();
|
|
|
|
if (currentState != null) {
|
|
return currentState.getName();
|
|
}
|
|
|
|
return "StateMachine not active";
|
|
}
|
|
|
|
/**
|
|
* Update Wifi state and send the broadcast.
|
|
*
|
|
* @param role Target/Set role for this client mode manager instance.
|
|
* @param newState new Wifi state
|
|
* @param currentState current wifi state
|
|
*/
|
|
private void updateConnectModeState(ClientRole role, int newState, int currentState) {
|
|
if (role != ROLE_CLIENT_PRIMARY || !mWifiStateChangeBroadcastEnabled) {
|
|
// do not raise public broadcast unless this is the primary client mode manager
|
|
return;
|
|
}
|
|
// TODO(b/186881160): May need to restore per STA state for Battery state reported.
|
|
mWifiInjector.getActiveModeWarden().setWifiStateForApiCalls(newState);
|
|
if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
|
|
// do not need to broadcast failure to system
|
|
return;
|
|
}
|
|
|
|
// TODO(b/175839153): this broadcast should only be sent out when wifi is toggled on/off,
|
|
// not per CMM
|
|
final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
|
|
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
|
intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
|
|
intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
|
|
String summary = "broadcast=WIFI_STATE_CHANGED_ACTION"
|
|
+ " EXTRA_WIFI_STATE=" + newState
|
|
+ " EXTRA_PREVIOUS_WIFI_STATE=" + currentState;
|
|
if (mVerboseLoggingEnabled) Log.d(getTag(), "Queuing " + summary);
|
|
ClientModeManagerBroadcastQueue.QueuedBroadcast broadcast =
|
|
() -> {
|
|
if (mVerboseLoggingEnabled) Log.d(getTag(), "Sending " + summary);
|
|
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
|
|
};
|
|
if (mRole == null && role == ROLE_CLIENT_PRIMARY) {
|
|
// This CMM is intended to be the primary, but has not completed the mode transition
|
|
// yet. Need to force broadcast to be sent.
|
|
broadcast.send();
|
|
} else {
|
|
mBroadcastQueue.queueOrSendBroadcast(this, broadcast);
|
|
}
|
|
}
|
|
|
|
private class ClientModeStateMachine extends StateMachine {
|
|
// Commands for the state machine.
|
|
public static final int CMD_START = 0;
|
|
public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE = 1;
|
|
public static final int CMD_SWITCH_TO_CONNECT_MODE = 2;
|
|
public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
|
|
public static final int CMD_INTERFACE_DESTROYED = 4;
|
|
public static final int CMD_INTERFACE_DOWN = 5;
|
|
public static final int CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE = 6;
|
|
private final State mIdleState = new IdleState();
|
|
private final State mStartedState = new StartedState();
|
|
private final State mScanOnlyModeState = new ScanOnlyModeState();
|
|
private final State mConnectModeState = new ConnectModeState();
|
|
// Workaround since we cannot use transitionTo(mScanOnlyModeState, RoleChangeInfo)
|
|
private RoleChangeInfo mScanRoleChangeInfoToSetOnTransition = null;
|
|
// Workaround since we cannot use transitionTo(mConnectModeState, RoleChangeInfo)
|
|
private RoleChangeInfo mConnectRoleChangeInfoToSetOnTransition = null;
|
|
|
|
@Nullable
|
|
private StateMachineObituary mObituary = null;
|
|
|
|
private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
|
|
@Override
|
|
public void onDestroyed(String ifaceName) {
|
|
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
|
|
Log.d(getTag(), "STA iface " + ifaceName + " was destroyed, "
|
|
+ "stopping client mode");
|
|
|
|
// we must immediately clean up state in ClientModeImpl to unregister
|
|
// all client mode related objects
|
|
// Note: onDestroyed is only called from the main Wifi thread
|
|
if (mClientModeImpl == null) {
|
|
Log.w(getTag(), "Received mWifiNativeInterfaceCallback.onDestroyed "
|
|
+ "callback when no ClientModeImpl instance is active.");
|
|
} else {
|
|
mClientModeImpl.handleIfaceDestroyed();
|
|
}
|
|
|
|
sendMessage(CMD_INTERFACE_DESTROYED);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onUp(String ifaceName) {
|
|
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
|
|
sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDown(String ifaceName) {
|
|
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
|
|
sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
ClientModeStateMachine(Looper looper) {
|
|
super(TAG, looper);
|
|
|
|
// CHECKSTYLE:OFF IndentationCheck
|
|
addState(mIdleState);
|
|
addState(mStartedState, mIdleState);
|
|
addState(mScanOnlyModeState, mStartedState);
|
|
addState(mConnectModeState, mStartedState);
|
|
// CHECKSTYLE:ON IndentationCheck
|
|
|
|
setInitialState(mIdleState);
|
|
start();
|
|
}
|
|
|
|
void captureObituaryAndQuitNow() {
|
|
// capture StateMachine LogRecs since we will lose them after we call quitNow()
|
|
// This is used for debugging.
|
|
mObituary = new StateMachineObituary(this);
|
|
|
|
quitNow();
|
|
}
|
|
|
|
@Override
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
if (mObituary == null) {
|
|
// StateMachine hasn't quit yet, dump `this` via StateMachineObituary's dump()
|
|
// method for consistency with `else` branch.
|
|
new StateMachineObituary(this).dump(fd, pw, args);
|
|
} else {
|
|
// StateMachine has quit and cleared all LogRecs.
|
|
// Get them from the obituary instead.
|
|
mObituary.dump(fd, pw, args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset this ConcreteClientModeManager when its role changes, so that it can be reused for
|
|
* another purpose.
|
|
*/
|
|
private void reset() {
|
|
// Therefore, the caller must ensure that the role change has been completed and these
|
|
// settings have already reset before setting them, otherwise the new setting would be
|
|
// lost.
|
|
setShouldReduceNetworkScore(false);
|
|
}
|
|
|
|
private void setRoleInternal(@NonNull RoleChangeInfo roleChangeInfo) {
|
|
mPreviousRole = mRole;
|
|
mLastRoleChangeSinceBootMs = mClock.getElapsedSinceBootMillis();
|
|
mRole = roleChangeInfo.role;
|
|
if (roleChangeInfo.requestorWs != null) {
|
|
mRequestorWs = roleChangeInfo.requestorWs;
|
|
}
|
|
if (roleChangeInfo.modeListener != null) {
|
|
mModeListener = roleChangeInfo.modeListener;
|
|
}
|
|
}
|
|
|
|
private void setRoleInternalAndInvokeCallback(@NonNull RoleChangeInfo roleChangeInfo) {
|
|
if (roleChangeInfo.role == mRole) return;
|
|
if (mRole == null) {
|
|
Log.v(getTag(), "ClientModeManager started in role: " + roleChangeInfo);
|
|
setRoleInternal(roleChangeInfo);
|
|
mModeListener.onStarted(ConcreteClientModeManager.this);
|
|
} else {
|
|
Log.v(getTag(), "ClientModeManager role changed: " + roleChangeInfo);
|
|
setRoleInternal(roleChangeInfo);
|
|
reset();
|
|
mModeListener.onRoleChanged(ConcreteClientModeManager.this);
|
|
}
|
|
if (mClientModeImpl != null) {
|
|
mClientModeImpl.onRoleChanged();
|
|
}
|
|
}
|
|
|
|
private class IdleState extends State {
|
|
@Override
|
|
public void enter() {
|
|
Log.d(getTag(), "entering IdleState");
|
|
mClientInterfaceName = null;
|
|
mIfaceIsUp = false;
|
|
}
|
|
|
|
@Override
|
|
public void exit() {
|
|
// Sometimes the wifi handler thread may become blocked that the statemachine
|
|
// will exit in the IdleState without first entering StartedState. Trigger a
|
|
// cleanup here in case the above sequence happens. This the statemachine was
|
|
// started normally this will will not send a duplicate broadcast since mIsStopped
|
|
// will get set to false the first time the exit happens.
|
|
cleanupOnQuitIfApplicable();
|
|
}
|
|
|
|
@Override
|
|
public boolean processMessage(Message message) {
|
|
switch (message.what) {
|
|
case CMD_START:
|
|
// Always start in scan mode first.
|
|
RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
|
|
mClientInterfaceName = mWifiNative.setupInterfaceForClientInScanMode(
|
|
mWifiNativeInterfaceCallback, roleChangeInfo.requestorWs);
|
|
if (TextUtils.isEmpty(mClientInterfaceName)) {
|
|
Log.e(getTag(), "Failed to create ClientInterface. Sit in Idle");
|
|
takeBugReportInterfaceFailureIfNeeded(
|
|
"Wi-Fi scan STA interface HAL failure");
|
|
mModeListener.onStartFailure(ConcreteClientModeManager.this);
|
|
break;
|
|
}
|
|
if (roleChangeInfo.role instanceof ClientConnectivityRole) {
|
|
sendMessage(CMD_SWITCH_TO_CONNECT_MODE, roleChangeInfo);
|
|
transitionTo(mStartedState);
|
|
} else {
|
|
mScanRoleChangeInfoToSetOnTransition = roleChangeInfo;
|
|
transitionTo(mScanOnlyModeState);
|
|
}
|
|
break;
|
|
default:
|
|
Log.d(getTag(), "received an invalid message: " + message);
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
}
|
|
|
|
private class StartedState extends State {
|
|
|
|
private void onUpChanged(boolean isUp) {
|
|
if (isUp == mIfaceIsUp) {
|
|
return; // no change
|
|
}
|
|
mIfaceIsUp = isUp;
|
|
if (!isUp) {
|
|
// if the interface goes down we should exit and go back to idle state.
|
|
Log.d(getTag(), "interface down!");
|
|
mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
|
|
}
|
|
if (mClientModeImpl != null) {
|
|
mClientModeImpl.onUpChanged(isUp);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void enter() {
|
|
Log.d(getTag(), "entering StartedState");
|
|
mIfaceIsUp = false;
|
|
mIsStopped = false;
|
|
onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
|
|
}
|
|
|
|
@Override
|
|
public boolean processMessage(Message message) {
|
|
switch (message.what) {
|
|
case CMD_START:
|
|
// Already started, ignore this command.
|
|
break;
|
|
case CMD_SWITCH_TO_CONNECT_MODE: {
|
|
RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
|
|
updateConnectModeState(roleChangeInfo.role,
|
|
WifiManager.WIFI_STATE_ENABLING,
|
|
WifiManager.WIFI_STATE_DISABLED);
|
|
if (!mWifiNative.switchClientInterfaceToConnectivityMode(
|
|
mClientInterfaceName, roleChangeInfo.requestorWs)) {
|
|
updateConnectModeState(roleChangeInfo.role,
|
|
WifiManager.WIFI_STATE_UNKNOWN,
|
|
WifiManager.WIFI_STATE_ENABLING);
|
|
updateConnectModeState(roleChangeInfo.role,
|
|
WifiManager.WIFI_STATE_DISABLED,
|
|
WifiManager.WIFI_STATE_UNKNOWN);
|
|
takeBugReportInterfaceFailureIfNeeded(
|
|
"Wi-Fi STA interface HAL failure");
|
|
mModeListener.onStartFailure(ConcreteClientModeManager.this);
|
|
break;
|
|
}
|
|
// Role set in the enter of ConnectModeState.
|
|
mConnectRoleChangeInfoToSetOnTransition = roleChangeInfo;
|
|
transitionTo(mConnectModeState);
|
|
break;
|
|
}
|
|
case CMD_SWITCH_TO_SCAN_ONLY_MODE:
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
|
|
WifiManager.WIFI_STATE_ENABLED);
|
|
mDeferStopHandler.start(getWifiOffDeferringTimeMs());
|
|
break;
|
|
case CMD_SWITCH_TO_SCAN_ONLY_MODE_CONTINUE: {
|
|
RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
|
|
mScanRoleChangeInfoToSetOnTransition = roleChangeInfo;
|
|
transitionTo(mScanOnlyModeState);
|
|
break;
|
|
}
|
|
case CMD_INTERFACE_DOWN:
|
|
Log.e(getTag(), "Detected an interface down, reporting failure to "
|
|
+ "SelfRecovery");
|
|
mSelfRecovery.trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
|
|
// once interface down, nothing else to do... stop the state machine
|
|
captureObituaryAndQuitNow();
|
|
break;
|
|
case CMD_INTERFACE_STATUS_CHANGED:
|
|
boolean isUp = message.arg1 == 1;
|
|
onUpChanged(isUp);
|
|
break;
|
|
case CMD_INTERFACE_DESTROYED:
|
|
Log.e(getTag(), "interface destroyed - client mode stopping");
|
|
mClientInterfaceName = null;
|
|
// once interface destroyed, nothing else to do... stop the state machine
|
|
captureObituaryAndQuitNow();
|
|
break;
|
|
default:
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
|
|
/**
|
|
* Clean up state, unregister listeners and update wifi state.
|
|
*/
|
|
@Override
|
|
public void exit() {
|
|
if (mClientInterfaceName != null) {
|
|
mWifiNative.teardownInterface(mClientInterfaceName);
|
|
mClientInterfaceName = null;
|
|
mIfaceIsUp = false;
|
|
}
|
|
|
|
Log.i(getTag(), "StartedState#exit(), setting mRole = null");
|
|
mIsStopped = true;
|
|
cleanupOnQuitIfApplicable();
|
|
}
|
|
}
|
|
|
|
private class ScanOnlyModeState extends State {
|
|
@Override
|
|
public void enter() {
|
|
Log.d(getTag(), "entering ScanOnlyModeState");
|
|
|
|
if (mClientInterfaceName != null) {
|
|
mScanOnlyModeImpl = mWifiInjector.makeScanOnlyModeImpl(
|
|
mClientInterfaceName);
|
|
} else {
|
|
Log.e(getTag(), "Entered ScanOnlyModeState with a null interface name!");
|
|
}
|
|
|
|
if (mScanRoleChangeInfoToSetOnTransition == null
|
|
|| (mScanRoleChangeInfoToSetOnTransition.role != ROLE_CLIENT_SCAN_ONLY)) {
|
|
Log.wtf(TAG, "Unexpected mScanRoleChangeInfoToSetOnTransition: "
|
|
+ mScanRoleChangeInfoToSetOnTransition);
|
|
// Should never happen, but fallback to scan only to avoid a crash.
|
|
mScanRoleChangeInfoToSetOnTransition =
|
|
new RoleChangeInfo(ROLE_CLIENT_SCAN_ONLY);
|
|
}
|
|
|
|
setRoleInternalAndInvokeCallback(mScanRoleChangeInfoToSetOnTransition);
|
|
// If we're in ScanOnlyModeState, there is only 1 CMM. So it's ok to call
|
|
// WakeupController directly, there won't be multiple CMMs trampling over each other
|
|
mWakeupController.start();
|
|
mWifiNative.setScanMode(mClientInterfaceName, true);
|
|
}
|
|
|
|
@Override
|
|
public boolean processMessage(Message message) {
|
|
switch (message.what) {
|
|
case CMD_SWITCH_TO_SCAN_ONLY_MODE:
|
|
// Already in scan only mode, ignore this command.
|
|
break;
|
|
default:
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
|
|
@Override
|
|
public void exit() {
|
|
mScanOnlyModeImpl = null;
|
|
mScanRoleChangeInfoToSetOnTransition = null;
|
|
|
|
// If we're in ScanOnlyModeState, there is only 1 CMM. So it's ok to call
|
|
// WakeupController directly, there won't be multiple CMMs trampling over each other
|
|
mWakeupController.stop();
|
|
mWifiNative.setScanMode(mClientInterfaceName, false);
|
|
}
|
|
}
|
|
|
|
private class ConnectModeState extends State {
|
|
@Override
|
|
public void enter() {
|
|
Log.d(getTag(), "entering ConnectModeState, starting ClientModeImpl");
|
|
if (mClientInterfaceName == null) {
|
|
Log.e(getTag(), "Supposed to start ClientModeImpl, but iface is null!");
|
|
} else {
|
|
if (mClientModeImpl != null) {
|
|
Log.e(getTag(), "ConnectModeState.enter(): mClientModeImpl is already "
|
|
+ "instantiated?!");
|
|
}
|
|
mClientModeImpl = mWifiInjector.makeClientModeImpl(
|
|
mClientInterfaceName, ConcreteClientModeManager.this,
|
|
mVerboseLoggingEnabled);
|
|
mClientModeImpl.setShouldReduceNetworkScore(mShouldReduceNetworkScore);
|
|
}
|
|
if (mConnectRoleChangeInfoToSetOnTransition == null
|
|
|| !(mConnectRoleChangeInfoToSetOnTransition.role
|
|
instanceof ClientConnectivityRole)) {
|
|
Log.wtf(TAG, "Unexpected mConnectRoleChangeInfoToSetOnTransition: "
|
|
+ mConnectRoleChangeInfoToSetOnTransition);
|
|
// Should never happen, but fallback to primary to avoid a crash.
|
|
mConnectRoleChangeInfoToSetOnTransition =
|
|
new RoleChangeInfo(ROLE_CLIENT_PRIMARY);
|
|
}
|
|
|
|
// Could be any one of possible connect mode roles.
|
|
setRoleInternalAndInvokeCallback(mConnectRoleChangeInfoToSetOnTransition);
|
|
updateConnectModeState(mConnectRoleChangeInfoToSetOnTransition.role,
|
|
WIFI_STATE_ENABLED, WIFI_STATE_ENABLING);
|
|
}
|
|
|
|
@Override
|
|
public boolean processMessage(Message message) {
|
|
switch (message.what) {
|
|
case CMD_SWITCH_TO_CONNECT_MODE:
|
|
RoleChangeInfo roleChangeInfo = (RoleChangeInfo) message.obj;
|
|
// switching to connect mode when already in connect mode, just update the
|
|
// requestor WorkSource.
|
|
boolean success = mWifiNative.replaceStaIfaceRequestorWs(
|
|
mClientInterfaceName, roleChangeInfo.requestorWs);
|
|
if (success) {
|
|
setRoleInternalAndInvokeCallback(roleChangeInfo);
|
|
} else {
|
|
// If this call failed, the iface would be torn down.
|
|
// Thus, simply abort and let the iface down handling take care of the
|
|
// rest.
|
|
Log.e(getTag(), "Failed to switch ClientModeManager="
|
|
+ ConcreteClientModeManager.this + "'s requestorWs");
|
|
}
|
|
break;
|
|
case CMD_SWITCH_TO_SCAN_ONLY_MODE:
|
|
case CMD_INTERFACE_DESTROYED:
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
|
|
WifiManager.WIFI_STATE_ENABLED);
|
|
return NOT_HANDLED; // Handled in StartedState.
|
|
case CMD_INTERFACE_DOWN:
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLING,
|
|
WifiManager.WIFI_STATE_UNKNOWN);
|
|
return NOT_HANDLED; // Handled in StartedState.
|
|
case CMD_INTERFACE_STATUS_CHANGED:
|
|
boolean isUp = message.arg1 == 1;
|
|
if (isUp == mIfaceIsUp) {
|
|
break; // no change
|
|
}
|
|
if (!isUp) {
|
|
// TODO(b/201584491) Figure out what to do with this block of code
|
|
// handling iface down since most devices should have MAC randomization
|
|
// enabled, which makes the "else" block essentially no-op. Also, the
|
|
// "else" block would actually fully disable wifi which is not desirable
|
|
// behavior because the firmware can recover the iface after it is down.
|
|
if (mWifiGlobals.isConnectedMacRandomizationEnabled()) {
|
|
return HANDLED; // For MAC randomization, ignore...
|
|
} else {
|
|
// Handle the error case where our underlying interface went down if
|
|
// we do not have mac randomization enabled (b/72459123).
|
|
// if the interface goes down we should exit and go back to idle
|
|
// state.
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_UNKNOWN,
|
|
WifiManager.WIFI_STATE_ENABLED);
|
|
}
|
|
}
|
|
return NOT_HANDLED; // Handled in StartedState.
|
|
default:
|
|
return NOT_HANDLED;
|
|
}
|
|
return HANDLED;
|
|
}
|
|
|
|
@Override
|
|
public void exit() {
|
|
updateConnectModeState(mRole, WifiManager.WIFI_STATE_DISABLED,
|
|
WifiManager.WIFI_STATE_DISABLING);
|
|
|
|
if (mClientModeImpl == null) {
|
|
Log.w(getTag(), "ConnectModeState.exit(): mClientModeImpl is already null?!");
|
|
} else {
|
|
Log.d(getTag(), "Stopping ClientModeImpl");
|
|
mClientModeImpl.stop();
|
|
mGraveyard.inter(mClientModeImpl);
|
|
mClientModeImpl = null;
|
|
}
|
|
|
|
mConnectRoleChangeInfoToSetOnTransition = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Called by a ClientModeImpl owned by this CMM informing it has fully stopped. */
|
|
public void onClientModeImplQuit() {
|
|
cleanupOnQuitIfApplicable();
|
|
}
|
|
|
|
/**
|
|
* Only clean up this CMM once the CMM and all associated ClientModeImpls have been stopped.
|
|
* This is necessary because ClientModeImpl sends broadcasts during stop, and the role must
|
|
* remain primary for {@link ClientModeManagerBroadcastQueue} to send them out.
|
|
*/
|
|
private void cleanupOnQuitIfApplicable() {
|
|
if (mIsStopped && mGraveyard.hasAllClientModeImplsQuit()) {
|
|
mPreviousRole = mRole;
|
|
mLastRoleChangeSinceBootMs = mClock.getElapsedSinceBootMillis();
|
|
mRole = null;
|
|
// only call onStopped() after role has been reset to null since ActiveModeWarden
|
|
// expects the CMM to be fully stopped before onStopped().
|
|
mModeListener.onStopped(ConcreteClientModeManager.this);
|
|
|
|
// reset to false so that onStopped() won't be triggered again.
|
|
mIsStopped = false;
|
|
}
|
|
}
|
|
|
|
private void takeBugReportInterfaceFailureIfNeeded(String bugTitle) {
|
|
if (mWifiInjector.getDeviceConfigFacade().isInterfaceFailureBugreportEnabled()) {
|
|
mWifiInjector.getWifiDiagnostics().takeBugReport(bugTitle, bugTitle);
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private ClientMode getClientMode() {
|
|
if (mClientModeImpl != null) {
|
|
return mClientModeImpl;
|
|
}
|
|
if (mScanOnlyModeImpl != null) {
|
|
return mScanOnlyModeImpl;
|
|
}
|
|
return mDefaultClientModeManager;
|
|
}
|
|
|
|
/*
|
|
* Note: These are simple wrappers over methods to {@link ClientModeImpl}.
|
|
*/
|
|
|
|
@Override
|
|
public void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
|
|
int callingUid, @NonNull String packageName) {
|
|
getClientMode().connectNetwork(result, wrapper, callingUid, packageName);
|
|
}
|
|
|
|
@Override
|
|
public void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
|
|
int callingUid, @NonNull String packageName) {
|
|
getClientMode().saveNetwork(result, wrapper, callingUid, packageName);
|
|
}
|
|
|
|
@Override
|
|
public void disconnect() {
|
|
getClientMode().disconnect();
|
|
}
|
|
|
|
@Override
|
|
public void reconnect(WorkSource ws) {
|
|
getClientMode().reconnect(ws);
|
|
}
|
|
|
|
@Override
|
|
public void reassociate() {
|
|
getClientMode().reassociate();
|
|
}
|
|
|
|
@Override
|
|
public void startConnectToNetwork(int networkId, int uid, String bssid) {
|
|
getClientMode().startConnectToNetwork(networkId, uid, bssid);
|
|
}
|
|
|
|
@Override
|
|
public void startRoamToNetwork(int networkId, String bssid) {
|
|
getClientMode().startRoamToNetwork(networkId, bssid);
|
|
}
|
|
|
|
@Override
|
|
public boolean setWifiConnectedNetworkScorer(
|
|
IBinder binder, IWifiConnectedNetworkScorer scorer) {
|
|
return getClientMode().setWifiConnectedNetworkScorer(binder, scorer);
|
|
}
|
|
|
|
@Override
|
|
public void clearWifiConnectedNetworkScorer() {
|
|
getClientMode().clearWifiConnectedNetworkScorer();
|
|
}
|
|
|
|
@Override
|
|
public void resetSimAuthNetworks(@ClientModeImpl.ResetSimReason int resetReason) {
|
|
getClientMode().resetSimAuthNetworks(resetReason);
|
|
}
|
|
|
|
@Override
|
|
public void onBluetoothConnectionStateChanged() {
|
|
getClientMode().onBluetoothConnectionStateChanged();
|
|
}
|
|
|
|
@Override
|
|
public WifiInfo syncRequestConnectionInfo() {
|
|
return getClientMode().syncRequestConnectionInfo();
|
|
}
|
|
|
|
@Override
|
|
public boolean syncQueryPasspointIcon(long bssid, String fileName) {
|
|
return getClientMode().syncQueryPasspointIcon(bssid, fileName);
|
|
}
|
|
|
|
@Override
|
|
public Network syncGetCurrentNetwork() {
|
|
return getClientMode().syncGetCurrentNetwork();
|
|
}
|
|
|
|
@Override
|
|
public DhcpResultsParcelable syncGetDhcpResultsParcelable() {
|
|
return getClientMode().syncGetDhcpResultsParcelable();
|
|
}
|
|
|
|
@Override
|
|
public long getSupportedFeatures() {
|
|
return getClientMode().getSupportedFeatures();
|
|
}
|
|
|
|
@Override
|
|
public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
|
|
IProvisioningCallback callback) {
|
|
return getClientMode().syncStartSubscriptionProvisioning(
|
|
callingUid, provider, callback);
|
|
}
|
|
|
|
@Override
|
|
public boolean isWifiStandardSupported(@WifiAnnotations.WifiStandard int standard) {
|
|
return getClientMode().isWifiStandardSupported(standard);
|
|
}
|
|
|
|
@Override
|
|
public void enableTdls(String remoteMacAddress, boolean enable) {
|
|
getClientMode().enableTdls(remoteMacAddress, enable);
|
|
}
|
|
|
|
@Override
|
|
public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
getClientMode().dumpIpClient(fd, pw, args);
|
|
}
|
|
|
|
@Override
|
|
public void dumpWifiScoreReport(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
getClientMode().dumpWifiScoreReport(fd, pw, args);
|
|
}
|
|
|
|
@Override
|
|
public void enableVerboseLogging(boolean verbose) {
|
|
mVerboseLoggingEnabled = verbose;
|
|
getClientMode().enableVerboseLogging(verbose);
|
|
}
|
|
|
|
@Override
|
|
public String getFactoryMacAddress() {
|
|
return getClientMode().getFactoryMacAddress();
|
|
}
|
|
|
|
@Override
|
|
public WifiConfiguration getConnectedWifiConfiguration() {
|
|
return getClientMode().getConnectedWifiConfiguration();
|
|
}
|
|
|
|
@Override
|
|
public WifiConfiguration getConnectingWifiConfiguration() {
|
|
return getClientMode().getConnectingWifiConfiguration();
|
|
}
|
|
|
|
@Override
|
|
public String getConnectedBssid() {
|
|
return getClientMode().getConnectedBssid();
|
|
}
|
|
|
|
@Override
|
|
public String getConnectingBssid() {
|
|
return getClientMode().getConnectingBssid();
|
|
}
|
|
|
|
@Override
|
|
public WifiLinkLayerStats getWifiLinkLayerStats() {
|
|
return getClientMode().getWifiLinkLayerStats();
|
|
}
|
|
|
|
@Override
|
|
public boolean setPowerSave(@PowerSaveClientType int client, boolean ps) {
|
|
return getClientMode().setPowerSave(client, ps);
|
|
}
|
|
|
|
@Override
|
|
public boolean enablePowerSave() {
|
|
return getClientMode().enablePowerSave();
|
|
}
|
|
|
|
@Override
|
|
public boolean setLowLatencyMode(boolean enabled) {
|
|
return getClientMode().setLowLatencyMode(enabled);
|
|
}
|
|
|
|
@Override
|
|
public WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
|
|
return getClientMode().getMcastLockManagerFilterController();
|
|
}
|
|
|
|
@Override
|
|
public boolean isConnected() {
|
|
return getClientMode().isConnected();
|
|
}
|
|
|
|
@Override
|
|
public boolean isConnecting() {
|
|
return getClientMode().isConnecting();
|
|
}
|
|
|
|
@Override
|
|
public boolean isRoaming() {
|
|
return getClientMode().isRoaming();
|
|
}
|
|
|
|
@Override
|
|
public boolean isDisconnected() {
|
|
return getClientMode().isDisconnected();
|
|
}
|
|
|
|
@Override
|
|
public boolean isSupplicantTransientState() {
|
|
return getClientMode().isSupplicantTransientState();
|
|
}
|
|
|
|
@Override
|
|
public void onCellularConnectivityChanged(@WifiDataStall.CellularDataStatusCode int status) {
|
|
getClientMode().onCellularConnectivityChanged(status);
|
|
}
|
|
|
|
@Override
|
|
public void probeLink(LinkProbeCallback callback, int mcs) {
|
|
getClientMode().probeLink(callback, mcs);
|
|
}
|
|
|
|
@Override
|
|
public void sendMessageToClientModeImpl(Message msg) {
|
|
getClientMode().sendMessageToClientModeImpl(msg);
|
|
}
|
|
|
|
@Override
|
|
public long getId() {
|
|
return mId;
|
|
}
|
|
|
|
@Override
|
|
public void setMboCellularDataStatus(boolean available) {
|
|
getClientMode().setMboCellularDataStatus(available);
|
|
}
|
|
|
|
@Override
|
|
public WifiNative.RoamingCapabilities getRoamingCapabilities() {
|
|
return getClientMode().getRoamingCapabilities();
|
|
}
|
|
|
|
@Override
|
|
public boolean configureRoaming(WifiNative.RoamingConfig config) {
|
|
return getClientMode().configureRoaming(config);
|
|
}
|
|
|
|
@Override
|
|
public boolean enableRoaming(boolean enabled) {
|
|
return getClientMode().enableRoaming(enabled);
|
|
}
|
|
|
|
@Override
|
|
public boolean setCountryCode(String countryCode) {
|
|
return getClientMode().setCountryCode(countryCode);
|
|
}
|
|
|
|
@Override
|
|
public List<TxFateReport> getTxPktFates() {
|
|
return getClientMode().getTxPktFates();
|
|
}
|
|
|
|
@Override
|
|
public List<RxFateReport> getRxPktFates() {
|
|
return getClientMode().getRxPktFates();
|
|
}
|
|
|
|
@Override
|
|
public DeviceWiphyCapabilities getDeviceWiphyCapabilities() {
|
|
return getClientMode().getDeviceWiphyCapabilities();
|
|
}
|
|
|
|
@Override
|
|
public boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes) {
|
|
return getClientMode().requestAnqp(bssid, anqpIds, hs20Subtypes);
|
|
}
|
|
|
|
@Override
|
|
public boolean requestVenueUrlAnqp(String bssid) {
|
|
return getClientMode().requestVenueUrlAnqp(bssid);
|
|
}
|
|
|
|
@Override
|
|
public boolean requestIcon(String bssid, String fileName) {
|
|
return getClientMode().requestIcon(bssid, fileName);
|
|
}
|
|
|
|
@Override
|
|
public void setShouldReduceNetworkScore(boolean shouldReduceNetworkScore) {
|
|
mShouldReduceNetworkScore = shouldReduceNetworkScore;
|
|
getClientMode().setShouldReduceNetworkScore(shouldReduceNetworkScore);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "ConcreteClientModeManager{id=" + getId()
|
|
+ " iface=" + getInterfaceName()
|
|
+ " role=" + getRole()
|
|
+ "}";
|
|
}
|
|
|
|
@Override
|
|
public void updateCapabilities() {
|
|
getClientMode().updateCapabilities();
|
|
}
|
|
}
|