1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 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 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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. 15 */ 16 package com.android.settings.wifi.details; 17 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; 21 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; 51 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; 65 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; 73 74 /** 75 * Controller for logic pertaining to displaying Wifi information for the 76 * {@link WifiNetworkDetailsFragment}. 77 */ 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); 82 83 @VisibleForTesting 84 static final String KEY_CONNECTION_DETAIL_PREF = "connection_detail"; 85 @VisibleForTesting 86 static final String KEY_BUTTONS_PREF = "buttons"; 87 @VisibleForTesting 88 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength"; 89 @VisibleForTesting 90 static final String KEY_LINK_SPEED = "link_speed"; 91 @VisibleForTesting 92 static final String KEY_FREQUENCY_PREF = "frequency"; 93 @VisibleForTesting 94 static final String KEY_SECURITY_PREF = "security"; 95 @VisibleForTesting 96 static final String KEY_MAC_ADDRESS_PREF = "mac_address"; 97 @VisibleForTesting 98 static final String KEY_IP_ADDRESS_PREF = "ip_address"; 99 @VisibleForTesting 100 static final String KEY_GATEWAY_PREF = "gateway"; 101 @VisibleForTesting 102 static final String KEY_SUBNET_MASK_PREF = "subnet_mask"; 103 @VisibleForTesting 104 static final String KEY_DNS_PREF = "dns"; 105 @VisibleForTesting 106 static final String KEY_IPV6_CATEGORY = "ipv6_category"; 107 @VisibleForTesting 108 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; 109 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; 120 private int mRssi; 121 private String[] mSignalStr; 122 private final WifiConfiguration mWifiConfig; 123 private WifiInfo mWifiInfo; 124 private final WifiManager mWifiManager; 125 private final MetricsFeatureProvider mMetricsFeatureProvider; 126 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; 143 144 private final IntentFilter mFilter; 145 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 146 @Override 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: 151 updateInfo(); 152 } 153 } 154 }; 155 156 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 157 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); 158 159 // Must be run on the UI thread since it directly manipulates UI state. 160 private final NetworkCallback mNetworkCallback = new NetworkCallback() { 161 @Override 162 public void onLinkPropertiesChanged(Network network, LinkProperties lp) { 163 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { 164 mLinkProperties = lp; 165 updateIpLayerInfo(); 166 } 167 } 168 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; 172 173 // nc can never be null, see ConnectivityService#callCallbackForRequest. 174 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap); 175 } 176 177 @Override 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(); 186 } 187 mNetworkCapabilities = nc; 188 updateIpLayerInfo(); 189 } 190 } 191 192 @Override 193 public void onLost(Network network) { 194 if (network.equals(mNetwork)) { 195 exitActivity(); 196 } 197 } 198 }; 199 WifiDetailPreferenceController( AccessPoint accessPoint, ConnectivityManagerWrapper connectivityManagerWrapper, Context context, Fragment fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider)200 public WifiDetailPreferenceController( 201 AccessPoint accessPoint, 202 ConnectivityManagerWrapper connectivityManagerWrapper, 203 Context context, 204 Fragment fragment, 205 Handler handler, 206 Lifecycle lifecycle, 207 WifiManager wifiManager, 208 MetricsFeatureProvider metricsFeatureProvider) { 209 super(context); 210 211 mAccessPoint = accessPoint; 212 mConnectivityManager = connectivityManagerWrapper.getConnectivityManager(); 213 mConnectivityManagerWrapper = connectivityManagerWrapper; 214 mFragment = fragment; 215 mHandler = handler; 216 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal); 217 mWifiConfig = accessPoint.getConfig(); 218 mWifiManager = wifiManager; 219 mMetricsFeatureProvider = metricsFeatureProvider; 220 221 mFilter = new IntentFilter(); 222 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 223 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 224 225 lifecycle.addObserver(this); 226 } 227 228 @Override isAvailable()229 public boolean isAvailable() { 230 return true; 231 } 232 233 @Override getPreferenceKey()234 public String getPreferenceKey() { 235 // Returns null since this controller contains more than one Preference 236 return null; 237 } 238 239 @Override displayPreference(PreferenceScreen screen)240 public void displayPreference(PreferenceScreen screen) { 241 super.displayPreference(screen); 242 243 mPrefContext = screen.getPreferenceManager().getContext(); 244 245 mConnectionDetailPref = screen.findPreference(KEY_CONNECTION_DETAIL_PREF); 246 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)); 252 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); 258 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); 264 265 mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY); 266 mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF); 267 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()); 272 } 273 274 @Override onResume()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); 281 updateInfo(); 282 mContext.registerReceiver(mReceiver, mFilter); 283 mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback, 284 mHandler); 285 } 286 287 @Override onPause()288 public void onPause() { 289 mNetwork = null; 290 mLinkProperties = null; 291 mNetworkCapabilities = null; 292 mNetworkInfo = null; 293 mWifiInfo = null; 294 mContext.unregisterReceiver(mReceiver); 295 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 296 } 297 updateInfo()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) { 304 exitActivity(); 305 return; 306 } 307 308 // Update whether the forgot button should be displayed. 309 mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE); 310 311 refreshNetworkState(); 312 313 // Update Connection Header icon and Signal Strength Preference 314 mRssi = mWifiInfo.getRssi(); 315 refreshRssiViews(); 316 317 // MAC Address Pref 318 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress()); 319 320 // Link Speed Pref 321 int linkSpeedMbps = mWifiInfo.getLinkSpeed(); 322 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0); 323 mLinkSpeedPref.setDetailText(mContext.getString( 324 R.string.link_speed, mWifiInfo.getLinkSpeed())); 325 326 // Frequency Pref 327 final int frequency = mWifiInfo.getFrequency(); 328 String band = null; 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); 335 } else { 336 Log.e(TAG, "Unexpected frequency " + frequency); 337 } 338 mFrequencyPref.setDetailText(band); 339 340 updateIpLayerInfo(); 341 } 342 exitActivity()343 private void exitActivity() { 344 if (DEBUG) { 345 Log.d(TAG, "Exiting the WifiNetworkDetailsPage"); 346 } 347 mFragment.getActivity().finish(); 348 } 349 refreshNetworkState()350 private void refreshNetworkState() { 351 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo); 352 mConnectionDetailPref.setTitle(mAccessPoint.getSettingsSummary()); 353 } 354 refreshRssiViews()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(); 360 361 mConnectionDetailPref.setIcon(wifiIcon); 362 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); 367 368 int summarySignalLevel = mAccessPoint.getLevel(); 369 mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]); 370 } 371 updatePreference(WifiDetailPreference pref, String detailText)372 private void updatePreference(WifiDetailPreference pref, String detailText) { 373 if (!TextUtils.isEmpty(detailText)) { 374 pref.setDetailText(detailText); 375 pref.setVisible(true); 376 } else { 377 pref.setVisible(false); 378 } 379 } 380 updateIpLayerInfo()381 private void updateIpLayerInfo() { 382 mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE); 383 mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE 384 || mSignInButton.getVisibility() == View.VISIBLE); 385 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); 392 return; 393 } 394 395 // Find IPv4 and IPv6 addresses. 396 String ipv4Address = null; 397 String subnet = null; 398 StringJoiner ipv6Addresses = new StringJoiner("\n"); 399 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()); 406 } 407 } 408 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(); 414 break; 415 } 416 } 417 418 // Find IPv4 DNS addresses. 419 String dnsServers = mLinkProperties.getDnsServers().stream() 420 .filter(Inet4Address.class::isInstance) 421 .map(InetAddress::getHostAddress) 422 .collect(Collectors.joining(",")); 423 424 // Update UI. 425 updatePreference(mIpAddressPref, ipv4Address); 426 updatePreference(mSubnetPref, subnet); 427 updatePreference(mGatewayPref, gateway); 428 updatePreference(mDnsPref, dnsServers); 429 430 if (ipv6Addresses.length() > 0) { 431 mIpv6AddressPref.setSummary(ipv6Addresses.toString()); 432 mIpv6Category.setVisible(true); 433 } else { 434 mIpv6Category.setVisible(false); 435 } 436 } 437 ipv4PrefixLengthToSubnetMask(int prefixLength)438 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) { 439 try { 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) { 444 return null; 445 } 446 } 447 448 /** 449 * Returns whether the network represented by this preference can be forgotten. 450 */ canForgetNetwork()451 private boolean canForgetNetwork() { 452 return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null; 453 } 454 455 /** 456 * Returns whether the user can sign into the network represented by this preference. 457 */ canSignIntoNetwork()458 private boolean canSignIntoNetwork() { 459 return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability( 460 NET_CAPABILITY_CAPTIVE_PORTAL); 461 } 462 463 /** 464 * Forgets the wifi network associated with this preference. 465 */ forgetNetwork()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); 472 } else { 473 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */); 474 } 475 } 476 mMetricsFeatureProvider.action( 477 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); 478 mFragment.getActivity().finish(); 479 } 480 } 481