package com.android.settings.wifi.details;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import android.app.Fragment;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
-import android.net.NetworkBadging;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.os.CountDownTimer;
import android.os.Handler;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceCategory;
-import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
-import android.view.View;
-import android.widget.Button;
import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
-import com.android.settings.applications.LayoutPreference;
-import com.android.settings.core.PreferenceController;
-import com.android.settings.core.instrumentation.MetricsFeatureProvider;
-import com.android.settings.vpn2.ConnectivityManagerWrapper;
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
+import com.android.settings.development.featureflags.FeatureFlagPersistent;
import com.android.settings.widget.EntityHeaderController;
-import com.android.settings.wifi.WifiDetailPreference;
+import com.android.settings.wifi.WifiDialog;
+import com.android.settings.wifi.WifiDialog.WifiDialogListener;
+import com.android.settings.wifi.WifiUtils;
+import com.android.settings.wifi.dpp.WifiDppUtils;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.widget.ActionButtonsPreference;
+import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTrackerFactory;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.time.Duration;
import java.util.StringJoiner;
import java.util.stream.Collectors;
* Controller for logic pertaining to displaying Wifi information for the
* {@link WifiNetworkDetailsFragment}.
*/
-public class WifiDetailPreferenceController extends PreferenceController implements
- LifecycleObserver, OnPause, OnResume {
+public class WifiDetailPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
+ OnResume {
+
private static final String TAG = "WifiDetailsPrefCtrl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting
static final String KEY_HEADER = "connection_header";
@VisibleForTesting
+ static final String KEY_DATA_USAGE_HEADER = "status_header";
+ @VisibleForTesting
static final String KEY_BUTTONS_PREF = "buttons";
@VisibleForTesting
static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
@VisibleForTesting
- static final String KEY_LINK_SPEED = "link_speed";
+ static final String KEY_TX_LINK_SPEED = "tx_link_speed";
+ @VisibleForTesting
+ static final String KEY_RX_LINK_SPEED = "rx_link_speed";
@VisibleForTesting
static final String KEY_FREQUENCY_PREF = "frequency";
@VisibleForTesting
static final String KEY_SECURITY_PREF = "security";
@VisibleForTesting
+ static final String KEY_SSID_PREF = "ssid";
+ @VisibleForTesting
static final String KEY_MAC_ADDRESS_PREF = "mac_address";
@VisibleForTesting
static final String KEY_IP_ADDRESS_PREF = "ip_address";
@VisibleForTesting
static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
+ private static final int STATE_NONE = 1;
+ private static final int STATE_ENABLE_WIFI = 2;
+ private static final int STATE_ENABLE_WIFI_FAILED = 3;
+ private static final int STATE_CONNECTING = 4;
+ private static final int STATE_CONNECTED = 5;
+ private static final int STATE_FAILED = 6;
+ private static final int STATE_NOT_IN_RANGE = 7;
+ private static final int STATE_DISCONNECTED = 8;
+ private static final long TIMEOUT = Duration.ofSeconds(10).toMillis();
+
+ // Be static to avoid too much object not be reset.
+ @VisibleForTesting
+ static CountDownTimer mTimer;
+
private AccessPoint mAccessPoint;
- private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
private final ConnectivityManager mConnectivityManager;
- private final Fragment mFragment;
+ private final PreferenceFragmentCompat mFragment;
private final Handler mHandler;
private LinkProperties mLinkProperties;
private Network mNetwork;
private NetworkInfo mNetworkInfo;
private NetworkCapabilities mNetworkCapabilities;
- private int mRssi;
+ private int mRssiSignalLevel = -1;
private String[] mSignalStr;
- private final WifiConfiguration mWifiConfig;
+ private WifiConfiguration mWifiConfig;
private WifiInfo mWifiInfo;
private final WifiManager mWifiManager;
+ private final WifiTracker mWifiTracker;
private final MetricsFeatureProvider mMetricsFeatureProvider;
+ private boolean mIsOutOfRange;
+ private boolean mIsEphemeral;
+ private boolean mConnected;
+ private int mConnectingState;
+ private WifiManager.ActionListener mConnectListener;
// UI elements - in order of appearance
- private LayoutPreference mButtonsPref;
+ private ActionButtonsPreference mButtonsPref;
private EntityHeaderController mEntityHeaderController;
- private Button mForgetButton;
- private Button mSignInButton;
- private WifiDetailPreference mSignalStrengthPref;
- private WifiDetailPreference mLinkSpeedPref;
- private WifiDetailPreference mFrequencyPref;
- private WifiDetailPreference mSecurityPref;
- private WifiDetailPreference mMacAddressPref;
- private WifiDetailPreference mIpAddressPref;
- private WifiDetailPreference mGatewayPref;
- private WifiDetailPreference mSubnetPref;
- private WifiDetailPreference mDnsPref;
+ private Preference mSignalStrengthPref;
+ private Preference mTxLinkSpeedPref;
+ private Preference mRxLinkSpeedPref;
+ private Preference mFrequencyPref;
+ private Preference mSecurityPref;
+ private Preference mSsidPref;
+ private Preference mMacAddressPref;
+ private Preference mIpAddressPref;
+ private Preference mGatewayPref;
+ private Preference mSubnetPref;
+ private Preference mDnsPref;
private PreferenceCategory mIpv6Category;
private Preference mIpv6AddressPref;
+ private Lifecycle mLifecycle;
+ Preference mDataUsageSummaryPref;
+ WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
+ private final IconInjector mIconInjector;
private final IntentFilter mFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
+ case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
+ if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
+ false /* defaultValue */)) {
+ // only one network changed
+ WifiConfiguration wifiConfiguration = intent
+ .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
+ if (mAccessPoint.matches(wifiConfiguration)) {
+ mWifiConfig = wifiConfiguration;
+ }
+ }
+ // fall through
case WifiManager.NETWORK_STATE_CHANGED_ACTION:
case WifiManager.RSSI_CHANGED_ACTION:
- updateInfo();
+ refreshPage();
+ break;
}
}
};
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
mLinkProperties = lp;
- updateIpLayerInfo();
+ refreshIpLayerInfo();
}
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
- // If the network just validated or lost Internet access, refresh network state.
- // Don't do this on every NetworkCapabilities change because refreshNetworkState
- // sends IPCs to the system server from the UI thread, which can cause jank.
+ // If the network just validated or lost Internet access or detected partial internet
+ // connectivity, refresh network state. Don't do this on every NetworkCapabilities
+ // change because refreshNetworkState sends IPCs to the system server from the UI
+ // thread, which can cause jank.
if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
- if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
- hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
- refreshNetworkState();
+ if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED)
+ || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)
+ || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
+ mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+ refreshEntityHeader();
}
mNetworkCapabilities = nc;
- updateIpLayerInfo();
+ refreshButtons();
+ refreshIpLayerInfo();
}
}
@Override
public void onLost(Network network) {
- if (network.equals(mNetwork)) {
+ // Ephemeral network not a saved network, leave detail page once disconnected
+ if (mIsEphemeral && network.equals(mNetwork)) {
exitActivity();
}
}
};
- public WifiDetailPreferenceController(
+ @VisibleForTesting
+ final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
+ /** Called when the state of Wifi has changed. */
+ public void onWifiStateChanged(int state) {
+ Log.d(TAG, "onWifiStateChanged(" + state + ")");
+ if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) {
+ updateConnectingState(STATE_CONNECTING);
+ } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) {
+ // update as disconnected once Wi-Fi disabled since may not received
+ // onConnectedChanged for this case.
+ updateConnectingState(STATE_DISCONNECTED);
+ }
+ }
+
+ /** Called when the connection state of wifi has changed. */
+ public void onConnectedChanged() {
+ refreshPage();
+ }
+
+ /**
+ * Called to indicate the list of AccessPoints has been updated and
+ * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
+ */
+ public void onAccessPointsChanged() {
+ refreshPage();
+ }
+ };
+
+ public static WifiDetailPreferenceController newInstance(
AccessPoint accessPoint,
- ConnectivityManagerWrapper connectivityManagerWrapper,
+ ConnectivityManager connectivityManager,
Context context,
- Fragment fragment,
+ PreferenceFragmentCompat fragment,
Handler handler,
Lifecycle lifecycle,
WifiManager wifiManager,
MetricsFeatureProvider metricsFeatureProvider) {
+ return new WifiDetailPreferenceController(
+ accessPoint, connectivityManager, context, fragment, handler, lifecycle,
+ wifiManager, metricsFeatureProvider, new IconInjector(context));
+ }
+
+ @VisibleForTesting
+ /* package */ WifiDetailPreferenceController(
+ AccessPoint accessPoint,
+ ConnectivityManager connectivityManager,
+ Context context,
+ PreferenceFragmentCompat fragment,
+ Handler handler,
+ Lifecycle lifecycle,
+ WifiManager wifiManager,
+ MetricsFeatureProvider metricsFeatureProvider,
+ IconInjector injector) {
super(context);
mAccessPoint = accessPoint;
- mConnectivityManager = connectivityManagerWrapper.getConnectivityManager();
- mConnectivityManagerWrapper = connectivityManagerWrapper;
+ mConnectivityManager = connectivityManager;
mFragment = fragment;
mHandler = handler;
mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
mWifiConfig = accessPoint.getConfig();
mWifiManager = wifiManager;
mMetricsFeatureProvider = metricsFeatureProvider;
+ mIconInjector = injector;
mFilter = new IntentFilter();
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ mLifecycle = lifecycle;
lifecycle.addObserver(this);
+
+ mWifiTracker = WifiTrackerFactory.create(
+ mFragment.getActivity(),
+ mWifiListener,
+ mLifecycle,
+ true /*includeSaved*/,
+ true /*includeScans*/);
+ mConnected = mAccessPoint.isActive();
+ // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we
+ // could not check if the AccessPoint is ephemeral. Need to cache it in first.
+ mIsEphemeral = mAccessPoint.isEphemeral();
+ mConnectingState = STATE_NONE;
+ mConnectListener = new WifiManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ // Do nothing
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ updateConnectingState(STATE_FAILED);
+ }
+ };
}
@Override
setupEntityHeader(screen);
- mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
- mSignInButton = mButtonsPref.findViewById(R.id.signin_button);
- mSignInButton.setText(R.string.support_sign_in_button_text);
- mSignInButton.setOnClickListener(view -> signIntoNetwork());
+ mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF))
+ .setButton1Text(R.string.forget)
+ .setButton1Icon(R.drawable.ic_settings_delete)
+ .setButton1OnClickListener(view -> forgetNetwork())
+ .setButton2Text(R.string.wifi_sign_in_button_text)
+ .setButton2Icon(R.drawable.ic_settings_sign_in)
+ .setButton2OnClickListener(view -> signIntoNetwork())
+ .setButton3Text(R.string.wifi_connect)
+ .setButton3Icon(R.drawable.ic_settings_wireless)
+ .setButton3OnClickListener(view -> connectNetwork())
+ .setButton3Enabled(true)
+ .setButton4Text(R.string.share)
+ .setButton4Icon(R.drawable.ic_qrcode_24dp)
+ .setButton4OnClickListener(view -> shareNetwork());
- mSignalStrengthPref =
- (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
- mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
- mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
- mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
+ mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
+ mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
+ mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED);
+ mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF);
+ mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
- mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
- mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
- mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
- mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
- mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
+ mSsidPref = screen.findPreference(KEY_SSID_PREF);
+ mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
+ mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
+ mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
+ mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
+ mDnsPref = screen.findPreference(KEY_DNS_PREF);
- mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
+ mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
- mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
- mForgetButton = mButtonsPref.findViewById(R.id.forget_button);
- mForgetButton.setText(R.string.forget);
- mForgetButton.setOnClickListener(view -> forgetNetwork());
+ mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
}
private void setupEntityHeader(PreferenceScreen screen) {
- LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
+ LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
+
+ if (usingDataUsageHeader(mContext)) {
+ headerPref.setVisible(false);
+ mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER);
+ mDataUsageSummaryPref.setVisible(true);
+ mSummaryHeaderController =
+ new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(),
+ mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid());
+ return;
+ }
+
mEntityHeaderController =
EntityHeaderController.newInstance(
mFragment.getActivity(), mFragment,
headerPref.findViewById(R.id.entity_header));
ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
- iconView.setBackground(
- mContext.getDrawable(R.drawable.ic_settings_widget_background));
+
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
+ mEntityHeaderController.setLabel(mAccessPoint.getTitle());
+ }
+
+ private void refreshEntityHeader() {
+ if (usingDataUsageHeader(mContext)) {
+ mSummaryHeaderController.updateState(mDataUsageSummaryPref);
+ } else {
+ mEntityHeaderController
+ .setSummary(
+ mAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+ .setRecyclerView(mFragment.getListView(), mLifecycle)
+ .done(mFragment.getActivity(), true /* rebind */);
+ }
+ }
+
+ private void updateNetworkInfo() {
+ mNetwork = mWifiManager.getCurrentNetwork();
+ mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
+ mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
}
@Override
public void onResume() {
// Ensure mNetwork is set before any callbacks above are delivered, since our
// NetworkCallback only looks at changes to mNetwork.
- mNetwork = mWifiManager.getCurrentNetwork();
- mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
- mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
- updateInfo();
+ updateNetworkInfo();
+ refreshPage();
mContext.registerReceiver(mReceiver, mFilter);
- mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
+ mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
mHandler);
}
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
- private void updateInfo() {
- // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
- // callbacks. mNetwork doesn't change except in onResume.
- mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
- mWifiInfo = mWifiManager.getConnectionInfo();
- if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
- exitActivity();
+ private void refreshPage() {
+ if(!updateAccessPoint()) {
return;
}
- // Update whether the forgot button should be displayed.
- mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
+ Log.d(TAG, "Update UI!");
+
+ // refresh header
+ refreshEntityHeader();
- refreshNetworkState();
+ // refresh Buttons
+ refreshButtons();
// Update Connection Header icon and Signal Strength Preference
- mRssi = mWifiInfo.getRssi();
refreshRssiViews();
-
+ // Frequency Pref
+ refreshFrequency();
+ // Transmit Link Speed Pref
+ refreshTxSpeed();
+ // Receive Link Speed Pref
+ refreshRxSpeed();
+ // IP related information
+ refreshIpLayerInfo();
+ // SSID Pref
+ refreshSsid();
// MAC Address Pref
- mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
+ refreshMacAddress();
+ }
- // Link Speed Pref
- int linkSpeedMbps = mWifiInfo.getLinkSpeed();
- mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
- mLinkSpeedPref.setDetailText(mContext.getString(
- R.string.link_speed, mWifiInfo.getLinkSpeed()));
+ @VisibleForTesting
+ boolean updateAccessPoint() {
+ boolean changed = false;
+ // remember mIsOutOfRange as old before updated
+ boolean oldState = mIsOutOfRange;
+ updateAccessPointFromScannedList();
+
+ if (mAccessPoint.isActive()) {
+ updateNetworkInfo();
+ mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
+ mWifiInfo = mWifiManager.getConnectionInfo();
+ if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
+ // Once connected, can't get mNetwork immediately, return false and wait for
+ // next time to update UI. also reset {@code mIsOutOfRange}
+ mIsOutOfRange = oldState;
+ return false;
+ }
+ changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+ }
+
+ // signal level changed
+ changed |= mRssiSignalLevel != mAccessPoint.getLevel();
+ // In/Out of range changed
+ changed |= oldState != mIsOutOfRange;
+ // connect state changed
+ if (mConnected != mAccessPoint.isActive()) {
+ mConnected = mAccessPoint.isActive();
+ changed = true;
+ updateConnectingState(mAccessPoint.isActive() ? STATE_CONNECTED : STATE_DISCONNECTED);
+ }
+
+ return changed;
+ }
+
+ private void updateAccessPointFromScannedList() {
+ mIsOutOfRange = true;
+
+ for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
+ if (mAccessPoint.matches(ap)) {
+ mAccessPoint = ap;
+ mWifiConfig = ap.getConfig();
+ mIsOutOfRange = !mAccessPoint.isReachable();
+ return;
+ }
+ }
+ }
+
+ private void exitActivity() {
+ if (DEBUG) {
+ Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
+ }
+ mFragment.getActivity().finish();
+ }
+
+ private void refreshRssiViews() {
+ int signalLevel = mAccessPoint.getLevel();
+
+ // Disappears signal view if not in range. e.g. for saved networks.
+ if (mIsOutOfRange) {
+ mSignalStrengthPref.setVisible(false);
+ mRssiSignalLevel = -1;
+ return;
+ }
+
+ if (mRssiSignalLevel == signalLevel) {
+ return;
+ }
+ mRssiSignalLevel = signalLevel;
+ Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
+
+ if (mEntityHeaderController != null) {
+ mEntityHeaderController
+ .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(),
+ true /* rebind */);
+ }
+
+ Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
+ wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
+ mSignalStrengthPref.setIcon(wifiIconDark);
+
+ mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
+ mSignalStrengthPref.setVisible(true);
+ }
+
+ private Drawable redrawIconForHeader(Drawable original) {
+ final int iconSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.wifi_detail_page_header_image_size);
+ final int actualWidth = original.getMinimumWidth();
+ final int actualHeight = original.getMinimumHeight();
+
+ if ((actualWidth == iconSize && actualHeight == iconSize)
+ || !VectorDrawable.class.isInstance(original)) {
+ return original;
+ }
+
+ // clear tint list to make sure can set 87% black after enlarge
+ original.setTintList(null);
+
+ // enlarge icon size
+ final Bitmap bitmap = Utils.createBitmap(original,
+ iconSize /*width*/,
+ iconSize /*height*/);
+ Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap);
+
+ // config color for 87% black after enlarge
+ newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
+
+ return newIcon;
+ }
+
+ private void refreshFrequency() {
+ if (mWifiInfo == null) {
+ mFrequencyPref.setVisible(false);
+ return;
+ }
- // Frequency Pref
final int frequency = mWifiInfo.getFrequency();
String band = null;
if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
band = mContext.getResources().getString(R.string.wifi_band_5ghz);
} else {
Log.e(TAG, "Unexpected frequency " + frequency);
+ // Connecting state is unstable, make it disappeared if unexpected
+ if (mConnectingState == STATE_CONNECTING) {
+ mFrequencyPref.setVisible(false);
+ }
+ return;
}
- mFrequencyPref.setDetailText(band);
+ mFrequencyPref.setSummary(band);
+ mFrequencyPref.setVisible(true);
+ }
- updateIpLayerInfo();
+ private void refreshTxSpeed() {
+ if (mWifiInfo == null) {
+ mTxLinkSpeedPref.setVisible(false);
+ return;
+ }
+
+ int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
+ mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
+ mTxLinkSpeedPref.setSummary(mContext.getString(
+ R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
}
- private void exitActivity() {
- if (DEBUG) {
- Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
+ private void refreshRxSpeed() {
+ if (mWifiInfo == null) {
+ mRxLinkSpeedPref.setVisible(false);
+ return;
}
- mFragment.getActivity().finish();
+
+ int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
+ mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
+ mRxLinkSpeedPref.setSummary(mContext.getString(
+ R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
}
- private void refreshNetworkState() {
- mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
- mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
- .done(mFragment.getActivity(), true /* rebind */);
+ private void refreshSsid() {
+ if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
+ mSsidPref.setVisible(true);
+ mSsidPref.setSummary(mAccessPoint.getSsidStr());
+ } else {
+ mSsidPref.setVisible(false);
+ }
}
- private void refreshRssiViews() {
- int iconSignalLevel = WifiManager.calculateSignalLevel(
- mRssi, WifiManager.RSSI_LEVELS);
- Drawable wifiIcon = NetworkBadging.getWifiIcon(
- iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
+ private void refreshMacAddress() {
+ String macAddress = getMacAddress();
+ if (macAddress == null) {
+ mMacAddressPref.setVisible(false);
+ return;
+ }
- wifiIcon.setTint(Utils.getColorAccent(mContext));
- mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
+ mMacAddressPref.setVisible(true);
+ mMacAddressPref.setSummary(macAddress);
+ }
- Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
- wifiIconDark.setTint(mContext.getResources().getColor(
- R.color.wifi_details_icon_color, mContext.getTheme()));
- mSignalStrengthPref.setIcon(wifiIconDark);
+ private String getMacAddress() {
+ if (mWifiInfo != null) {
+ // get MAC address from connected network information
+ return mWifiInfo.getMacAddress();
+ }
+
+ // return randomized MAC address
+ if (mWifiConfig != null &&
+ mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+ return mWifiConfig.getRandomizedMacAddress().toString();
+ }
- int summarySignalLevel = mAccessPoint.getLevel();
- mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
+ // return device MAC address
+ final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
+ if (macAddresses != null && macAddresses.length > 0) {
+ return macAddresses[0];
+ }
+
+ Log.e(TAG, "Can't get device MAC address!");
+ return null;
}
- private void updatePreference(WifiDetailPreference pref, String detailText) {
+ private void updatePreference(Preference pref, String detailText) {
if (!TextUtils.isEmpty(detailText)) {
- pref.setDetailText(detailText);
+ pref.setSummary(detailText);
pref.setVisible(true);
} else {
pref.setVisible(false);
}
}
- private void updateIpLayerInfo() {
- mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
- mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
- || mSignInButton.getVisibility() == View.VISIBLE);
+ private void refreshButtons() {
+ // Ephemeral network won't be removed permanently, but be putted in blacklist.
+ mButtonsPref.setButton1Text(
+ mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
+
+ boolean canForgetNetwork = canForgetNetwork();
+ boolean canSignIntoNetwork = canSignIntoNetwork();
+ boolean canConnectNetwork = canConnectNetwork();
+ boolean canShareNetwork = canShareNetwork();
- if (mNetwork == null || mLinkProperties == null) {
+ mButtonsPref.setButton1Visible(canForgetNetwork);
+ mButtonsPref.setButton2Visible(canSignIntoNetwork);
+ mButtonsPref.setButton3Visible(canConnectNetwork);
+ mButtonsPref.setButton4Visible(canShareNetwork);
+ mButtonsPref.setVisible(canForgetNetwork
+ || canSignIntoNetwork
+ || canConnectNetwork
+ || canShareNetwork);
+ }
+
+ private boolean canConnectNetwork() {
+ // Display connect button for disconnected AP even not in the range.
+ return !mAccessPoint.isActive();
+ }
+
+ private void refreshIpLayerInfo() {
+ // Hide IP layer info if not a connected network.
+ if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
mIpAddressPref.setVisible(false);
mSubnetPref.setVisible(false);
mGatewayPref.setVisible(false);
}
}
- // Find IPv4 DNS addresses.
+ // Find all (IPv4 and IPv6) DNS addresses.
String dnsServers = mLinkProperties.getDnsServers().stream()
- .filter(Inet4Address.class::isInstance)
.map(InetAddress::getHostAddress)
- .collect(Collectors.joining(","));
+ .collect(Collectors.joining("\n"));
// Update UI.
updatePreference(mIpAddressPref, ipv4Address);
updatePreference(mDnsPref, dnsServers);
if (ipv6Addresses.length() > 0) {
- mIpv6AddressPref.setSummary(ipv6Addresses.toString());
+ mIpv6AddressPref.setSummary(
+ BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
mIpv6Category.setVisible(true);
} else {
mIpv6Category.setVisible(false);
* Returns whether the network represented by this preference can be forgotten.
*/
private boolean canForgetNetwork() {
- return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null;
+ return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork()
+ || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig();
+ }
+
+ /**
+ * Returns whether the network represented by this preference can be modified.
+ */
+ public boolean canModifyNetwork() {
+ return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
}
/**
* Returns whether the user can sign into the network represented by this preference.
*/
private boolean canSignIntoNetwork() {
- return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
- NET_CAPABILITY_CAPTIVE_PORTAL);
+ return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
+ }
+
+ /**
+ * Returns whether the user can share the network represented by this preference with QR code.
+ */
+ private boolean canShareNetwork() {
+ return mAccessPoint.getConfig() != null &&
+ WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint);
}
/**
private void forgetNetwork() {
if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
- } else if (mWifiConfig != null) {
- if (mWifiConfig.isPasspoint()) {
- mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
- } else {
- mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
+ } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
+ // Post a dialog to confirm if user really want to forget the passpoint network.
+ if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) {
+ showConfirmForgetDialog();
+ return;
}
+
+ try {
+ mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Failed to remove Passpoint configuration for "
+ + mAccessPoint.getPasspointFqdn());
+ }
+ } else if (mWifiConfig != null) {
+ mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
}
+
mMetricsFeatureProvider.action(
- mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
+ mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
mFragment.getActivity().finish();
}
+ @VisibleForTesting
+ protected void showConfirmForgetDialog() {
+ final AlertDialog dialog = new AlertDialog.Builder(mContext)
+ .setPositiveButton(R.string.forget, ((dialog1, which) -> {
+ try {
+ mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Failed to remove Passpoint configuration for "
+ + mAccessPoint.getPasspointFqdn());
+ }
+ mMetricsFeatureProvider.action(
+ mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
+ mFragment.getActivity().finish();
+ }))
+ .setNegativeButton(R.string.cancel, null /* listener */)
+ .setTitle(R.string.wifi_forget_dialog_title)
+ .setMessage(R.string.forget_passpoint_dialog_message)
+ .create();
+ dialog.show();
+ }
+
+ /**
+ * Show QR code to share the network represented by this preference.
+ */
+ private void launchWifiDppConfiguratorActivity() {
+ final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext,
+ mWifiManager, mAccessPoint);
+
+ if (intent == null) {
+ Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!");
+ } else {
+ mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
+ SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE,
+ SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
+ /* key */ null,
+ /* value */ Integer.MIN_VALUE);
+
+ mContext.startActivity(intent);
+ }
+ }
+
+ /**
+ * Share the wifi network with QR code.
+ */
+ private void shareNetwork() {
+ WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
+ }
+
/**
* Sign in to the captive portal found on this wifi network associated with this preference.
*/
private void signIntoNetwork() {
mMetricsFeatureProvider.action(
- mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
- mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork);
+ mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN);
+ mConnectivityManager.startCaptivePortalApp(mNetwork);
+ }
+
+ @Override
+ public void onSubmit(WifiDialog dialog) {
+ if (dialog.getController() != null) {
+ mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Activity activity = mFragment.getActivity();
+ if (activity != null) {
+ Toast.makeText(activity,
+ R.string.wifi_failed_save_message,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Wrapper for testing compatibility.
+ */
+ @VisibleForTesting
+ static class IconInjector {
+ private final Context mContext;
+
+ public IconInjector(Context context) {
+ mContext = context;
+ }
+
+ public Drawable getIcon(int level) {
+ return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
+ }
+ }
+
+ private boolean usingDataUsageHeader(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
+ }
+
+ @VisibleForTesting
+ void connectNetwork() {
+ final Activity activity = mFragment.getActivity();
+ // error handling, connected/saved network should have mWifiConfig.
+ if (mWifiConfig == null) {
+ Toast.makeText(activity,
+ R.string.wifi_failed_connect_message,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // init state before connect
+ mConnectingState = STATE_NONE;
+
+ if (mWifiManager.isWifiEnabled()) {
+ updateConnectingState(STATE_CONNECTING);
+ } else {
+ // Enable Wi-Fi automatically to connect AP
+ updateConnectingState(STATE_ENABLE_WIFI);
+ }
+ }
+
+ private void updateConnectingState(int state) {
+ final Activity activity = mFragment.getActivity();
+ Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state);
+ switch (mConnectingState) {
+ case STATE_NONE:
+ case STATE_ENABLE_WIFI:
+ if (state == STATE_ENABLE_WIFI) {
+ Log.d(TAG, "Turn on Wi-Fi automatically!");
+ updateConnectedButton(STATE_ENABLE_WIFI);
+ Toast.makeText(activity,
+ R.string.wifi_turned_on_message,
+ Toast.LENGTH_SHORT).show();
+ mWifiManager.setWifiEnabled(true);
+ // start timer for error handling
+ startTimer();
+ } else if (state == STATE_CONNECTING) {
+ Log.d(TAG, "connecting...");
+ updateConnectedButton(STATE_CONNECTING);
+ if (mAccessPoint.isPasspoint()) {
+ mWifiManager.connect(mWifiConfig, mConnectListener);
+ } else {
+ mWifiManager.connect(mWifiConfig.networkId, mConnectListener);
+ }
+ // start timer for error handling since framework didn't call back if failed
+ startTimer();
+ } else if (state == STATE_ENABLE_WIFI_FAILED) {
+ Log.e(TAG, "Wi-Fi failed to enable network!");
+ stopTimer();
+ // reset state
+ state = STATE_NONE;
+ Toast.makeText(activity,
+ R.string.wifi_failed_connect_message,
+ Toast.LENGTH_SHORT).show();
+ updateConnectedButton(STATE_ENABLE_WIFI_FAILED);
+ }
+ // Do not break here for disconnected event.
+ case STATE_CONNECTED:
+ if (state == STATE_DISCONNECTED) {
+ Log.d(TAG, "disconnected");
+ // reset state
+ state = STATE_NONE;
+ updateConnectedButton(STATE_DISCONNECTED);
+ refreshPage();
+ // clear for getting MAC Address from saved configuration
+ mWifiInfo = null;
+ }
+ break;
+ case STATE_CONNECTING:
+ if (state == STATE_CONNECTED) {
+ Log.d(TAG, "connected");
+ stopTimer();
+ updateConnectedButton(STATE_CONNECTED);
+ Toast.makeText(activity,
+ mContext.getString(R.string.wifi_connected_to_message,
+ mAccessPoint.getTitle()),
+ Toast.LENGTH_SHORT).show();
+
+ refreshPage();
+ } else if (state == STATE_NOT_IN_RANGE) {
+ Log.d(TAG, "AP not in range");
+ stopTimer();
+ // reset state
+ state = STATE_NONE;
+ Toast.makeText(activity,
+ R.string.wifi_not_in_range_message,
+ Toast.LENGTH_SHORT).show();
+ updateConnectedButton(STATE_NOT_IN_RANGE);
+ } else if (state == STATE_FAILED) {
+ Log.d(TAG, "failed");
+ stopTimer();
+ // reset state
+ state = STATE_NONE;
+ Toast.makeText(activity,
+ R.string.wifi_failed_connect_message,
+ Toast.LENGTH_SHORT).show();
+ updateConnectedButton(STATE_FAILED);
+ }
+ break;
+ default:
+ Log.e(TAG, "Invalid state : " + mConnectingState);
+ // don't update invalid state
+ return;
+ }
+
+ mConnectingState = state;
+ }
+
+ private void updateConnectedButton(int state) {
+ switch (state) {
+ case STATE_ENABLE_WIFI:
+ case STATE_CONNECTING:
+ mButtonsPref.setButton3Text(R.string.wifi_connecting)
+ .setButton3Enabled(false);
+ break;
+ case STATE_CONNECTED:
+ // init button state and set as invisible
+ mButtonsPref.setButton3Text(R.string.wifi_connect)
+ .setButton3Icon(R.drawable.ic_settings_wireless)
+ .setButton3Enabled(true)
+ .setButton3Visible(false);
+ break;
+ case STATE_DISCONNECTED:
+ case STATE_NOT_IN_RANGE:
+ case STATE_FAILED:
+ case STATE_ENABLE_WIFI_FAILED:
+ mButtonsPref.setButton3Text(R.string.wifi_connect)
+ .setButton3Icon(R.drawable.ic_settings_wireless)
+ .setButton3Enabled(true)
+ .setButton3Visible(true);
+ break;
+ default:
+ Log.e(TAG, "Invalid connect button state : " + state);
+ break;
+ }
+ }
+
+ private void startTimer() {
+ if (mTimer != null) {
+ stopTimer();
+ }
+
+ mTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ // Do nothing
+ }
+ @Override
+ public void onFinish() {
+ if (mFragment == null || mFragment.getActivity() == null) {
+ Log.d(TAG, "Ignore timeout since activity not exist!");
+ return;
+ }
+ Log.e(TAG, "Timeout for state:" + mConnectingState);
+ if (mConnectingState == STATE_ENABLE_WIFI) {
+ updateConnectingState(STATE_ENABLE_WIFI_FAILED);
+ } else if (mConnectingState == STATE_CONNECTING) {
+ updateAccessPointFromScannedList();
+ if (mIsOutOfRange) {
+ updateConnectingState(STATE_NOT_IN_RANGE);
+ } else {
+ updateConnectingState(STATE_FAILED);
+ }
+ }
+ }
+ };
+ mTimer.start();
+ }
+
+ private void stopTimer() {
+ if (mTimer == null) return;
+
+ mTimer.cancel();
+ mTimer = null;
}
}