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.fragment.app.Fragment;
58 import androidx.preference.Preference;
59 import androidx.preference.PreferenceCategory;
60 import androidx.preference.PreferenceFragmentCompat;
61 import androidx.preference.PreferenceScreen;
63 import com.android.settings.R;
64 import com.android.settings.Utils;
65 import com.android.settings.core.FeatureFlags;
66 import com.android.settings.core.PreferenceControllerMixin;
67 import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
68 import com.android.settings.development.featureflags.FeatureFlagPersistent;
69 import com.android.settings.widget.EntityHeaderController;
70 import com.android.settings.wifi.WifiDialog;
71 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
72 import com.android.settings.wifi.WifiUtils;
73 import com.android.settings.wifi.dpp.WifiDppUtils;
74 import com.android.settingslib.core.AbstractPreferenceController;
75 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
76 import com.android.settingslib.core.lifecycle.Lifecycle;
77 import com.android.settingslib.core.lifecycle.LifecycleObserver;
78 import com.android.settingslib.core.lifecycle.events.OnPause;
79 import com.android.settingslib.core.lifecycle.events.OnResume;
80 import com.android.settingslib.widget.ActionButtonsPreference;
81 import com.android.settingslib.widget.LayoutPreference;
82 import com.android.settingslib.wifi.AccessPoint;
83 import com.android.settingslib.wifi.WifiTracker;
84 import com.android.settingslib.wifi.WifiTrackerFactory;
86 import java.net.Inet4Address;
87 import java.net.Inet6Address;
88 import java.net.InetAddress;
89 import java.net.UnknownHostException;
90 import java.time.Duration;
91 import java.util.StringJoiner;
92 import java.util.stream.Collectors;
95 * Controller for logic pertaining to displaying Wifi information for the
96 * {@link WifiNetworkDetailsFragment}.
98 public class WifiDetailPreferenceController extends AbstractPreferenceController
99 implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
102 private static final String TAG = "WifiDetailsPrefCtrl";
103 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
106 static final String KEY_HEADER = "connection_header";
108 static final String KEY_DATA_USAGE_HEADER = "status_header";
110 static final String KEY_BUTTONS_PREF = "buttons";
112 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
114 static final String KEY_TX_LINK_SPEED = "tx_link_speed";
116 static final String KEY_RX_LINK_SPEED = "rx_link_speed";
118 static final String KEY_FREQUENCY_PREF = "frequency";
120 static final String KEY_SECURITY_PREF = "security";
122 static final String KEY_SSID_PREF = "ssid";
124 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
126 static final String KEY_IP_ADDRESS_PREF = "ip_address";
128 static final String KEY_GATEWAY_PREF = "gateway";
130 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
132 static final String KEY_DNS_PREF = "dns";
134 static final String KEY_IPV6_CATEGORY = "ipv6_category";
136 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
138 private static final int STATE_NONE = 1;
139 private static final int STATE_ENABLE_WIFI = 2;
140 private static final int STATE_ENABLE_WIFI_FAILED = 3;
141 private static final int STATE_CONNECTING = 4;
142 private static final int STATE_CONNECTED = 5;
143 private static final int STATE_FAILED = 6;
144 private static final int STATE_NOT_IN_RANGE = 7;
145 private static final int STATE_DISCONNECTED = 8;
146 private static final long TIMEOUT = Duration.ofSeconds(10).toMillis();
148 // Be static to avoid too much object not be reset.
149 private static CountDownTimer mTimer;
151 private AccessPoint mAccessPoint;
152 private final ConnectivityManager mConnectivityManager;
153 private final Fragment 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 final boolean lostCurrentNetwork = network.equals(mNetwork);
260 if (lostCurrentNetwork) {
261 // Should update as disconnect but not exit. Except for ephemeral network which
262 // should not show on saved network list.
272 private final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
273 /** Called when the state of Wifi has changed. */
274 public void onWifiStateChanged(int state) {
275 Log.d(TAG, "onWifiStateChanged(" + state + ")");
276 if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) {
277 updateConnectingState(STATE_CONNECTING);
278 } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) {
279 // update as disconnected once Wi-Fi disabled since may not received
280 // onConnectedChanged for this case.
281 updateConnectingState(STATE_DISCONNECTED);
285 /** Called when the connection state of wifi has changed. */
286 public void onConnectedChanged() {
287 updateAccessPointFromScannedList();
288 if (mConnected != mAccessPoint.isActive()) {
289 Log.d(TAG, "Connection state changed!");
290 mConnected = mAccessPoint.isActive();
291 if (mAccessPoint.isActive()) {
292 updateConnectingState(STATE_CONNECTED);
294 updateConnectingState(STATE_DISCONNECTED);
300 * Called to indicate the list of AccessPoints has been updated and
301 * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
303 public void onAccessPointsChanged() {
308 public static WifiDetailPreferenceController newInstance(
309 AccessPoint accessPoint,
310 ConnectivityManager connectivityManager,
315 WifiManager wifiManager,
316 MetricsFeatureProvider metricsFeatureProvider) {
317 return new WifiDetailPreferenceController(
318 accessPoint, connectivityManager, context, fragment, handler, lifecycle,
319 wifiManager, metricsFeatureProvider, new IconInjector(context));
323 /* package */ WifiDetailPreferenceController(
324 AccessPoint accessPoint,
325 ConnectivityManager connectivityManager,
330 WifiManager wifiManager,
331 MetricsFeatureProvider metricsFeatureProvider,
332 IconInjector injector) {
335 mAccessPoint = accessPoint;
336 mConnectivityManager = connectivityManager;
337 mFragment = fragment;
339 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
340 mWifiConfig = accessPoint.getConfig();
341 mWifiManager = wifiManager;
342 mMetricsFeatureProvider = metricsFeatureProvider;
343 mIconInjector = injector;
345 mFilter = new IntentFilter();
346 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
347 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
348 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
350 mLifecycle = lifecycle;
351 lifecycle.addObserver(this);
353 mWifiTracker = WifiTrackerFactory.create(
354 mFragment.getActivity(),
357 true /*includeSaved*/,
358 true /*includeScans*/);
359 mConnected = mAccessPoint.isActive();
360 // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we
361 // could not check if the AccessPoint is ephemeral. Need to cache it in first.
362 mIsEphemeral = mAccessPoint.isEphemeral();
363 mConnectingState = STATE_NONE;
364 mConnectListener = new WifiManager.ActionListener() {
366 public void onSuccess() {
371 public void onFailure(int reason) {
372 updateConnectingState(STATE_FAILED);
378 public boolean isAvailable() {
383 public String getPreferenceKey() {
384 // Returns null since this controller contains more than one Preference
389 public void displayPreference(PreferenceScreen screen) {
390 super.displayPreference(screen);
392 setupEntityHeader(screen);
394 mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF))
395 .setButton1Text(R.string.forget)
396 .setButton1Icon(R.drawable.ic_settings_delete)
397 .setButton1OnClickListener(view -> forgetNetwork())
398 .setButton2Text(R.string.wifi_sign_in_button_text)
399 .setButton2Icon(R.drawable.ic_settings_sign_in)
400 .setButton2OnClickListener(view -> signIntoNetwork())
401 .setButton3Text(R.string.wifi_connect)
402 .setButton3Icon(R.drawable.ic_settings_wireless)
403 .setButton3OnClickListener(view -> connectNetwork())
404 .setButton3Enabled(true)
405 .setButton4Text(R.string.share)
406 .setButton4Icon(R.drawable.ic_qrcode_24dp)
407 .setButton4OnClickListener(view -> shareNetwork());
409 mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
410 mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
411 mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED);
412 mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF);
413 mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
415 mSsidPref = screen.findPreference(KEY_SSID_PREF);
416 mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
417 mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
418 mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
419 mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
420 mDnsPref = screen.findPreference(KEY_DNS_PREF);
422 mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
423 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
425 mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
428 private void setupEntityHeader(PreferenceScreen screen) {
429 LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
431 if (usingDataUsageHeader(mContext)) {
432 headerPref.setVisible(false);
433 mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER);
434 mDataUsageSummaryPref.setVisible(true);
435 mSummaryHeaderController =
436 new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(),
437 mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid());
441 mEntityHeaderController =
442 EntityHeaderController.newInstance(
443 mFragment.getActivity(), mFragment,
444 headerPref.findViewById(R.id.entity_header));
446 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
448 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
450 mEntityHeaderController.setLabel(mAccessPoint.getTitle());
453 private void refreshEntityHeader() {
454 if (usingDataUsageHeader(mContext)) {
455 mSummaryHeaderController.updateState(mDataUsageSummaryPref);
457 mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
458 .done(mFragment.getActivity(), true /* rebind */);
462 private void updateNetworkInfo() {
463 mNetwork = mWifiManager.getCurrentNetwork();
464 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
465 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
469 public void onResume() {
470 // Ensure mNetwork is set before any callbacks above are delivered, since our
471 // NetworkCallback only looks at changes to mNetwork.
474 mContext.registerReceiver(mReceiver, mFilter);
475 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
480 public void onPause() {
482 mLinkProperties = null;
483 mNetworkCapabilities = null;
486 mContext.unregisterReceiver(mReceiver);
487 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
490 private void refreshPage() {
491 if(!updateAccessPoint()) {
495 Log.d(TAG, "Update UI!");
498 refreshEntityHeader();
503 // Update Connection Header icon and Signal Strength Preference
507 // Transmit Link Speed Pref
509 // Receive Link Speed Pref
511 // IP related information
512 refreshIpLayerInfo();
519 private boolean updateAccessPoint() {
520 boolean changed = false;
521 if (mWifiTracker != null) {
522 // remember mIsOutOfRange as old before updated
523 boolean oldState = mIsOutOfRange;
524 updateAccessPointFromScannedList();
525 // refresh UI if signal level changed for disconnect network.
526 changed = mRssiSignalLevel != mAccessPoint.getLevel();
527 changed |= oldState != mIsOutOfRange;
530 if (mAccessPoint.isActive()) {
531 // Sometimes {@link WifiManager#getCurrentNetwork()} return null after connected,
532 // refresh it if needed.
533 if (mNetwork == null) {
536 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
537 mWifiInfo = mWifiManager.getConnectionInfo();
538 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
539 // Once connected, can't get mNetworkInfo immediately, return false and wait for
540 // next time to update UI.
544 changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
545 // If feature for saved network not enabled, always return true.
546 return mWifiTracker == null || changed;
552 private void updateAccessPointFromScannedList() {
553 if (mWifiTracker == null) return;
555 mIsOutOfRange = true;
557 for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
558 if (mAccessPoint.matches(ap)) {
560 mWifiConfig = ap.getConfig();
561 mIsOutOfRange = !mAccessPoint.isReachable();
567 private void exitActivity() {
569 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
571 mFragment.getActivity().finish();
574 private void refreshRssiViews() {
575 int signalLevel = mAccessPoint.getLevel();
577 // Disappears signal view if not in range. e.g. for saved networks.
579 mSignalStrengthPref.setVisible(false);
580 mRssiSignalLevel = -1;
584 if (mRssiSignalLevel == signalLevel) {
587 mRssiSignalLevel = signalLevel;
588 Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
590 if (mEntityHeaderController != null) {
591 mEntityHeaderController
592 .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(),
596 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
597 wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
598 mSignalStrengthPref.setIcon(wifiIconDark);
600 mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
601 mSignalStrengthPref.setVisible(true);
604 private Drawable redrawIconForHeader(Drawable original) {
605 final int iconSize = mContext.getResources().getDimensionPixelSize(
606 R.dimen.wifi_detail_page_header_image_size);
607 final int actualWidth = original.getMinimumWidth();
608 final int actualHeight = original.getMinimumHeight();
610 if ((actualWidth == iconSize && actualHeight == iconSize)
611 || !VectorDrawable.class.isInstance(original)) {
615 // clear tint list to make sure can set 87% black after enlarge
616 original.setTintList(null);
619 final Bitmap bitmap = Utils.createBitmap(original,
621 iconSize /*height*/);
622 Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap);
624 // config color for 87% black after enlarge
625 newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
630 private void refreshFrequency() {
631 if (mWifiInfo == null) {
632 mFrequencyPref.setVisible(false);
636 final int frequency = mWifiInfo.getFrequency();
638 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
639 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
640 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
641 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
642 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
643 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
645 Log.e(TAG, "Unexpected frequency " + frequency);
646 // Connecting state is unstable, make it disappeared if unexpected
647 if (mConnectingState == STATE_CONNECTING) {
648 mFrequencyPref.setVisible(false);
652 mFrequencyPref.setSummary(band);
653 mFrequencyPref.setVisible(true);
656 private void refreshTxSpeed() {
657 if (mWifiInfo == null) {
658 mTxLinkSpeedPref.setVisible(false);
662 int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
663 mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
664 mTxLinkSpeedPref.setSummary(mContext.getString(
665 R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
668 private void refreshRxSpeed() {
669 if (mWifiInfo == null) {
670 mRxLinkSpeedPref.setVisible(false);
674 int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
675 mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
676 mRxLinkSpeedPref.setSummary(mContext.getString(
677 R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
680 private void refreshSsid() {
681 if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
682 mSsidPref.setVisible(true);
683 mSsidPref.setSummary(mAccessPoint.getSsidStr());
685 mSsidPref.setVisible(false);
689 private void refreshMacAddress() {
690 String macAddress = getMacAddress();
691 if (macAddress == null) {
692 mMacAddressPref.setVisible(false);
696 mMacAddressPref.setVisible(true);
697 mMacAddressPref.setSummary(macAddress);
700 private String getMacAddress() {
701 if (mWifiInfo != null) {
702 // get MAC address from connected network information
703 return mWifiInfo.getMacAddress();
706 // return randomized MAC address
707 if (mWifiConfig != null &&
708 mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
709 return mWifiConfig.getRandomizedMacAddress().toString();
712 // return device MAC address
713 final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
714 if (macAddresses != null && macAddresses.length > 0) {
715 return macAddresses[0];
718 Log.e(TAG, "Can't get device MAC address!");
722 private void updatePreference(Preference pref, String detailText) {
723 if (!TextUtils.isEmpty(detailText)) {
724 pref.setSummary(detailText);
725 pref.setVisible(true);
727 pref.setVisible(false);
731 private void refreshButtons() {
732 // Ephemeral network won't be removed permanently, but be putted in blacklist.
733 mButtonsPref.setButton1Text(
734 mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
736 boolean canForgetNetwork = canForgetNetwork();
737 boolean canSignIntoNetwork = canSignIntoNetwork();
738 boolean canConnectNetwork = canConnectNetwork();
739 boolean canShareNetwork = canShareNetwork();
741 mButtonsPref.setButton1Visible(canForgetNetwork);
742 mButtonsPref.setButton2Visible(canSignIntoNetwork);
743 mButtonsPref.setButton3Visible(canConnectNetwork);
744 mButtonsPref.setButton4Visible(canShareNetwork);
745 mButtonsPref.setVisible(canForgetNetwork
746 || canSignIntoNetwork
751 private boolean canConnectNetwork() {
752 // Display connect button for disconnected AP even not in the range.
753 return !mAccessPoint.isActive();
756 private void refreshIpLayerInfo() {
757 // Hide IP layer info if not a connected network.
758 if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
759 mIpAddressPref.setVisible(false);
760 mSubnetPref.setVisible(false);
761 mGatewayPref.setVisible(false);
762 mDnsPref.setVisible(false);
763 mIpv6Category.setVisible(false);
767 // Find IPv4 and IPv6 addresses.
768 String ipv4Address = null;
769 String subnet = null;
770 StringJoiner ipv6Addresses = new StringJoiner("\n");
772 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
773 if (addr.getAddress() instanceof Inet4Address) {
774 ipv4Address = addr.getAddress().getHostAddress();
775 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
776 } else if (addr.getAddress() instanceof Inet6Address) {
777 ipv6Addresses.add(addr.getAddress().getHostAddress());
781 // Find IPv4 default gateway.
782 String gateway = null;
783 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
784 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
785 gateway = routeInfo.getGateway().getHostAddress();
790 // Find all (IPv4 and IPv6) DNS addresses.
791 String dnsServers = mLinkProperties.getDnsServers().stream()
792 .map(InetAddress::getHostAddress)
793 .collect(Collectors.joining("\n"));
796 updatePreference(mIpAddressPref, ipv4Address);
797 updatePreference(mSubnetPref, subnet);
798 updatePreference(mGatewayPref, gateway);
799 updatePreference(mDnsPref, dnsServers);
801 if (ipv6Addresses.length() > 0) {
802 mIpv6AddressPref.setSummary(
803 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
804 mIpv6Category.setVisible(true);
806 mIpv6Category.setVisible(false);
810 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
812 InetAddress all = InetAddress.getByAddress(
813 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
814 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
815 } catch (UnknownHostException e) {
821 * Returns whether the network represented by this preference can be forgotten.
823 private boolean canForgetNetwork() {
824 return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork()
825 || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig();
829 * Returns whether the network represented by this preference can be modified.
831 public boolean canModifyNetwork() {
832 return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
836 * Returns whether the user can sign into the network represented by this preference.
838 private boolean canSignIntoNetwork() {
839 return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
843 * Returns whether the user can share the network represented by this preference with QR code.
845 private boolean canShareNetwork() {
846 return mAccessPoint.getConfig() != null &&
847 WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint);
851 * Forgets the wifi network associated with this preference.
853 private void forgetNetwork() {
854 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
855 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
856 } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
857 // Post a dialog to confirm if user really want to forget the passpoint network.
858 if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) {
859 showConfirmForgetDialog();
864 mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
865 } catch (RuntimeException e) {
866 Log.e(TAG, "Failed to remove Passpoint configuration for "
867 + mAccessPoint.getPasspointFqdn());
869 } else if (mWifiConfig != null) {
870 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
873 mMetricsFeatureProvider.action(
874 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
875 mFragment.getActivity().finish();
879 protected void showConfirmForgetDialog() {
880 final AlertDialog dialog = new AlertDialog.Builder(mContext)
881 .setPositiveButton(R.string.forget, ((dialog1, which) -> {
883 mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
884 } catch (RuntimeException e) {
885 Log.e(TAG, "Failed to remove Passpoint configuration for "
886 + mAccessPoint.getPasspointFqdn());
888 mMetricsFeatureProvider.action(
889 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
890 mFragment.getActivity().finish();
892 .setNegativeButton(R.string.cancel, null /* listener */)
893 .setTitle(R.string.wifi_forget_dialog_title)
894 .setMessage(R.string.forget_passpoint_dialog_message)
900 * Show QR code to share the network represented by this preference.
902 public void launchWifiDppConfiguratorActivity() {
903 final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext,
904 mWifiManager, mAccessPoint);
906 if (intent == null) {
907 Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!");
909 mContext.startActivity(intent);
914 * Share the wifi network with QR code.
916 private void shareNetwork() {
917 WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
921 * Sign in to the captive portal found on this wifi network associated with this preference.
923 private void signIntoNetwork() {
924 mMetricsFeatureProvider.action(
925 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN);
926 mConnectivityManager.startCaptivePortalApp(mNetwork);
930 public void onSubmit(WifiDialog dialog) {
931 if (dialog.getController() != null) {
932 mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
934 public void onSuccess() {
938 public void onFailure(int reason) {
939 Activity activity = mFragment.getActivity();
940 if (activity != null) {
941 Toast.makeText(activity,
942 R.string.wifi_failed_save_message,
943 Toast.LENGTH_SHORT).show();
951 * Wrapper for testing compatibility.
954 static class IconInjector {
955 private final Context mContext;
957 public IconInjector(Context context) {
961 public Drawable getIcon(int level) {
962 return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
966 private boolean usingDataUsageHeader(Context context) {
967 return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
970 private void connectNetwork() {
971 final Activity activity = mFragment.getActivity();
972 // error handling, connected/saved network should have mWifiConfig.
973 if (mWifiConfig == null) {
974 Toast.makeText(activity,
975 R.string.wifi_failed_connect_message,
976 Toast.LENGTH_SHORT).show();
980 // init state before connect
981 mConnectingState = STATE_NONE;
983 if (mWifiManager.isWifiEnabled()) {
984 updateConnectingState(STATE_CONNECTING);
986 // Enable Wi-Fi automatically to connect AP
987 updateConnectingState(STATE_ENABLE_WIFI);
991 private void updateConnectingState(int state) {
992 final Activity activity = mFragment.getActivity();
993 Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state);
994 switch (mConnectingState) {
996 case STATE_ENABLE_WIFI:
997 if (state == STATE_ENABLE_WIFI) {
998 Log.d(TAG, "Turn on Wi-Fi automatically!");
999 updateConnectedButton(STATE_ENABLE_WIFI);
1000 Toast.makeText(activity,
1001 R.string.wifi_turned_on_message,
1002 Toast.LENGTH_SHORT).show();
1003 mWifiManager.setWifiEnabled(true);
1004 // start timer for error handling
1006 } else if (state == STATE_CONNECTING) {
1007 Log.d(TAG, "connecting...");
1008 updateConnectedButton(STATE_CONNECTING);
1009 if (mAccessPoint.isPasspoint()) {
1010 mWifiManager.connect(mWifiConfig, mConnectListener);
1012 mWifiManager.connect(mWifiConfig.networkId, mConnectListener);
1014 // start timer for error handling since framework didn't call back if failed
1016 } else if (state == STATE_ENABLE_WIFI_FAILED) {
1017 Log.e(TAG, "Wi-Fi failed to enable network!");
1021 Toast.makeText(activity,
1022 R.string.wifi_failed_connect_message,
1023 Toast.LENGTH_SHORT).show();
1024 updateConnectedButton(STATE_ENABLE_WIFI_FAILED);
1026 // Do not break here for disconnected event.
1027 case STATE_CONNECTED:
1028 if (state == STATE_DISCONNECTED) {
1029 Log.d(TAG, "disconnected");
1032 updateConnectedButton(STATE_DISCONNECTED);
1034 // clear for getting MAC Address from saved configuration
1038 case STATE_CONNECTING:
1039 if (state == STATE_CONNECTED) {
1040 Log.d(TAG, "connected");
1042 updateConnectedButton(STATE_CONNECTED);
1043 Toast.makeText(activity,
1044 mContext.getString(R.string.wifi_connected_to_message,
1045 mAccessPoint.getTitle()),
1046 Toast.LENGTH_SHORT).show();
1048 updateNetworkInfo();
1050 } else if (state == STATE_NOT_IN_RANGE) {
1051 Log.d(TAG, "AP not in range");
1055 Toast.makeText(activity,
1056 R.string.wifi_not_in_range_message,
1057 Toast.LENGTH_SHORT).show();
1058 updateConnectedButton(STATE_NOT_IN_RANGE);
1059 } else if (state == STATE_FAILED) {
1060 Log.d(TAG, "failed");
1064 Toast.makeText(activity,
1065 R.string.wifi_failed_connect_message,
1066 Toast.LENGTH_SHORT).show();
1067 updateConnectedButton(STATE_FAILED);
1071 Log.e(TAG, "Invalid state : " + mConnectingState);
1072 // don't update invalid state
1076 mConnectingState = state;
1079 private void updateConnectedButton(int state) {
1081 case STATE_ENABLE_WIFI:
1082 case STATE_CONNECTING:
1083 mButtonsPref.setButton3Text(R.string.wifi_connecting)
1084 .setButton3Enabled(false);
1086 case STATE_CONNECTED:
1087 mButtonsPref.setButton3Visible(false);
1089 case STATE_DISCONNECTED:
1090 case STATE_NOT_IN_RANGE:
1092 case STATE_ENABLE_WIFI_FAILED:
1093 mButtonsPref.setButton3Text(R.string.wifi_connect)
1094 .setButton3Icon(R.drawable.ic_settings_wireless)
1095 .setButton3Enabled(true)
1096 .setButton3Visible(true);
1099 Log.e(TAG, "Invalid connect button state : " + state);
1104 private void startTimer() {
1105 if (mTimer != null) {
1109 mTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) {
1111 public void onTick(long millisUntilFinished) {
1115 public void onFinish() {
1116 Log.e(TAG, "Timeout for state:" + mConnectingState);
1117 if (mConnectingState == STATE_ENABLE_WIFI) {
1118 updateConnectingState(STATE_ENABLE_WIFI_FAILED);
1119 } else if (mConnectingState == STATE_CONNECTING) {
1120 updateAccessPointFromScannedList();
1121 if (mIsOutOfRange) {
1122 updateConnectingState(STATE_NOT_IN_RANGE);
1124 updateConnectingState(STATE_FAILED);
1132 private void stopTimer() {
1133 if (mTimer == null) return;