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.Fragment;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.graphics.drawable.Drawable;
28 import android.net.ConnectivityManager;
29 import android.net.ConnectivityManager.NetworkCallback;
30 import android.net.IpPrefix;
31 import android.net.LinkAddress;
32 import android.net.LinkProperties;
33 import android.net.Network;
34 import android.net.NetworkBadging;
35 import android.net.NetworkCapabilities;
36 import android.net.NetworkInfo;
37 import android.net.NetworkRequest;
38 import android.net.NetworkUtils;
39 import android.net.RouteInfo;
40 import android.net.wifi.WifiConfiguration;
41 import android.net.wifi.WifiInfo;
42 import android.net.wifi.WifiManager;
43 import android.os.Handler;
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.view.View;
50 import android.widget.Button;
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.applications.LayoutPreference;
56 import com.android.settings.core.PreferenceController;
57 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
58 import com.android.settings.core.lifecycle.Lifecycle;
59 import com.android.settings.core.lifecycle.LifecycleObserver;
60 import com.android.settings.core.lifecycle.events.OnPause;
61 import com.android.settings.core.lifecycle.events.OnResume;
62 import com.android.settings.vpn2.ConnectivityManagerWrapper;
63 import com.android.settings.wifi.WifiDetailPreference;
64 import com.android.settingslib.wifi.AccessPoint;
66 import java.net.Inet4Address;
67 import java.net.Inet6Address;
68 import java.net.InetAddress;
69 import java.net.UnknownHostException;
70 import java.util.List;
71 import java.util.StringJoiner;
72 import java.util.stream.Collectors;
75 * Controller for logic pertaining to displaying Wifi information for the
76 * {@link WifiNetworkDetailsFragment}.
78 public class WifiDetailPreferenceController extends PreferenceController implements
79 LifecycleObserver, OnPause, OnResume {
80 private static final String TAG = "WifiDetailsPrefCtrl";
81 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
84 static final String KEY_CONNECTION_DETAIL_PREF = "connection_detail";
86 static final String KEY_BUTTONS_PREF = "buttons";
88 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
90 static final String KEY_LINK_SPEED = "link_speed";
92 static final String KEY_FREQUENCY_PREF = "frequency";
94 static final String KEY_SECURITY_PREF = "security";
96 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
98 static final String KEY_IP_ADDRESS_PREF = "ip_address";
100 static final String KEY_GATEWAY_PREF = "gateway";
102 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
104 static final String KEY_DNS_PREF = "dns";
106 static final String KEY_IPV6_CATEGORY = "ipv6_category";
108 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
110 private AccessPoint mAccessPoint;
111 private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
112 private final ConnectivityManager mConnectivityManager;
113 private final Fragment mFragment;
114 private final Handler mHandler;
115 private LinkProperties mLinkProperties;
116 private Network mNetwork;
117 private NetworkInfo mNetworkInfo;
118 private NetworkCapabilities mNetworkCapabilities;
119 private Context mPrefContext;
121 private String[] mSignalStr;
122 private final WifiConfiguration mWifiConfig;
123 private WifiInfo mWifiInfo;
124 private final WifiManager mWifiManager;
125 private final MetricsFeatureProvider mMetricsFeatureProvider;
127 // UI elements - in order of appearance
128 private Preference mConnectionDetailPref;
129 private LayoutPreference mButtonsPref;
130 private Button mForgetButton;
131 private Button mSignInButton;
132 private WifiDetailPreference mSignalStrengthPref;
133 private WifiDetailPreference mLinkSpeedPref;
134 private WifiDetailPreference mFrequencyPref;
135 private WifiDetailPreference mSecurityPref;
136 private WifiDetailPreference mMacAddressPref;
137 private WifiDetailPreference mIpAddressPref;
138 private WifiDetailPreference mGatewayPref;
139 private WifiDetailPreference mSubnetPref;
140 private WifiDetailPreference mDnsPref;
141 private PreferenceCategory mIpv6Category;
142 private Preference mIpv6AddressPref;
144 private final IntentFilter mFilter;
145 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
147 public void onReceive(Context context, Intent intent) {
148 switch (intent.getAction()) {
149 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
150 case WifiManager.RSSI_CHANGED_ACTION:
156 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
157 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
159 // Must be run on the UI thread since it directly manipulates UI state.
160 private final NetworkCallback mNetworkCallback = new NetworkCallback() {
162 public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
163 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
164 mLinkProperties = lp;
169 private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
170 // If this is the first time we get NetworkCapabilities, report that something changed.
171 if (mNetworkCapabilities == null) return true;
173 // nc can never be null, see ConnectivityService#callCallbackForRequest.
174 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
178 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
179 // If the network just validated or lost Internet access, refresh network state.
180 // Don't do this on every NetworkCapabilities change because refreshNetworkState
181 // sends IPCs to the system server from the UI thread, which can cause jank.
182 if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
183 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
184 hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
185 refreshNetworkState();
187 mNetworkCapabilities = nc;
193 public void onLost(Network network) {
194 if (network.equals(mNetwork)) {
200 public WifiDetailPreferenceController(
201 AccessPoint accessPoint,
202 ConnectivityManagerWrapper connectivityManagerWrapper,
207 WifiManager wifiManager,
208 MetricsFeatureProvider metricsFeatureProvider) {
211 mAccessPoint = accessPoint;
212 mConnectivityManager = connectivityManagerWrapper.getConnectivityManager();
213 mConnectivityManagerWrapper = connectivityManagerWrapper;
214 mFragment = fragment;
216 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
217 mWifiConfig = accessPoint.getConfig();
218 mWifiManager = wifiManager;
219 mMetricsFeatureProvider = metricsFeatureProvider;
221 mFilter = new IntentFilter();
222 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
223 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
225 lifecycle.addObserver(this);
229 public boolean isAvailable() {
234 public String getPreferenceKey() {
235 // Returns null since this controller contains more than one Preference
240 public void displayPreference(PreferenceScreen screen) {
241 super.displayPreference(screen);
243 mPrefContext = screen.getPreferenceManager().getContext();
245 mConnectionDetailPref = screen.findPreference(KEY_CONNECTION_DETAIL_PREF);
247 mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
248 mSignInButton = (Button) mButtonsPref.findViewById(R.id.signin_button);
249 mSignInButton.setText(R.string.support_sign_in_button_text);
250 mSignInButton.setOnClickListener(
251 view -> mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork));
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 = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
268 mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
269 mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button);
270 mForgetButton.setText(R.string.forget);
271 mForgetButton.setOnClickListener(view -> forgetNetwork());
275 public void onResume() {
276 // Ensure mNetwork is set before any callbacks above are delivered, since our
277 // NetworkCallback only looks at changes to mNetwork.
278 mNetwork = mWifiManager.getCurrentNetwork();
279 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
280 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
282 mContext.registerReceiver(mReceiver, mFilter);
283 mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
288 public void onPause() {
290 mLinkProperties = null;
291 mNetworkCapabilities = null;
294 mContext.unregisterReceiver(mReceiver);
295 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
298 private void updateInfo() {
299 // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
300 // callbacks. mNetwork doesn't change except in onResume.
301 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
302 mWifiInfo = mWifiManager.getConnectionInfo();
303 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
308 // Update whether the forgot button should be displayed.
309 mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
311 refreshNetworkState();
313 // Update Connection Header icon and Signal Strength Preference
314 mRssi = mWifiInfo.getRssi();
318 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
321 int linkSpeedMbps = mWifiInfo.getLinkSpeed();
322 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
323 mLinkSpeedPref.setDetailText(mContext.getString(
324 R.string.link_speed, mWifiInfo.getLinkSpeed()));
327 final int frequency = mWifiInfo.getFrequency();
329 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
330 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
331 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
332 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
333 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
334 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
336 Log.e(TAG, "Unexpected frequency " + frequency);
338 mFrequencyPref.setDetailText(band);
343 private void exitActivity() {
345 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
347 mFragment.getActivity().finish();
350 private void refreshNetworkState() {
351 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
352 mConnectionDetailPref.setTitle(mAccessPoint.getSettingsSummary());
355 private void refreshRssiViews() {
356 int iconSignalLevel = WifiManager.calculateSignalLevel(
357 mRssi, WifiManager.RSSI_LEVELS);
358 Drawable wifiIcon = NetworkBadging.getWifiIcon(
359 iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
361 mConnectionDetailPref.setIcon(wifiIcon);
363 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
364 wifiIconDark.setTint(mContext.getResources().getColor(
365 R.color.wifi_details_icon_color, mContext.getTheme()));
366 mSignalStrengthPref.setIcon(wifiIconDark);
368 int summarySignalLevel = mAccessPoint.getLevel();
369 mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
372 private void updatePreference(WifiDetailPreference pref, String detailText) {
373 if (!TextUtils.isEmpty(detailText)) {
374 pref.setDetailText(detailText);
375 pref.setVisible(true);
377 pref.setVisible(false);
381 private void updateIpLayerInfo() {
382 mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
383 mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
384 || mSignInButton.getVisibility() == View.VISIBLE);
386 if (mNetwork == null || mLinkProperties == null) {
387 mIpAddressPref.setVisible(false);
388 mSubnetPref.setVisible(false);
389 mGatewayPref.setVisible(false);
390 mDnsPref.setVisible(false);
391 mIpv6Category.setVisible(false);
395 // Find IPv4 and IPv6 addresses.
396 String ipv4Address = null;
397 String subnet = null;
398 StringJoiner ipv6Addresses = new StringJoiner("\n");
400 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
401 if (addr.getAddress() instanceof Inet4Address) {
402 ipv4Address = addr.getAddress().getHostAddress();
403 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
404 } else if (addr.getAddress() instanceof Inet6Address) {
405 ipv6Addresses.add(addr.getAddress().getHostAddress());
409 // Find IPv4 default gateway.
410 String gateway = null;
411 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
412 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
413 gateway = routeInfo.getGateway().getHostAddress();
418 // Find IPv4 DNS addresses.
419 String dnsServers = mLinkProperties.getDnsServers().stream()
420 .filter(Inet4Address.class::isInstance)
421 .map(InetAddress::getHostAddress)
422 .collect(Collectors.joining(","));
425 updatePreference(mIpAddressPref, ipv4Address);
426 updatePreference(mSubnetPref, subnet);
427 updatePreference(mGatewayPref, gateway);
428 updatePreference(mDnsPref, dnsServers);
430 if (ipv6Addresses.length() > 0) {
431 mIpv6AddressPref.setSummary(ipv6Addresses.toString());
432 mIpv6Category.setVisible(true);
434 mIpv6Category.setVisible(false);
438 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
440 InetAddress all = InetAddress.getByAddress(
441 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
442 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
443 } catch (UnknownHostException e) {
449 * Returns whether the network represented by this preference can be forgotten.
451 private boolean canForgetNetwork() {
452 return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null;
456 * Returns whether the user can sign into the network represented by this preference.
458 private boolean canSignIntoNetwork() {
459 return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
460 NET_CAPABILITY_CAPTIVE_PORTAL);
464 * Forgets the wifi network associated with this preference.
466 private void forgetNetwork() {
467 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
468 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
469 } else if (mWifiConfig != null) {
470 if (mWifiConfig.isPasspoint()) {
471 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
473 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
476 mMetricsFeatureProvider.action(
477 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
478 mFragment.getActivity().finish();