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_VALIDATED;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
22 import android.app.Activity;
23 import android.app.Fragment;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.graphics.drawable.Drawable;
29 import android.net.ConnectivityManager;
30 import android.net.ConnectivityManager.NetworkCallback;
31 import android.net.LinkAddress;
32 import android.net.LinkProperties;
33 import android.net.Network;
34 import android.net.NetworkCapabilities;
35 import android.net.NetworkInfo;
36 import android.net.NetworkRequest;
37 import android.net.NetworkUtils;
38 import android.net.RouteInfo;
39 import android.net.wifi.WifiConfiguration;
40 import android.net.wifi.WifiInfo;
41 import android.net.wifi.WifiManager;
42 import android.os.Handler;
43 import android.support.v4.text.BidiFormatter;
44 import android.support.v7.preference.Preference;
45 import android.support.v7.preference.PreferenceCategory;
46 import android.support.v7.preference.PreferenceScreen;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.widget.ImageView;
50 import android.widget.Toast;
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.logging.nano.MetricsProto;
54 import com.android.settings.R;
55 import com.android.settings.Utils;
56 import com.android.settings.applications.LayoutPreference;
57 import com.android.settings.core.PreferenceControllerMixin;
58 import com.android.settings.widget.ActionButtonPreference;
59 import com.android.settings.widget.EntityHeaderController;
60 import com.android.settings.wifi.WifiDetailPreference;
61 import com.android.settings.wifi.WifiDialog;
62 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
63 import com.android.settings.wifi.WifiUtils;
64 import com.android.settingslib.core.AbstractPreferenceController;
65 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
66 import com.android.settingslib.core.lifecycle.Lifecycle;
67 import com.android.settingslib.core.lifecycle.LifecycleObserver;
68 import com.android.settingslib.core.lifecycle.events.OnPause;
69 import com.android.settingslib.core.lifecycle.events.OnResume;
70 import com.android.settingslib.wifi.AccessPoint;
72 import java.net.Inet4Address;
73 import java.net.Inet6Address;
74 import java.net.InetAddress;
75 import java.net.UnknownHostException;
76 import java.util.StringJoiner;
77 import java.util.stream.Collectors;
80 * Controller for logic pertaining to displaying Wifi information for the
81 * {@link WifiNetworkDetailsFragment}.
83 public class WifiDetailPreferenceController extends AbstractPreferenceController
84 implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
87 private static final String TAG = "WifiDetailsPrefCtrl";
88 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
91 static final String KEY_HEADER = "connection_header";
93 static final String KEY_BUTTONS_PREF = "buttons";
95 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
97 static final String KEY_LINK_SPEED = "link_speed";
99 static final String KEY_FREQUENCY_PREF = "frequency";
101 static final String KEY_SECURITY_PREF = "security";
103 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
105 static final String KEY_IP_ADDRESS_PREF = "ip_address";
107 static final String KEY_GATEWAY_PREF = "gateway";
109 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
111 static final String KEY_DNS_PREF = "dns";
113 static final String KEY_IPV6_CATEGORY = "ipv6_category";
115 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
117 private AccessPoint mAccessPoint;
118 private final ConnectivityManager mConnectivityManager;
119 private final Fragment mFragment;
120 private final Handler mHandler;
121 private LinkProperties mLinkProperties;
122 private Network mNetwork;
123 private NetworkInfo mNetworkInfo;
124 private NetworkCapabilities mNetworkCapabilities;
125 private int mRssiSignalLevel = -1;
126 private String[] mSignalStr;
127 private WifiConfiguration mWifiConfig;
128 private WifiInfo mWifiInfo;
129 private final WifiManager mWifiManager;
130 private final MetricsFeatureProvider mMetricsFeatureProvider;
132 // UI elements - in order of appearance
133 private ActionButtonPreference mButtonsPref;
134 private EntityHeaderController mEntityHeaderController;
135 private WifiDetailPreference mSignalStrengthPref;
136 private WifiDetailPreference mLinkSpeedPref;
137 private WifiDetailPreference mFrequencyPref;
138 private WifiDetailPreference mSecurityPref;
139 private WifiDetailPreference mMacAddressPref;
140 private WifiDetailPreference mIpAddressPref;
141 private WifiDetailPreference mGatewayPref;
142 private WifiDetailPreference mSubnetPref;
143 private WifiDetailPreference mDnsPref;
144 private PreferenceCategory mIpv6Category;
145 private Preference mIpv6AddressPref;
147 private final IconInjector mIconInjector;
148 private final IntentFilter mFilter;
149 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
151 public void onReceive(Context context, Intent intent) {
152 switch (intent.getAction()) {
153 case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
154 if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
155 false /* defaultValue */)) {
156 // only one network changed
157 WifiConfiguration wifiConfiguration = intent
158 .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
159 if (mAccessPoint.matches(wifiConfiguration)) {
160 mWifiConfig = wifiConfiguration;
164 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
165 case WifiManager.RSSI_CHANGED_ACTION:
172 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
173 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
175 // Must be run on the UI thread since it directly manipulates UI state.
176 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
178 public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
179 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
180 mLinkProperties = lp;
185 private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
186 // If this is the first time we get NetworkCapabilities, report that something changed.
187 if (mNetworkCapabilities == null) return true;
189 // nc can never be null, see ConnectivityService#callCallbackForRequest.
190 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
194 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
195 // If the network just validated or lost Internet access, refresh network state.
196 // Don't do this on every NetworkCapabilities change because refreshNetworkState
197 // sends IPCs to the system server from the UI thread, which can cause jank.
198 if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
199 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
200 hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
201 refreshNetworkState();
203 mNetworkCapabilities = nc;
209 public void onLost(Network network) {
210 if (network.equals(mNetwork)) {
216 public static WifiDetailPreferenceController newInstance(
217 AccessPoint accessPoint,
218 ConnectivityManager connectivityManager,
223 WifiManager wifiManager,
224 MetricsFeatureProvider metricsFeatureProvider) {
225 return new WifiDetailPreferenceController(
226 accessPoint, connectivityManager, context, fragment, handler, lifecycle,
227 wifiManager, metricsFeatureProvider, new IconInjector(context));
231 /* package */ WifiDetailPreferenceController(
232 AccessPoint accessPoint,
233 ConnectivityManager connectivityManager,
238 WifiManager wifiManager,
239 MetricsFeatureProvider metricsFeatureProvider,
240 IconInjector injector) {
243 mAccessPoint = accessPoint;
244 mConnectivityManager = connectivityManager;
245 mFragment = fragment;
247 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
248 mWifiConfig = accessPoint.getConfig();
249 mWifiManager = wifiManager;
250 mMetricsFeatureProvider = metricsFeatureProvider;
251 mIconInjector = injector;
253 mFilter = new IntentFilter();
254 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
255 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
256 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
258 lifecycle.addObserver(this);
262 public boolean isAvailable() {
267 public String getPreferenceKey() {
268 // Returns null since this controller contains more than one Preference
273 public void displayPreference(PreferenceScreen screen) {
274 super.displayPreference(screen);
276 setupEntityHeader(screen);
278 mButtonsPref = ((ActionButtonPreference) screen.findPreference(KEY_BUTTONS_PREF))
279 .setButton1Text(R.string.forget)
280 .setButton1Positive(false)
281 .setButton1OnClickListener(view -> forgetNetwork())
282 .setButton2Text(R.string.wifi_sign_in_button_text)
283 .setButton2Positive(true)
284 .setButton2OnClickListener(view -> signIntoNetwork());
286 mSignalStrengthPref =
287 (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
288 mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
289 mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
290 mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
292 mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
293 mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
294 mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
295 mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
296 mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
298 mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
299 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
301 mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
304 private void setupEntityHeader(PreferenceScreen screen) {
305 LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
306 mEntityHeaderController =
307 EntityHeaderController.newInstance(
308 mFragment.getActivity(), mFragment,
309 headerPref.findViewById(R.id.entity_header));
311 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
312 iconView.setBackground(
313 mContext.getDrawable(R.drawable.ic_settings_widget_background));
314 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
316 mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
320 public void onResume() {
321 // Ensure mNetwork is set before any callbacks above are delivered, since our
322 // NetworkCallback only looks at changes to mNetwork.
323 mNetwork = mWifiManager.getCurrentNetwork();
324 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
325 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
327 mContext.registerReceiver(mReceiver, mFilter);
328 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
333 public void onPause() {
335 mLinkProperties = null;
336 mNetworkCapabilities = null;
339 mContext.unregisterReceiver(mReceiver);
340 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
343 private void updateInfo() {
344 // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
345 // callbacks. mNetwork doesn't change except in onResume.
346 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
347 mWifiInfo = mWifiManager.getConnectionInfo();
348 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
353 // Update whether the forget button should be displayed.
354 mButtonsPref.setButton1Visible(canForgetNetwork());
356 refreshNetworkState();
358 // Update Connection Header icon and Signal Strength Preference
362 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
365 int linkSpeedMbps = mWifiInfo.getLinkSpeed();
366 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
367 mLinkSpeedPref.setDetailText(mContext.getString(
368 R.string.link_speed, mWifiInfo.getLinkSpeed()));
371 final int frequency = mWifiInfo.getFrequency();
373 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
374 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
375 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
376 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
377 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
378 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
380 Log.e(TAG, "Unexpected frequency " + frequency);
382 mFrequencyPref.setDetailText(band);
387 private void exitActivity() {
389 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
391 mFragment.getActivity().finish();
394 private void refreshNetworkState() {
395 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
396 mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
397 .done(mFragment.getActivity(), true /* rebind */);
400 private void refreshRssiViews() {
401 int signalLevel = mAccessPoint.getLevel();
403 if (mRssiSignalLevel == signalLevel) {
406 mRssiSignalLevel = signalLevel;
407 Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
409 wifiIcon.setTint(Utils.getColorAccent(mContext));
410 mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
412 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
413 wifiIconDark.setTint(mContext.getResources().getColor(
414 R.color.wifi_details_icon_color, mContext.getTheme()));
415 mSignalStrengthPref.setIcon(wifiIconDark);
417 mSignalStrengthPref.setDetailText(mSignalStr[mRssiSignalLevel]);
420 private void updatePreference(WifiDetailPreference pref, String detailText) {
421 if (!TextUtils.isEmpty(detailText)) {
422 pref.setDetailText(detailText);
423 pref.setVisible(true);
425 pref.setVisible(false);
429 private void updateIpLayerInfo() {
430 mButtonsPref.setButton2Visible(canSignIntoNetwork());
431 mButtonsPref.setVisible(canSignIntoNetwork() || canForgetNetwork());
433 if (mNetwork == null || mLinkProperties == null) {
434 mIpAddressPref.setVisible(false);
435 mSubnetPref.setVisible(false);
436 mGatewayPref.setVisible(false);
437 mDnsPref.setVisible(false);
438 mIpv6Category.setVisible(false);
442 // Find IPv4 and IPv6 addresses.
443 String ipv4Address = null;
444 String subnet = null;
445 StringJoiner ipv6Addresses = new StringJoiner("\n");
447 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
448 if (addr.getAddress() instanceof Inet4Address) {
449 ipv4Address = addr.getAddress().getHostAddress();
450 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
451 } else if (addr.getAddress() instanceof Inet6Address) {
452 ipv6Addresses.add(addr.getAddress().getHostAddress());
456 // Find IPv4 default gateway.
457 String gateway = null;
458 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
459 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
460 gateway = routeInfo.getGateway().getHostAddress();
465 // Find all (IPv4 and IPv6) DNS addresses.
466 String dnsServers = mLinkProperties.getDnsServers().stream()
467 .map(InetAddress::getHostAddress)
468 .collect(Collectors.joining("\n"));
471 updatePreference(mIpAddressPref, ipv4Address);
472 updatePreference(mSubnetPref, subnet);
473 updatePreference(mGatewayPref, gateway);
474 updatePreference(mDnsPref, dnsServers);
476 if (ipv6Addresses.length() > 0) {
477 mIpv6AddressPref.setSummary(
478 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
479 mIpv6Category.setVisible(true);
481 mIpv6Category.setVisible(false);
485 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
487 InetAddress all = InetAddress.getByAddress(
488 new byte[] {(byte) 255, (byte) 255, (byte) 255, (byte) 255});
489 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
490 } catch (UnknownHostException e) {
496 * Returns whether the network represented by this preference can be forgotten.
498 private boolean canForgetNetwork() {
499 return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork();
503 * Returns whether the network represented by this preference can be modified.
505 public boolean canModifyNetwork() {
506 return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
510 * Returns whether the user can sign into the network represented by this preference.
512 private boolean canSignIntoNetwork() {
513 return WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
517 * Forgets the wifi network associated with this preference.
519 private void forgetNetwork() {
520 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
521 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
522 } else if (mWifiConfig != null) {
523 if (mWifiConfig.isPasspoint()) {
524 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
526 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
529 mMetricsFeatureProvider.action(
530 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
531 mFragment.getActivity().finish();
535 * Sign in to the captive portal found on this wifi network associated with this preference.
537 private void signIntoNetwork() {
538 mMetricsFeatureProvider.action(
539 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
540 mConnectivityManager.startCaptivePortalApp(mNetwork);
544 public void onForget(WifiDialog dialog) {
545 // can't forget network from a 'modify' dialog
549 public void onSubmit(WifiDialog dialog) {
550 if (dialog.getController() != null) {
551 mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
553 public void onSuccess() {
557 public void onFailure(int reason) {
558 Activity activity = mFragment.getActivity();
559 if (activity != null) {
560 Toast.makeText(activity,
561 R.string.wifi_failed_save_message,
562 Toast.LENGTH_SHORT).show();
570 * Wrapper for testing compatibility.
573 static class IconInjector {
574 private final Context mContext;
576 public IconInjector(Context context) {
580 public Drawable getIcon(int level) {
581 return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();