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 static com.android.settings.wifi.WifiSettings.isEditabilityLockedDown; 23 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; 54 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; 71 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; 78 79 /** 80 * Controller for logic pertaining to displaying Wifi information for the 81 * {@link WifiNetworkDetailsFragment}. 82 */ 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); 87 88 @VisibleForTesting 89 static final String KEY_HEADER = "connection_header"; 90 @VisibleForTesting 91 static final String KEY_BUTTONS_PREF = "buttons"; 92 @VisibleForTesting 93 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength"; 94 @VisibleForTesting 95 static final String KEY_LINK_SPEED = "link_speed"; 96 @VisibleForTesting 97 static final String KEY_FREQUENCY_PREF = "frequency"; 98 @VisibleForTesting 99 static final String KEY_SECURITY_PREF = "security"; 100 @VisibleForTesting 101 static final String KEY_MAC_ADDRESS_PREF = "mac_address"; 102 @VisibleForTesting 103 static final String KEY_IP_ADDRESS_PREF = "ip_address"; 104 @VisibleForTesting 105 static final String KEY_GATEWAY_PREF = "gateway"; 106 @VisibleForTesting 107 static final String KEY_SUBNET_MASK_PREF = "subnet_mask"; 108 @VisibleForTesting 109 static final String KEY_DNS_PREF = "dns"; 110 @VisibleForTesting 111 static final String KEY_IPV6_CATEGORY = "ipv6_category"; 112 @VisibleForTesting 113 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; 114 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; 124 private int mRssi; 125 private String[] mSignalStr; 126 private final WifiConfiguration mWifiConfig; 127 private WifiInfo mWifiInfo; 128 private final WifiManager mWifiManager; 129 private final MetricsFeatureProvider mMetricsFeatureProvider; 130 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; 147 148 private final IntentFilter mFilter; 149 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 150 @Override 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: 155 updateInfo(); 156 } 157 } 158 }; 159 160 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 161 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); 162 163 // Must be run on the UI thread since it directly manipulates UI state. 164 private final NetworkCallback mNetworkCallback = new NetworkCallback() { 165 @Override 166 public void onLinkPropertiesChanged(Network network, LinkProperties lp) { 167 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { 168 mLinkProperties = lp; 169 updateIpLayerInfo(); 170 } 171 } 172 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; 176 177 // nc can never be null, see ConnectivityService#callCallbackForRequest. 178 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap); 179 } 180 181 @Override 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(); 190 } 191 mNetworkCapabilities = nc; 192 updateIpLayerInfo(); 193 } 194 } 195 196 @Override 197 public void onLost(Network network) { 198 if (network.equals(mNetwork)) { 199 exitActivity(); 200 } 201 } 202 }; 203 204 public WifiDetailPreferenceController( 205 AccessPoint accessPoint, 206 ConnectivityManagerWrapper connectivityManagerWrapper, 207 Context context, 208 Fragment fragment, 209 Handler handler, 210 Lifecycle lifecycle, 211 WifiManager wifiManager, 212 MetricsFeatureProvider metricsFeatureProvider) { 213 super(context); 214 215 mAccessPoint = accessPoint; 216 mConnectivityManager = connectivityManagerWrapper.getConnectivityManager(); 217 mConnectivityManagerWrapper = connectivityManagerWrapper; 218 mFragment = fragment; 219 mHandler = handler; 220 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal); 221 mWifiConfig = accessPoint.getConfig(); 222 mWifiManager = wifiManager; 223 mMetricsFeatureProvider = metricsFeatureProvider; 224 225 mFilter = new IntentFilter(); 226 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 227 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 228 229 lifecycle.addObserver(this); 230 } 231 232 @Override 233 public boolean isAvailable() { 234 return true; 235 } 236 237 @Override 238 public String getPreferenceKey() { 239 // Returns null since this controller contains more than one Preference 240 return null; 241 } 242 243 @Override 244 public void displayPreference(PreferenceScreen screen) { 245 super.displayPreference(screen); 246 247 setupEntityHeader(screen); 248 249 mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF); 250 mSignInButton = mButtonsPref.findViewById(R.id.signin_button); 251 mSignInButton.setOnClickListener(view -> signIntoNetwork()); 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 = screen.findPreference(KEY_IPV6_ADDRESSES_PREF); 267 268 mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */)); 269 mForgetButton = mButtonsPref.findViewById(R.id.forget_button); 270 mForgetButton.setOnClickListener(view -> forgetNetwork()); 271 } 272 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)); 279 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); 284 285 mEntityHeaderController.setLabel(mAccessPoint.getSsidStr()); 286 } 287 288 @Override 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); 295 updateInfo(); 296 mContext.registerReceiver(mReceiver, mFilter); 297 mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback, 298 mHandler); 299 } 300 301 @Override 302 public void onPause() { 303 mNetwork = null; 304 mLinkProperties = null; 305 mNetworkCapabilities = null; 306 mNetworkInfo = null; 307 mWifiInfo = null; 308 mContext.unregisterReceiver(mReceiver); 309 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 310 } 311 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) { 318 exitActivity(); 319 return; 320 } 321 322 // Update whether the forgot button should be displayed. 323 mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE); 324 325 refreshNetworkState(); 326 327 // Update Connection Header icon and Signal Strength Preference 328 mRssi = mWifiInfo.getRssi(); 329 refreshRssiViews(); 330 331 // MAC Address Pref 332 mMacAddressPref.setDetailText(mWifiInfo.getMacAddress()); 333 334 // Link Speed Pref 335 int linkSpeedMbps = mWifiInfo.getLinkSpeed(); 336 mLinkSpeedPref.setVisible(linkSpeedMbps >= 0); 337 mLinkSpeedPref.setDetailText(mContext.getString( 338 R.string.link_speed, mWifiInfo.getLinkSpeed())); 339 340 // Frequency Pref 341 final int frequency = mWifiInfo.getFrequency(); 342 String band = null; 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); 349 } else { 350 Log.e(TAG, "Unexpected frequency " + frequency); 351 } 352 mFrequencyPref.setDetailText(band); 353 354 updateIpLayerInfo(); 355 } 356 357 private void exitActivity() { 358 if (DEBUG) { 359 Log.d(TAG, "Exiting the WifiNetworkDetailsPage"); 360 } 361 mFragment.getActivity().finish(); 362 } 363 364 private void refreshNetworkState() { 365 mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo); 366 mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary()) 367 .done(mFragment.getActivity(), true /* rebind */); 368 } 369 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(); 375 376 wifiIcon.setTint(Utils.getColorAccent(mContext)); 377 mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */); 378 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); 383 384 int summarySignalLevel = mAccessPoint.getLevel(); 385 mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]); 386 } 387 388 private void updatePreference(WifiDetailPreference pref, String detailText) { 389 if (!TextUtils.isEmpty(detailText)) { 390 pref.setDetailText(detailText); 391 pref.setVisible(true); 392 } else { 393 pref.setVisible(false); 394 } 395 } 396 397 private void updateIpLayerInfo() { 398 mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE); 399 mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE 400 || mSignInButton.getVisibility() == View.VISIBLE); 401 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); 408 return; 409 } 410 411 // Find IPv4 and IPv6 addresses. 412 String ipv4Address = null; 413 String subnet = null; 414 StringJoiner ipv6Addresses = new StringJoiner("\n"); 415 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()); 422 } 423 } 424 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(); 430 break; 431 } 432 } 433 434 // Find all (IPv4 and IPv6) DNS addresses. 435 String dnsServers = mLinkProperties.getDnsServers().stream() 436 .map(InetAddress::getHostAddress) 437 .collect(Collectors.joining("\n")); 438 439 // Update UI. 440 updatePreference(mIpAddressPref, ipv4Address); 441 updatePreference(mSubnetPref, subnet); 442 updatePreference(mGatewayPref, gateway); 443 updatePreference(mDnsPref, dnsServers); 444 445 if (ipv6Addresses.length() > 0) { 446 mIpv6AddressPref.setSummary( 447 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString())); 448 mIpv6Category.setVisible(true); 449 } else { 450 mIpv6Category.setVisible(false); 451 } 452 } 453 454 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) { 455 try { 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) { 460 return null; 461 } 462 } 463 464 /** 465 * Returns whether the network represented by this preference can be forgotten. 466 */ 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)); 471 } 472 473 /** 474 * Returns whether the user can sign into the network represented by this preference. 475 */ 476 private boolean canSignIntoNetwork() { 477 return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability( 478 NET_CAPABILITY_CAPTIVE_PORTAL); 479 } 480 481 /** 482 * Forgets the wifi network associated with this preference. 483 */ 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); 490 } else { 491 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */); 492 } 493 } 494 mMetricsFeatureProvider.action( 495 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); 496 mFragment.getActivity().finish(); 497 } 498 499 /** 500 * Sign in to the captive portal found on this wifi network associated with this preference. 501 */ 502 private void signIntoNetwork() { 503 mMetricsFeatureProvider.action( 504 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN); 505 mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork); 506 } 507 } 508