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.LinkAddress;
31 import android.net.LinkProperties;
32 import android.net.Network;
33 import android.net.NetworkBadging;
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.v7.preference.Preference;
44 import android.support.v7.preference.PreferenceCategory;
45 import android.support.v7.preference.PreferenceScreen;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.view.View;
49 import android.widget.Button;
50 import android.widget.ImageView;
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.PreferenceController;
58 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
59 import com.android.settings.vpn2.ConnectivityManagerWrapper;
60 import com.android.settings.widget.EntityHeaderController;
61 import com.android.settings.wifi.WifiDetailPreference;
62 import com.android.settingslib.core.lifecycle.Lifecycle;
63 import com.android.settingslib.core.lifecycle.LifecycleObserver;
64 import com.android.settingslib.core.lifecycle.events.OnPause;
65 import com.android.settingslib.core.lifecycle.events.OnResume;
66 import com.android.settingslib.wifi.AccessPoint;
68 import java.net.Inet4Address;
69 import java.net.Inet6Address;
70 import java.net.InetAddress;
71 import java.net.UnknownHostException;
72 import java.util.StringJoiner;
73 import java.util.stream.Collectors;
76 * Controller for logic pertaining to displaying Wifi information for the
77 * {@link WifiNetworkDetailsFragment}.
79 public class WifiDetailPreferenceController extends PreferenceController implements
80 LifecycleObserver, OnPause, OnResume {
81 private static final String TAG = "WifiDetailsPrefCtrl";
82 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
85 static final String KEY_HEADER = "connection_header";
87 static final String KEY_BUTTONS_PREF = "buttons";
89 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
91 static final String KEY_LINK_SPEED = "link_speed";
93 static final String KEY_FREQUENCY_PREF = "frequency";
95 static final String KEY_SECURITY_PREF = "security";
97 static final String KEY_MAC_ADDRESS_PREF = "mac_address";
99 static final String KEY_IP_ADDRESS_PREF = "ip_address";
101 static final String KEY_GATEWAY_PREF = "gateway";
103 static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
105 static final String KEY_DNS_PREF = "dns";
107 static final String KEY_IPV6_CATEGORY = "ipv6_category";
109 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
111 private AccessPoint mAccessPoint;
112 private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
113 private final ConnectivityManager mConnectivityManager;
114 private final Fragment mFragment;
115 private final Handler mHandler;
116 private LinkProperties mLinkProperties;
117 private Network mNetwork;
118 private NetworkInfo mNetworkInfo;
119 private NetworkCapabilities mNetworkCapabilities;
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 LayoutPreference mButtonsPref;
129 private EntityHeaderController mEntityHeaderController;
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 setupEntityHeader(screen);
245 mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
246 mSignInButton = mButtonsPref.findViewById(R.id.signin_button);
247 mSignInButton.setText(R.string.support_sign_in_button_text);
248 mSignInButton.setOnClickListener(view -> signIntoNetwork());
250 mSignalStrengthPref =
251 (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
252 mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
253 mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
254 mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
256 mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
257 mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
258 mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
259 mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
260 mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
262 mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
263 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
265 mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
266 mForgetButton = mButtonsPref.findViewById(R.id.forget_button);
267 mForgetButton.setText(R.string.forget);
268 mForgetButton.setOnClickListener(view -> forgetNetwork());
271 private void setupEntityHeader(PreferenceScreen screen) {
272 LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
273 mEntityHeaderController =
274 EntityHeaderController.newInstance(
275 mFragment.getActivity(), mFragment,
276 headerPref.findViewById(R.id.entity_header));
278 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
279 iconView.setBackground(
280 mContext.getDrawable(R.drawable.ic_settings_widget_background));
281 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
283 mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
287 public void onResume() {
288 // Ensure mNetwork is set before any callbacks above are delivered, since our
289 // NetworkCallback only looks at changes to mNetwork.
290 mNetwork = mWifiManager.getCurrentNetwork();
291 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
292 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
294 mContext.registerReceiver(mReceiver, mFilter);
295 mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
300 public void onPause() {
302 mLinkProperties = null;
303 mNetworkCapabilities = null;
306 mContext.unregisterReceiver(mReceiver);
307 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
310 private void updateInfo() {
311 // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
312 // callbacks. mNetwork doesn't change except in onResume.
313 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
314 mWifiInfo = mWifiManager.getConnectionInfo();
315 if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
320 // Update whether the forgot button should be displayed.
321 mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
323 refreshNetworkState();
325 // Update Connection Header icon and Signal Strength Preference
326 mRssi = mWifiInfo.getRssi();
330 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
333 int linkSpeedMbps = mWifiInfo.getLinkSpeed();
334 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
335 mLinkSpeedPref.setDetailText(mContext.getString(
336 R.string.link_speed, mWifiInfo.getLinkSpeed()));
339 final int frequency = mWifiInfo.getFrequency();
341 if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
342 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
343 band = mContext.getResources().getString(R.string.wifi_band_24ghz);
344 } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
345 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
346 band = mContext.getResources().getString(R.string.wifi_band_5ghz);
348 Log.e(TAG, "Unexpected frequency " + frequency);
350 mFrequencyPref.setDetailText(band);
355 private void exitActivity() {
357 Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
359 mFragment.getActivity().finish();
362 private void refreshNetworkState() {
363 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
364 mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
365 .done(mFragment.getActivity(), true /* rebind */);
368 private void refreshRssiViews() {
369 int iconSignalLevel = WifiManager.calculateSignalLevel(
370 mRssi, WifiManager.RSSI_LEVELS);
371 Drawable wifiIcon = NetworkBadging.getWifiIcon(
372 iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
374 wifiIcon.setTint(Utils.getColorAccent(mContext));
375 mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
377 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
378 wifiIconDark.setTint(mContext.getResources().getColor(
379 R.color.wifi_details_icon_color, mContext.getTheme()));
380 mSignalStrengthPref.setIcon(wifiIconDark);
382 int summarySignalLevel = mAccessPoint.getLevel();
383 mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
386 private void updatePreference(WifiDetailPreference pref, String detailText) {
387 if (!TextUtils.isEmpty(detailText)) {
388 pref.setDetailText(detailText);
389 pref.setVisible(true);
391 pref.setVisible(false);
395 private void updateIpLayerInfo() {
396 mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
397 mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
398 || mSignInButton.getVisibility() == View.VISIBLE);
400 if (mNetwork == null || mLinkProperties == null) {
401 mIpAddressPref.setVisible(false);
402 mSubnetPref.setVisible(false);
403 mGatewayPref.setVisible(false);
404 mDnsPref.setVisible(false);
405 mIpv6Category.setVisible(false);
409 // Find IPv4 and IPv6 addresses.
410 String ipv4Address = null;
411 String subnet = null;
412 StringJoiner ipv6Addresses = new StringJoiner("\n");
414 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
415 if (addr.getAddress() instanceof Inet4Address) {
416 ipv4Address = addr.getAddress().getHostAddress();
417 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
418 } else if (addr.getAddress() instanceof Inet6Address) {
419 ipv6Addresses.add(addr.getAddress().getHostAddress());
423 // Find IPv4 default gateway.
424 String gateway = null;
425 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
426 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
427 gateway = routeInfo.getGateway().getHostAddress();
432 // Find IPv4 DNS addresses.
433 String dnsServers = mLinkProperties.getDnsServers().stream()
434 .filter(Inet4Address.class::isInstance)
435 .map(InetAddress::getHostAddress)
436 .collect(Collectors.joining(","));
439 updatePreference(mIpAddressPref, ipv4Address);
440 updatePreference(mSubnetPref, subnet);
441 updatePreference(mGatewayPref, gateway);
442 updatePreference(mDnsPref, dnsServers);
444 if (ipv6Addresses.length() > 0) {
445 mIpv6AddressPref.setSummary(ipv6Addresses.toString());
446 mIpv6Category.setVisible(true);
448 mIpv6Category.setVisible(false);
452 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
454 InetAddress all = InetAddress.getByAddress(
455 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
456 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
457 } catch (UnknownHostException e) {
463 * Returns whether the network represented by this preference can be forgotten.
465 private boolean canForgetNetwork() {
466 return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null;
470 * Returns whether the user can sign into the network represented by this preference.
472 private boolean canSignIntoNetwork() {
473 return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
474 NET_CAPABILITY_CAPTIVE_PORTAL);
478 * Forgets the wifi network associated with this preference.
480 private void forgetNetwork() {
481 if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
482 mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
483 } else if (mWifiConfig != null) {
484 if (mWifiConfig.isPasspoint()) {
485 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
487 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
490 mMetricsFeatureProvider.action(
491 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
492 mFragment.getActivity().finish();
496 * Sign in to the captive portal found on this wifi network associated with this preference.
498 private void signIntoNetwork() {
499 mMetricsFeatureProvider.action(
500 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
501 mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork);