2 * Copyright (C) 2017 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.android.settings.wifi.details;
18 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
23 import android.app.Activity;
24 import android.app.AlertDialog;
25 import android.app.settings.SettingsEnums;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.graphics.Bitmap;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.graphics.drawable.VectorDrawable;
34 import android.net.ConnectivityManager;
35 import android.net.ConnectivityManager.NetworkCallback;
36 import android.net.LinkAddress;
37 import android.net.LinkProperties;
38 import android.net.Network;
39 import android.net.NetworkCapabilities;
40 import android.net.NetworkInfo;
41 import android.net.NetworkRequest;
42 import android.net.NetworkUtils;
43 import android.net.RouteInfo;
44 import android.net.wifi.WifiConfiguration;
45 import android.net.wifi.WifiInfo;
46 import android.net.wifi.WifiManager;
47 import android.os.CountDownTimer;
48 import android.os.Handler;
49 import android.text.TextUtils;
50 import android.util.FeatureFlagUtils;
51 import android.util.Log;
52 import android.widget.ImageView;
53 import android.widget.Toast;
55 import androidx.annotation.VisibleForTesting;
56 import androidx.core.text.BidiFormatter;
57 import androidx.preference.Preference;
58 import androidx.preference.PreferenceCategory;
59 import androidx.preference.PreferenceFragmentCompat;
60 import androidx.preference.PreferenceScreen;
62 import com.android.settings.R;
63 import com.android.settings.Utils;
64 import com.android.settings.core.FeatureFlags;
65 import com.android.settings.core.PreferenceControllerMixin;
66 import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
67 import com.android.settings.development.featureflags.FeatureFlagPersistent;
68 import com.android.settings.widget.EntityHeaderController;
69 import com.android.settings.wifi.WifiDialog;
70 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
71 import com.android.settings.wifi.WifiUtils;
72 import com.android.settings.wifi.dpp.WifiDppUtils;
73 import com.android.settingslib.core.AbstractPreferenceController;
74 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
75 import com.android.settingslib.core.lifecycle.Lifecycle;
76 import com.android.settingslib.core.lifecycle.LifecycleObserver;
77 import com.android.settingslib.core.lifecycle.events.OnPause;
78 import com.android.settingslib.core.lifecycle.events.OnResume;
79 import com.android.settingslib.widget.ActionButtonsPreference;
80 import com.android.settingslib.widget.LayoutPreference;
81 import com.android.settingslib.wifi.AccessPoint;
82 import com.android.settingslib.wifi.WifiTracker;
83 import com.android.settingslib.wifi.WifiTrackerFactory;
85 import java.net.Inet4Address;
86 import java.net.Inet6Address;
87 import java.net.InetAddress;
88 import java.net.UnknownHostException;
89 import java.time.Duration;
90 import java.util.StringJoiner;
91 import java.util.stream.Collectors;
94 * Controller for logic pertaining to displaying Wifi information for the
95 * {@link WifiNetworkDetailsFragment}.
97 public class WifiDetailPreferenceController extends AbstractPreferenceController
98 implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
101 private static final String TAG = "WifiDetailsPrefCtrl";
102 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
105 static final String KEY_HEADER = "connection_header";
107 static final String KEY_DATA_USAGE_HEADER = "status_header";
109 static final String KEY_BUTTONS_PREF = "buttons";
111 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
113 static final String KEY_TX_LINK_SPEED = "tx_link_speed";
115 static final String KEY_RX_LINK_SPEED = "rx_link_speed";
117 static final String KEY_FREQUENCY_PREF = "frequency";
119 static final String KEY_SECURITY_PREF = "security";
121 static final String KEY_SSID_PREF = "ssid";
123 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
125 static final String KEY_IP_ADDRESS_PREF = "ip_address";
127 static final String KEY_GATEWAY_PREF = "gateway";
129 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
131 static final String KEY_DNS_PREF = "dns";
133 static final String KEY_IPV6_CATEGORY = "ipv6_category";
135 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
137 private static final int STATE_NONE = 1;
138 private static final int STATE_ENABLE_WIFI = 2;
139 private static final int STATE_ENABLE_WIFI_FAILED = 3;
140 private static final int STATE_CONNECTING = 4;
141 private static final int STATE_CONNECTED = 5;
142 private static final int STATE_FAILED = 6;
143 private static final int STATE_NOT_IN_RANGE = 7;
144 private static final int STATE_DISCONNECTED = 8;
145 private static final long TIMEOUT = Duration.ofSeconds(10).toMillis();
147 // Be static to avoid too much object not be reset.
149 static CountDownTimer mTimer;
151 private AccessPoint mAccessPoint;
152 private final ConnectivityManager mConnectivityManager;
153 private final PreferenceFragmentCompat mFragment;
154 private final Handler mHandler;
155 private LinkProperties mLinkProperties;
156 private Network mNetwork;
157 private NetworkInfo mNetworkInfo;
158 private NetworkCapabilities mNetworkCapabilities;
159 private int mRssiSignalLevel = -1;
160 private String[] mSignalStr;
161 private WifiConfiguration mWifiConfig;
162 private WifiInfo mWifiInfo;
163 private final WifiManager mWifiManager;
164 private final WifiTracker mWifiTracker;
165 private final MetricsFeatureProvider mMetricsFeatureProvider;
166 private boolean mIsOutOfRange;
167 private boolean mIsEphemeral;
168 private boolean mConnected;
169 private int mConnectingState;
170 private WifiManager.ActionListener mConnectListener;
172 // UI elements - in order of appearance
173 private ActionButtonsPreference mButtonsPref;
174 private EntityHeaderController mEntityHeaderController;
175 private Preference mSignalStrengthPref;
176 private Preference mTxLinkSpeedPref;
177 private Preference mRxLinkSpeedPref;
178 private Preference mFrequencyPref;
179 private Preference mSecurityPref;
180 private Preference mSsidPref;
181 private Preference mMacAddressPref;
182 private Preference mIpAddressPref;
183 private Preference mGatewayPref;
184 private Preference mSubnetPref;
185 private Preference mDnsPref;
186 private PreferenceCategory mIpv6Category;
187 private Preference mIpv6AddressPref;
188 private Lifecycle mLifecycle;
189 Preference mDataUsageSummaryPref;
190 WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
192 private final IconInjector mIconInjector;
193 private final IntentFilter mFilter;
194 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
196 public void onReceive(Context context, Intent intent) {
197 switch (intent.getAction()) {
198 case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
199 if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
200 false /* defaultValue */)) {
201 // only one network changed
202 WifiConfiguration wifiConfiguration = intent
203 .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
204 if (mAccessPoint.matches(wifiConfiguration)) {
205 mWifiConfig = wifiConfiguration;
209 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
210 case WifiManager.RSSI_CHANGED_ACTION:
217 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
218 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
220 // Must be run on the UI thread since it directly manipulates UI state.
221 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
223 public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
224 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
225 mLinkProperties = lp;
226 refreshIpLayerInfo();
230 private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
231 // If this is the first time we get NetworkCapabilities, report that something changed.
232 if (mNetworkCapabilities == null) return true;
234 // nc can never be null, see ConnectivityService#callCallbackForRequest.
235 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
239 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
240 // If the network just validated or lost Internet access or detected partial internet
241 // connectivity, refresh network state. Don't do this on every NetworkCapabilities
242 // change because refreshNetworkState sends IPCs to the system server from the UI
243 // thread, which can cause jank.
244 if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
245 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED)
246 || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)
247 || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
248 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
249 refreshEntityHeader();
251 mNetworkCapabilities = nc;
253 refreshIpLayerInfo();
258 public void onLost(Network network) {
259 // Ephemeral network not a saved network, leave detail page once disconnected
260 if (mIsEphemeral && network.equals(mNetwork)) {
267 final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
268 /** Called when the state of Wifi has changed. */
269 public void onWifiStateChanged(int state) {
270 Log.d(TAG, "onWifiStateChanged(" + state + ")");
271 if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) {
272 updateConnectingState(STATE_CONNECTING);
273 } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) {
274 // update as disconnected once Wi-Fi disabled since may not received
275 // onConnectedChanged for this case.
276 updateConnectingState(STATE_DISCONNECTED);
280 /** Called when the connection state of wifi has changed. */
281 public void onConnectedChanged() {
286 * Called to indicate the list of AccessPoints has been updated and
287 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
289 public void onAccessPointsChanged() {
294 public static WifiDetailPreferenceController newInstance(
295 AccessPoint accessPoint,
296 ConnectivityManager connectivityManager,
298 PreferenceFragmentCompat fragment,
301 WifiManager wifiManager,
302 MetricsFeatureProvider metricsFeatureProvider) {
303 return new WifiDetailPreferenceController(
304 accessPoint, connectivityManager, context, fragment, handler, lifecycle,
305 wifiManager, metricsFeatureProvider, new IconInjector(context));
309 /* package */ WifiDetailPreferenceController(
310 AccessPoint accessPoint,
311 ConnectivityManager connectivityManager,
313 PreferenceFragmentCompat fragment,
316 WifiManager wifiManager,
317 MetricsFeatureProvider metricsFeatureProvider,
318 IconInjector injector) {
321 mAccessPoint = accessPoint;
322 mConnectivityManager = connectivityManager;
323 mFragment = fragment;
325 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
326 mWifiConfig = accessPoint.getConfig();
327 mWifiManager = wifiManager;
328 mMetricsFeatureProvider = metricsFeatureProvider;
329 mIconInjector = injector;
331 mFilter = new IntentFilter();
332 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
333 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
334 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
336 mLifecycle = lifecycle;
337 lifecycle.addObserver(this);
339 mWifiTracker = WifiTrackerFactory.create(
340 mFragment.getActivity(),
343 true /*includeSaved*/,
344 true /*includeScans*/);
345 mConnected = mAccessPoint.isActive();
346 // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we
347 // could not check if the AccessPoint is ephemeral. Need to cache it in first.
348 mIsEphemeral = mAccessPoint.isEphemeral();
349 mConnectingState = STATE_NONE;
350 mConnectListener = new WifiManager.ActionListener() {
352 public void onSuccess() {
357 public void onFailure(int reason) {
358 updateConnectingState(STATE_FAILED);
364 public boolean isAvailable() {
369 public String getPreferenceKey() {
370 // Returns null since this controller contains more than one Preference
375 public void displayPreference(PreferenceScreen screen) {
376 super.displayPreference(screen);
378 setupEntityHeader(screen);
380 mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF))
381 .setButton1Text(R.string.forget)
382 .setButton1Icon(R.drawable.ic_settings_delete)
383 .setButton1OnClickListener(view -> forgetNetwork())
384 .setButton2Text(R.string.wifi_sign_in_button_text)
385 .setButton2Icon(R.drawable.ic_settings_sign_in)
386 .setButton2OnClickListener(view -> signIntoNetwork())
387 .setButton3Text(R.string.wifi_connect)
388 .setButton3Icon(R.drawable.ic_settings_wireless)
389 .setButton3OnClickListener(view -> connectNetwork())
390 .setButton3Enabled(true)
391 .setButton4Text(R.string.share)
392 .setButton4Icon(R.drawable.ic_qrcode_24dp)
393 .setButton4OnClickListener(view -> shareNetwork());
395 mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
396 mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
397 mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED);
398 mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF);
399 mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
401 mSsidPref = screen.findPreference(KEY_SSID_PREF);
402 mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
403 mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
404 mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
405 mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
406 mDnsPref = screen.findPreference(KEY_DNS_PREF);
408 mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
409 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
411 mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
414 private void setupEntityHeader(PreferenceScreen screen) {
415 LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
417 if (usingDataUsageHeader(mContext)) {
418 headerPref.setVisible(false);
419 mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER);
420 mDataUsageSummaryPref.setVisible(true);
421 mSummaryHeaderController =
422 new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(),
423 mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid());
427 mEntityHeaderController =
428 EntityHeaderController.newInstance(
429 mFragment.getActivity(), mFragment,
430 headerPref.findViewById(R.id.entity_header));
432 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
434 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
436 mEntityHeaderController.setLabel(mAccessPoint.getTitle());
439 private void refreshEntityHeader() {
440 if (usingDataUsageHeader(mContext)) {
441 mSummaryHeaderController.updateState(mDataUsageSummaryPref);
443 mEntityHeaderController
445 mAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
446 .setRecyclerView(mFragment.getListView(), mLifecycle)
447 .done(mFragment.getActivity(), true /* rebind */);
451 private void updateNetworkInfo() {
452 mNetwork = mWifiManager.getCurrentNetwork();
453 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
454 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
458 public void onResume() {
459 // Ensure mNetwork is set before any callbacks above are delivered, since our
460 // NetworkCallback only looks at changes to mNetwork.
463 mContext.registerReceiver(mReceiver, mFilter);
464 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
469 public void onPause() {
471 mLinkProperties = null;
472 mNetworkCapabilities = null;
475 mContext.unregisterReceiver(mReceiver);
476 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
479 private void refreshPage() {
480 if(!updateAccessPoint()) {
484 Log.d(TAG, "Update UI!");
487 refreshEntityHeader();
492 // Update Connection Header icon and Signal Strength Preference
496 // Transmit Link Speed Pref
498 // Receive Link Speed Pref
500 // IP related information
501 refreshIpLayerInfo();
509 boolean updateAccessPoint() {
510 boolean changed = false;
511 // remember mIsOutOfRange as old before updated
512 boolean oldState = mIsOutOfRange;
513 updateAccessPointFromScannedList();
515 if (mAccessPoint.isActive()) {
517 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
518 mWifiInfo = mWifiManager.getConnectionInfo();
519 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
520 // Once connected, can't get mNetwork immediately, return false and wait for
521 // next time to update UI. also reset {@code mIsOutOfRange}
522 mIsOutOfRange = oldState;
525 changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
528 // signal level changed
529 changed |= mRssiSignalLevel != mAccessPoint.getLevel();
530 // In/Out of range changed
531 changed |= oldState != mIsOutOfRange;
532 // connect state changed
533 if (mConnected != mAccessPoint.isActive()) {
534 mConnected = mAccessPoint.isActive();
536 updateConnectingState(mAccessPoint.isActive() ? STATE_CONNECTED : STATE_DISCONNECTED);
542 private void updateAccessPointFromScannedList() {
543 mIsOutOfRange = true;
545 for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
546 if (mAccessPoint.matches(ap)) {
548 mWifiConfig = ap.getConfig();
549 mIsOutOfRange = !mAccessPoint.isReachable();
555 private void exitActivity() {
557 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
559 mFragment.getActivity().finish();
562 private void refreshRssiViews() {
563 int signalLevel = mAccessPoint.getLevel();
565 // Disappears signal view if not in range. e.g. for saved networks.
567 mSignalStrengthPref.setVisible(false);
568 mRssiSignalLevel = -1;
572 if (mRssiSignalLevel == signalLevel) {
575 mRssiSignalLevel = signalLevel;
576 Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
578 if (mEntityHeaderController != null) {
579 mEntityHeaderController
580 .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(),
584 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
585 wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
586 mSignalStrengthPref.setIcon(wifiIconDark);
588 mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
589 mSignalStrengthPref.setVisible(true);
592 private Drawable redrawIconForHeader(Drawable original) {
593 final int iconSize = mContext.getResources().getDimensionPixelSize(
594 R.dimen.wifi_detail_page_header_image_size);
595 final int actualWidth = original.getMinimumWidth();
596 final int actualHeight = original.getMinimumHeight();
598 if ((actualWidth == iconSize && actualHeight == iconSize)
599 || !VectorDrawable.class.isInstance(original)) {
603 // clear tint list to make sure can set 87% black after enlarge
604 original.setTintList(null);
607 final Bitmap bitmap = Utils.createBitmap(original,
609 iconSize /*height*/);
610 Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap);
612 // config color for 87% black after enlarge
613 newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
618 private void refreshFrequency() {
619 if (mWifiInfo == null) {
620 mFrequencyPref.setVisible(false);
624 final int frequency = mWifiInfo.getFrequency();
626 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
627 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
628 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
629 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
630 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
631 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
633 Log.e(TAG, "Unexpected frequency " + frequency);
634 // Connecting state is unstable, make it disappeared if unexpected
635 if (mConnectingState == STATE_CONNECTING) {
636 mFrequencyPref.setVisible(false);
640 mFrequencyPref.setSummary(band);
641 mFrequencyPref.setVisible(true);
644 private void refreshTxSpeed() {
645 if (mWifiInfo == null) {
646 mTxLinkSpeedPref.setVisible(false);
650 int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
651 mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
652 mTxLinkSpeedPref.setSummary(mContext.getString(
653 R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
656 private void refreshRxSpeed() {
657 if (mWifiInfo == null) {
658 mRxLinkSpeedPref.setVisible(false);
662 int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
663 mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
664 mRxLinkSpeedPref.setSummary(mContext.getString(
665 R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
668 private void refreshSsid() {
669 if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
670 mSsidPref.setVisible(true);
671 mSsidPref.setSummary(mAccessPoint.getSsidStr());
673 mSsidPref.setVisible(false);
677 private void refreshMacAddress() {
678 String macAddress = getMacAddress();
679 if (macAddress == null) {
680 mMacAddressPref.setVisible(false);
684 mMacAddressPref.setVisible(true);
685 mMacAddressPref.setSummary(macAddress);
688 private String getMacAddress() {
689 if (mWifiInfo != null) {
690 // get MAC address from connected network information
691 return mWifiInfo.getMacAddress();
694 // return randomized MAC address
695 if (mWifiConfig != null &&
696 mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
697 return mWifiConfig.getRandomizedMacAddress().toString();
700 // return device MAC address
701 final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
702 if (macAddresses != null && macAddresses.length > 0) {
703 return macAddresses[0];
706 Log.e(TAG, "Can't get device MAC address!");
710 private void updatePreference(Preference pref, String detailText) {
711 if (!TextUtils.isEmpty(detailText)) {
712 pref.setSummary(detailText);
713 pref.setVisible(true);
715 pref.setVisible(false);
719 private void refreshButtons() {
720 // Ephemeral network won't be removed permanently, but be putted in blacklist.
721 mButtonsPref.setButton1Text(
722 mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
724 boolean canForgetNetwork = canForgetNetwork();
725 boolean canSignIntoNetwork = canSignIntoNetwork();
726 boolean canConnectNetwork = canConnectNetwork();
727 boolean canShareNetwork = canShareNetwork();
729 mButtonsPref.setButton1Visible(canForgetNetwork);
730 mButtonsPref.setButton2Visible(canSignIntoNetwork);
731 mButtonsPref.setButton3Visible(canConnectNetwork);
732 mButtonsPref.setButton4Visible(canShareNetwork);
733 mButtonsPref.setVisible(canForgetNetwork
734 || canSignIntoNetwork
739 private boolean canConnectNetwork() {
740 // Display connect button for disconnected AP even not in the range.
741 return !mAccessPoint.isActive();
744 private void refreshIpLayerInfo() {
745 // Hide IP layer info if not a connected network.
746 if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
747 mIpAddressPref.setVisible(false);
748 mSubnetPref.setVisible(false);
749 mGatewayPref.setVisible(false);
750 mDnsPref.setVisible(false);
751 mIpv6Category.setVisible(false);
755 // Find IPv4 and IPv6 addresses.
756 String ipv4Address = null;
757 String subnet = null;
758 StringJoiner ipv6Addresses = new StringJoiner("\n");
760 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
761 if (addr.getAddress() instanceof Inet4Address) {
762 ipv4Address = addr.getAddress().getHostAddress();
763 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
764 } else if (addr.getAddress() instanceof Inet6Address) {
765 ipv6Addresses.add(addr.getAddress().getHostAddress());
769 // Find IPv4 default gateway.
770 String gateway = null;
771 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
772 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
773 gateway = routeInfo.getGateway().getHostAddress();
778 // Find all (IPv4 and IPv6) DNS addresses.
779 String dnsServers = mLinkProperties.getDnsServers().stream()
780 .map(InetAddress::getHostAddress)
781 .collect(Collectors.joining("\n"));
784 updatePreference(mIpAddressPref, ipv4Address);
785 updatePreference(mSubnetPref, subnet);
786 updatePreference(mGatewayPref, gateway);
787 updatePreference(mDnsPref, dnsServers);
789 if (ipv6Addresses.length() > 0) {
790 mIpv6AddressPref.setSummary(
791 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
792 mIpv6Category.setVisible(true);
794 mIpv6Category.setVisible(false);
798 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
800 InetAddress all = InetAddress.getByAddress(
801 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
802 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
803 } catch (UnknownHostException e) {
809 * Returns whether the network represented by this preference can be forgotten.
811 private boolean canForgetNetwork() {
812 return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork()
813 || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig();
817 * Returns whether the network represented by this preference can be modified.
819 public boolean canModifyNetwork() {
820 return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
824 * Returns whether the user can sign into the network represented by this preference.
826 private boolean canSignIntoNetwork() {
827 return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
831 * Returns whether the user can share the network represented by this preference with QR code.
833 private boolean canShareNetwork() {
834 return mAccessPoint.getConfig() != null &&
835 WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint);
839 * Forgets the wifi network associated with this preference.
841 private void forgetNetwork() {
842 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
843 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
844 } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
845 // Post a dialog to confirm if user really want to forget the passpoint network.
846 if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) {
847 showConfirmForgetDialog();
852 mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
853 } catch (RuntimeException e) {
854 Log.e(TAG, "Failed to remove Passpoint configuration for "
855 + mAccessPoint.getPasspointFqdn());
857 } else if (mWifiConfig != null) {
858 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
861 mMetricsFeatureProvider.action(
862 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
863 mFragment.getActivity().finish();
867 protected void showConfirmForgetDialog() {
868 final AlertDialog dialog = new AlertDialog.Builder(mContext)
869 .setPositiveButton(R.string.forget, ((dialog1, which) -> {
871 mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
872 } catch (RuntimeException e) {
873 Log.e(TAG, "Failed to remove Passpoint configuration for "
874 + mAccessPoint.getPasspointFqdn());
876 mMetricsFeatureProvider.action(
877 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
878 mFragment.getActivity().finish();
880 .setNegativeButton(R.string.cancel, null /* listener */)
881 .setTitle(R.string.wifi_forget_dialog_title)
882 .setMessage(R.string.forget_passpoint_dialog_message)
888 * Show QR code to share the network represented by this preference.
890 private void launchWifiDppConfiguratorActivity() {
891 final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext,
892 mWifiManager, mAccessPoint);
894 if (intent == null) {
895 Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!");
897 mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
898 SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE,
899 SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
901 /* value */ Integer.MIN_VALUE);
903 mContext.startActivity(intent);
908 * Share the wifi network with QR code.
910 private void shareNetwork() {
911 WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
915 * Sign in to the captive portal found on this wifi network associated with this preference.
917 private void signIntoNetwork() {
918 mMetricsFeatureProvider.action(
919 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN);
920 mConnectivityManager.startCaptivePortalApp(mNetwork);
924 public void onSubmit(WifiDialog dialog) {
925 if (dialog.getController() != null) {
926 mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
928 public void onSuccess() {
932 public void onFailure(int reason) {
933 Activity activity = mFragment.getActivity();
934 if (activity != null) {
935 Toast.makeText(activity,
936 R.string.wifi_failed_save_message,
937 Toast.LENGTH_SHORT).show();
945 * Wrapper for testing compatibility.
948 static class IconInjector {
949 private final Context mContext;
951 public IconInjector(Context context) {
955 public Drawable getIcon(int level) {
956 return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
960 private boolean usingDataUsageHeader(Context context) {
961 return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
965 void connectNetwork() {
966 final Activity activity = mFragment.getActivity();
967 // error handling, connected/saved network should have mWifiConfig.
968 if (mWifiConfig == null) {
969 Toast.makeText(activity,
970 R.string.wifi_failed_connect_message,
971 Toast.LENGTH_SHORT).show();
975 // init state before connect
976 mConnectingState = STATE_NONE;
978 if (mWifiManager.isWifiEnabled()) {
979 updateConnectingState(STATE_CONNECTING);
981 // Enable Wi-Fi automatically to connect AP
982 updateConnectingState(STATE_ENABLE_WIFI);
986 private void updateConnectingState(int state) {
987 final Activity activity = mFragment.getActivity();
988 Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state);
989 switch (mConnectingState) {
991 case STATE_ENABLE_WIFI:
992 if (state == STATE_ENABLE_WIFI) {
993 Log.d(TAG, "Turn on Wi-Fi automatically!");
994 updateConnectedButton(STATE_ENABLE_WIFI);
995 Toast.makeText(activity,
996 R.string.wifi_turned_on_message,
997 Toast.LENGTH_SHORT).show();
998 mWifiManager.setWifiEnabled(true);
999 // start timer for error handling
1001 } else if (state == STATE_CONNECTING) {
1002 Log.d(TAG, "connecting...");
1003 updateConnectedButton(STATE_CONNECTING);
1004 if (mAccessPoint.isPasspoint()) {
1005 mWifiManager.connect(mWifiConfig, mConnectListener);
1007 mWifiManager.connect(mWifiConfig.networkId, mConnectListener);
1009 // start timer for error handling since framework didn't call back if failed
1011 } else if (state == STATE_ENABLE_WIFI_FAILED) {
1012 Log.e(TAG, "Wi-Fi failed to enable network!");
1016 Toast.makeText(activity,
1017 R.string.wifi_failed_connect_message,
1018 Toast.LENGTH_SHORT).show();
1019 updateConnectedButton(STATE_ENABLE_WIFI_FAILED);
1021 // Do not break here for disconnected event.
1022 case STATE_CONNECTED:
1023 if (state == STATE_DISCONNECTED) {
1024 Log.d(TAG, "disconnected");
1027 updateConnectedButton(STATE_DISCONNECTED);
1029 // clear for getting MAC Address from saved configuration
1033 case STATE_CONNECTING:
1034 if (state == STATE_CONNECTED) {
1035 Log.d(TAG, "connected");
1037 updateConnectedButton(STATE_CONNECTED);
1038 Toast.makeText(activity,
1039 mContext.getString(R.string.wifi_connected_to_message,
1040 mAccessPoint.getTitle()),
1041 Toast.LENGTH_SHORT).show();
1044 } else if (state == STATE_NOT_IN_RANGE) {
1045 Log.d(TAG, "AP not in range");
1049 Toast.makeText(activity,
1050 R.string.wifi_not_in_range_message,
1051 Toast.LENGTH_SHORT).show();
1052 updateConnectedButton(STATE_NOT_IN_RANGE);
1053 } else if (state == STATE_FAILED) {
1054 Log.d(TAG, "failed");
1058 Toast.makeText(activity,
1059 R.string.wifi_failed_connect_message,
1060 Toast.LENGTH_SHORT).show();
1061 updateConnectedButton(STATE_FAILED);
1065 Log.e(TAG, "Invalid state : " + mConnectingState);
1066 // don't update invalid state
1070 mConnectingState = state;
1073 private void updateConnectedButton(int state) {
1075 case STATE_ENABLE_WIFI:
1076 case STATE_CONNECTING:
1077 mButtonsPref.setButton3Text(R.string.wifi_connecting)
1078 .setButton3Enabled(false);
1080 case STATE_CONNECTED:
1081 // init button state and set as invisible
1082 mButtonsPref.setButton3Text(R.string.wifi_connect)
1083 .setButton3Icon(R.drawable.ic_settings_wireless)
1084 .setButton3Enabled(true)
1085 .setButton3Visible(false);
1087 case STATE_DISCONNECTED:
1088 case STATE_NOT_IN_RANGE:
1090 case STATE_ENABLE_WIFI_FAILED:
1091 mButtonsPref.setButton3Text(R.string.wifi_connect)
1092 .setButton3Icon(R.drawable.ic_settings_wireless)
1093 .setButton3Enabled(true)
1094 .setButton3Visible(true);
1097 Log.e(TAG, "Invalid connect button state : " + state);
1102 private void startTimer() {
1103 if (mTimer != null) {
1107 mTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) {
1109 public void onTick(long millisUntilFinished) {
1113 public void onFinish() {
1114 if (mFragment == null || mFragment.getActivity() == null) {
1115 Log.d(TAG, "Ignore timeout since activity not exist!");
1118 Log.e(TAG, "Timeout for state:" + mConnectingState);
1119 if (mConnectingState == STATE_ENABLE_WIFI) {
1120 updateConnectingState(STATE_ENABLE_WIFI_FAILED);
1121 } else if (mConnectingState == STATE_CONNECTING) {
1122 updateAccessPointFromScannedList();
1123 if (mIsOutOfRange) {
1124 updateConnectingState(STATE_NOT_IN_RANGE);
1126 updateConnectingState(STATE_FAILED);
1134 private void stopTimer() {
1135 if (mTimer == null) return;