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 static com.android.settings.wifi.WifiSettings.isEditabilityLockedDown;
24 import android.app.Fragment;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.graphics.drawable.Drawable;
30 import android.net.ConnectivityManager;
31 import android.net.ConnectivityManager.NetworkCallback;
32 import android.net.LinkAddress;
33 import android.net.LinkProperties;
34 import android.net.Network;
35 import android.net.NetworkBadging;
36 import android.net.NetworkCapabilities;
37 import android.net.NetworkInfo;
38 import android.net.NetworkRequest;
39 import android.net.NetworkUtils;
40 import android.net.RouteInfo;
41 import android.net.wifi.WifiConfiguration;
42 import android.net.wifi.WifiInfo;
43 import android.net.wifi.WifiManager;
44 import android.os.Handler;
45 import android.support.v4.text.BidiFormatter;
46 import android.support.v7.preference.Preference;
47 import android.support.v7.preference.PreferenceCategory;
48 import android.support.v7.preference.PreferenceScreen;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.view.View;
52 import android.widget.Button;
53 import android.widget.ImageView;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.logging.nano.MetricsProto;
57 import com.android.settings.R;
58 import com.android.settings.Utils;
59 import com.android.settings.applications.LayoutPreference;
60 import com.android.settings.core.PreferenceControllerMixin;
61 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
62 import com.android.settings.vpn2.ConnectivityManagerWrapper;
63 import com.android.settings.widget.EntityHeaderController;
64 import com.android.settings.wifi.WifiDetailPreference;
65 import com.android.settingslib.core.AbstractPreferenceController;
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, LifecycleObserver, OnPause, OnResume {
85 private static final String TAG = "WifiDetailsPrefCtrl";
86 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
89 static final String KEY_HEADER = "connection_header";
91 static final String KEY_BUTTONS_PREF = "buttons";
93 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
95 static final String KEY_LINK_SPEED = "link_speed";
97 static final String KEY_FREQUENCY_PREF = "frequency";
99 static final String KEY_SECURITY_PREF = "security";
101 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
103 static final String KEY_IP_ADDRESS_PREF = "ip_address";
105 static final String KEY_GATEWAY_PREF = "gateway";
107 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
109 static final String KEY_DNS_PREF = "dns";
111 static final String KEY_IPV6_CATEGORY = "ipv6_category";
113 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
115 private AccessPoint mAccessPoint;
116 private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
117 private final ConnectivityManager mConnectivityManager;
118 private final Fragment mFragment;
119 private final Handler mHandler;
120 private LinkProperties mLinkProperties;
121 private Network mNetwork;
122 private NetworkInfo mNetworkInfo;
123 private NetworkCapabilities mNetworkCapabilities;
125 private String[] mSignalStr;
126 private final WifiConfiguration mWifiConfig;
127 private WifiInfo mWifiInfo;
128 private final WifiManager mWifiManager;
129 private final MetricsFeatureProvider mMetricsFeatureProvider;
131 // UI elements - in order of appearance
132 private LayoutPreference mButtonsPref;
133 private EntityHeaderController mEntityHeaderController;
134 private Button mForgetButton;
135 private Button mSignInButton;
136 private WifiDetailPreference mSignalStrengthPref;
137 private WifiDetailPreference mLinkSpeedPref;
138 private WifiDetailPreference mFrequencyPref;
139 private WifiDetailPreference mSecurityPref;
140 private WifiDetailPreference mMacAddressPref;
141 private WifiDetailPreference mIpAddressPref;
142 private WifiDetailPreference mGatewayPref;
143 private WifiDetailPreference mSubnetPref;
144 private WifiDetailPreference mDnsPref;
145 private PreferenceCategory mIpv6Category;
146 private Preference mIpv6AddressPref;
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.NETWORK_STATE_CHANGED_ACTION:
154 case WifiManager.RSSI_CHANGED_ACTION:
160 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
161 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
163 // Must be run on the UI thread since it directly manipulates UI state.
164 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
166 public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
167 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
168 mLinkProperties = lp;
173 private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
174 // If this is the first time we get NetworkCapabilities, report that something changed.
175 if (mNetworkCapabilities == null) return true;
177 // nc can never be null, see ConnectivityService#callCallbackForRequest.
178 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
182 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
183 // If the network just validated or lost Internet access, refresh network state.
184 // Don't do this on every NetworkCapabilities change because refreshNetworkState
185 // sends IPCs to the system server from the UI thread, which can cause jank.
186 if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
187 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
188 hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
189 refreshNetworkState();
191 mNetworkCapabilities = nc;
197 public void onLost(Network network) {
198 if (network.equals(mNetwork)) {
204 public WifiDetailPreferenceController(
205 AccessPoint accessPoint,
206 ConnectivityManagerWrapper connectivityManagerWrapper,
211 WifiManager wifiManager,
212 MetricsFeatureProvider metricsFeatureProvider) {
215 mAccessPoint = accessPoint;
216 mConnectivityManager = connectivityManagerWrapper.getConnectivityManager();
217 mConnectivityManagerWrapper = connectivityManagerWrapper;
218 mFragment = fragment;
220 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
221 mWifiConfig = accessPoint.getConfig();
222 mWifiManager = wifiManager;
223 mMetricsFeatureProvider = metricsFeatureProvider;
225 mFilter = new IntentFilter();
226 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
227 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
229 lifecycle.addObserver(this);
233 public boolean isAvailable() {
238 public String getPreferenceKey() {
239 // Returns null since this controller contains more than one Preference
244 public void displayPreference(PreferenceScreen screen) {
245 super.displayPreference(screen);
247 setupEntityHeader(screen);
249 mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
250 mSignInButton = mButtonsPref.findViewById(R.id.signin_button);
251 mSignInButton.setOnClickListener(view -> signIntoNetwork());
253 mSignalStrengthPref =
254 (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
255 mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
256 mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
257 mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
259 mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
260 mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
261 mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
262 mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
263 mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
265 mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
266 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
268 mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
269 mForgetButton = mButtonsPref.findViewById(R.id.forget_button);
270 mForgetButton.setOnClickListener(view -> forgetNetwork());
273 private void setupEntityHeader(PreferenceScreen screen) {
274 LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
275 mEntityHeaderController =
276 EntityHeaderController.newInstance(
277 mFragment.getActivity(), mFragment,
278 headerPref.findViewById(R.id.entity_header));
280 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
281 iconView.setBackground(
282 mContext.getDrawable(R.drawable.ic_settings_widget_background));
283 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
285 mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
289 public void onResume() {
290 // Ensure mNetwork is set before any callbacks above are delivered, since our
291 // NetworkCallback only looks at changes to mNetwork.
292 mNetwork = mWifiManager.getCurrentNetwork();
293 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
294 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
296 mContext.registerReceiver(mReceiver, mFilter);
297 mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
302 public void onPause() {
304 mLinkProperties = null;
305 mNetworkCapabilities = null;
308 mContext.unregisterReceiver(mReceiver);
309 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
312 private void updateInfo() {
313 // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
314 // callbacks. mNetwork doesn't change except in onResume.
315 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
316 mWifiInfo = mWifiManager.getConnectionInfo();
317 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
322 // Update whether the forgot button should be displayed.
323 mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
325 refreshNetworkState();
327 // Update Connection Header icon and Signal Strength Preference
328 mRssi = mWifiInfo.getRssi();
332 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
335 int linkSpeedMbps = mWifiInfo.getLinkSpeed();
336 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
337 mLinkSpeedPref.setDetailText(mContext.getString(
338 R.string.link_speed, mWifiInfo.getLinkSpeed()));
341 final int frequency = mWifiInfo.getFrequency();
343 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
344 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
345 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
346 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
347 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
348 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
350 Log.e(TAG, "Unexpected frequency " + frequency);
352 mFrequencyPref.setDetailText(band);
357 private void exitActivity() {
359 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
361 mFragment.getActivity().finish();
364 private void refreshNetworkState() {
365 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
366 mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
367 .done(mFragment.getActivity(), true /* rebind */);
370 private void refreshRssiViews() {
371 int iconSignalLevel = WifiManager.calculateSignalLevel(
372 mRssi, WifiManager.RSSI_LEVELS);
373 Drawable wifiIcon = NetworkBadging.getWifiIcon(
374 iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
376 wifiIcon.setTint(Utils.getColorAccent(mContext));
377 mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
379 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
380 wifiIconDark.setTint(mContext.getResources().getColor(
381 R.color.wifi_details_icon_color, mContext.getTheme()));
382 mSignalStrengthPref.setIcon(wifiIconDark);
384 int summarySignalLevel = mAccessPoint.getLevel();
385 mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
388 private void updatePreference(WifiDetailPreference pref, String detailText) {
389 if (!TextUtils.isEmpty(detailText)) {
390 pref.setDetailText(detailText);
391 pref.setVisible(true);
393 pref.setVisible(false);
397 private void updateIpLayerInfo() {
398 mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
399 mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
400 || mSignInButton.getVisibility() == View.VISIBLE);
402 if (mNetwork == null || mLinkProperties == null) {
403 mIpAddressPref.setVisible(false);
404 mSubnetPref.setVisible(false);
405 mGatewayPref.setVisible(false);
406 mDnsPref.setVisible(false);
407 mIpv6Category.setVisible(false);
411 // Find IPv4 and IPv6 addresses.
412 String ipv4Address = null;
413 String subnet = null;
414 StringJoiner ipv6Addresses = new StringJoiner("\n");
416 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
417 if (addr.getAddress() instanceof Inet4Address) {
418 ipv4Address = addr.getAddress().getHostAddress();
419 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
420 } else if (addr.getAddress() instanceof Inet6Address) {
421 ipv6Addresses.add(addr.getAddress().getHostAddress());
425 // Find IPv4 default gateway.
426 String gateway = null;
427 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
428 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
429 gateway = routeInfo.getGateway().getHostAddress();
434 // Find all (IPv4 and IPv6) DNS addresses.
435 String dnsServers = mLinkProperties.getDnsServers().stream()
436 .map(InetAddress::getHostAddress)
437 .collect(Collectors.joining("\n"));
440 updatePreference(mIpAddressPref, ipv4Address);
441 updatePreference(mSubnetPref, subnet);
442 updatePreference(mGatewayPref, gateway);
443 updatePreference(mDnsPref, dnsServers);
445 if (ipv6Addresses.length() > 0) {
446 mIpv6AddressPref.setSummary(
447 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
448 mIpv6Category.setVisible(true);
450 mIpv6Category.setVisible(false);
454 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
456 InetAddress all = InetAddress.getByAddress(
457 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
458 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
459 } catch (UnknownHostException e) {
465 * Returns whether the network represented by this preference can be forgotten.
467 private boolean canForgetNetwork() {
468 // TODO(65396674): create test for the locked down scenario
469 return (mWifiInfo != null && mWifiInfo.isEphemeral())
470 || (mWifiConfig != null && !isEditabilityLockedDown(mContext, mWifiConfig));
474 * Returns whether the user can sign into the network represented by this preference.
476 private boolean canSignIntoNetwork() {
477 return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
478 NET_CAPABILITY_CAPTIVE_PORTAL);
482 * Forgets the wifi network associated with this preference.
484 private void forgetNetwork() {
485 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
486 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
487 } else if (mWifiConfig != null) {
488 if (mWifiConfig.isPasspoint()) {
489 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
491 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
494 mMetricsFeatureProvider.action(
495 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
496 mFragment.getActivity().finish();
500 * Sign in to the captive portal found on this wifi network associated with this preference.
502 private void signIntoNetwork() {
503 mMetricsFeatureProvider.action(
504 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
505 mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork);