1 /* 2 * Copyright (C) 2019 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.details2; 17 18 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; 20 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 22 23 import android.app.Activity; 24 import android.app.AlertDialog; 25 import android.app.settings.SettingsEnums; 26 import android.content.AsyncQueryHandler; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.database.Cursor; 30 import android.graphics.Bitmap; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.VectorDrawable; 34 import android.net.CaptivePortalData; 35 import android.net.ConnectivityManager; 36 import android.net.ConnectivityManager.NetworkCallback; 37 import android.net.LinkAddress; 38 import android.net.LinkProperties; 39 import android.net.Network; 40 import android.net.NetworkCapabilities; 41 import android.net.NetworkInfo; 42 import android.net.NetworkRequest; 43 import android.net.NetworkUtils; 44 import android.net.RouteInfo; 45 import android.net.Uri; 46 import android.net.wifi.WifiConfiguration; 47 import android.net.wifi.WifiInfo; 48 import android.net.wifi.WifiManager; 49 import android.os.Handler; 50 import android.provider.Telephony.CarrierId; 51 import android.telephony.SubscriptionInfo; 52 import android.telephony.SubscriptionManager; 53 import android.telephony.TelephonyManager; 54 import android.text.TextUtils; 55 import android.util.FeatureFlagUtils; 56 import android.util.Log; 57 import android.widget.ImageView; 58 import android.widget.Toast; 59 60 import androidx.annotation.VisibleForTesting; 61 import androidx.core.text.BidiFormatter; 62 import androidx.preference.Preference; 63 import androidx.preference.PreferenceCategory; 64 import androidx.preference.PreferenceFragmentCompat; 65 import androidx.preference.PreferenceScreen; 66 67 import com.android.settings.R; 68 import com.android.settings.Utils; 69 import com.android.settings.core.FeatureFlags; 70 import com.android.settings.core.PreferenceControllerMixin; 71 import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController; 72 import com.android.settings.widget.EntityHeaderController; 73 import com.android.settings.wifi.WifiDialog2; 74 import com.android.settings.wifi.WifiDialog2.WifiDialog2Listener; 75 import com.android.settings.wifi.WifiEntryShell; 76 import com.android.settings.wifi.WifiUtils; 77 import com.android.settings.wifi.dpp.WifiDppUtils; 78 import com.android.settingslib.core.AbstractPreferenceController; 79 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 80 import com.android.settingslib.core.lifecycle.Lifecycle; 81 import com.android.settingslib.core.lifecycle.LifecycleObserver; 82 import com.android.settingslib.core.lifecycle.events.OnPause; 83 import com.android.settingslib.core.lifecycle.events.OnResume; 84 import com.android.settingslib.utils.StringUtil; 85 import com.android.settingslib.widget.ActionButtonsPreference; 86 import com.android.settingslib.widget.LayoutPreference; 87 import com.android.wifitrackerlib.WifiEntry; 88 import com.android.wifitrackerlib.WifiEntry.ConnectCallback; 89 import com.android.wifitrackerlib.WifiEntry.ConnectCallback.ConnectStatus; 90 import com.android.wifitrackerlib.WifiEntry.ConnectedInfo; 91 import com.android.wifitrackerlib.WifiEntry.DisconnectCallback; 92 import com.android.wifitrackerlib.WifiEntry.DisconnectCallback.DisconnectStatus; 93 import com.android.wifitrackerlib.WifiEntry.ForgetCallback; 94 import com.android.wifitrackerlib.WifiEntry.ForgetCallback.ForgetStatus; 95 import com.android.wifitrackerlib.WifiEntry.SignInCallback; 96 import com.android.wifitrackerlib.WifiEntry.SignInCallback.SignInStatus; 97 import com.android.wifitrackerlib.WifiEntry.WifiEntryCallback; 98 99 import java.net.Inet4Address; 100 import java.net.Inet6Address; 101 import java.net.InetAddress; 102 import java.net.UnknownHostException; 103 import java.time.Duration; 104 import java.time.Instant; 105 import java.time.ZonedDateTime; 106 import java.time.format.DateTimeFormatter; 107 import java.time.format.FormatStyle; 108 import java.util.List; 109 import java.util.StringJoiner; 110 import java.util.stream.Collectors; 111 112 // TODO(b/151133650): Replace AbstractPreferenceController with BasePreferenceController. 113 /** 114 * Controller for logic pertaining to displaying Wifi information for the 115 * {@link WifiNetworkDetailsFragment}. 116 */ 117 public class WifiDetailPreferenceController2 extends AbstractPreferenceController 118 implements PreferenceControllerMixin, WifiDialog2Listener, LifecycleObserver, OnPause, 119 OnResume, WifiEntryCallback, ConnectCallback, DisconnectCallback, ForgetCallback, 120 SignInCallback { 121 122 private static final String TAG = "WifiDetailsPrefCtrl2"; 123 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 124 125 @VisibleForTesting 126 static final String KEY_HEADER = "connection_header"; 127 @VisibleForTesting 128 static final String KEY_DATA_USAGE_HEADER = "status_header"; 129 @VisibleForTesting 130 static final String KEY_BUTTONS_PREF = "buttons"; 131 @VisibleForTesting 132 static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength"; 133 @VisibleForTesting 134 static final String KEY_TX_LINK_SPEED = "tx_link_speed"; 135 @VisibleForTesting 136 static final String KEY_RX_LINK_SPEED = "rx_link_speed"; 137 @VisibleForTesting 138 static final String KEY_FREQUENCY_PREF = "frequency"; 139 @VisibleForTesting 140 static final String KEY_SECURITY_PREF = "security"; 141 @VisibleForTesting 142 static final String KEY_SSID_PREF = "ssid"; 143 @VisibleForTesting 144 static final String KEY_EAP_SIM_SUBSCRIPTION_PREF = "eap_sim_subscription"; 145 @VisibleForTesting 146 static final String KEY_MAC_ADDRESS_PREF = "mac_address"; 147 @VisibleForTesting 148 static final String KEY_IP_ADDRESS_PREF = "ip_address"; 149 @VisibleForTesting 150 static final String KEY_GATEWAY_PREF = "gateway"; 151 @VisibleForTesting 152 static final String KEY_SUBNET_MASK_PREF = "subnet_mask"; 153 @VisibleForTesting 154 static final String KEY_DNS_PREF = "dns"; 155 @VisibleForTesting 156 static final String KEY_IPV6_CATEGORY = "ipv6_category"; 157 @VisibleForTesting 158 static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; 159 160 private final WifiEntry mWifiEntry; 161 private final ConnectivityManager mConnectivityManager; 162 private final PreferenceFragmentCompat mFragment; 163 private final Handler mHandler; 164 private LinkProperties mLinkProperties; 165 private Network mNetwork; 166 private NetworkInfo mNetworkInfo; 167 private NetworkCapabilities mNetworkCapabilities; 168 private int mRssiSignalLevel = -1; 169 @VisibleForTesting boolean mShowX; // Shows the Wi-Fi signal icon of Pie+x when it's true. 170 private String[] mSignalStr; 171 private WifiInfo mWifiInfo; 172 private final WifiManager mWifiManager; 173 private final MetricsFeatureProvider mMetricsFeatureProvider; 174 175 // UI elements - in order of appearance 176 private ActionButtonsPreference mButtonsPref; 177 private EntityHeaderController mEntityHeaderController; 178 private Preference mSignalStrengthPref; 179 private Preference mTxLinkSpeedPref; 180 private Preference mRxLinkSpeedPref; 181 private Preference mFrequencyPref; 182 private Preference mSecurityPref; 183 private Preference mSsidPref; 184 private Preference mEapSimSubscriptionPref; 185 private Preference mMacAddressPref; 186 private Preference mIpAddressPref; 187 private Preference mGatewayPref; 188 private Preference mSubnetPref; 189 private Preference mDnsPref; 190 private PreferenceCategory mIpv6Category; 191 private Preference mIpv6AddressPref; 192 private Lifecycle mLifecycle; 193 Preference mDataUsageSummaryPref; 194 WifiDataUsageSummaryPreferenceController mSummaryHeaderController; 195 196 private final IconInjector mIconInjector; 197 private final Clock mClock; 198 199 private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() 200 .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); 201 202 private CarrierIdAsyncQueryHandler mCarrierIdAsyncQueryHandler; 203 private static final int TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY = 1; 204 private static final int COLUMN_CARRIER_NAME = 0; 205 206 private class CarrierIdAsyncQueryHandler extends AsyncQueryHandler { 207 CarrierIdAsyncQueryHandler(Context context)208 private CarrierIdAsyncQueryHandler(Context context) { 209 super(context.getContentResolver()); 210 } 211 212 @Override onQueryComplete(int token, Object cookie, Cursor cursor)213 protected void onQueryComplete(int token, Object cookie, Cursor cursor) { 214 if (token == TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY) { 215 if (mContext == null || cursor == null || !cursor.moveToFirst()) { 216 if (cursor != null) { 217 cursor.close(); 218 } 219 mEapSimSubscriptionPref.setSummary(R.string.wifi_require_sim_card_to_connect); 220 return; 221 } 222 mEapSimSubscriptionPref.setSummary(mContext.getString( 223 R.string.wifi_require_specific_sim_card_to_connect, 224 cursor.getString(COLUMN_CARRIER_NAME))); 225 cursor.close(); 226 return; 227 } 228 } 229 } 230 231 // Must be run on the UI thread since it directly manipulates UI state. 232 private final NetworkCallback mNetworkCallback = new NetworkCallback() { 233 @Override 234 public void onLinkPropertiesChanged(Network network, LinkProperties lp) { 235 if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { 236 mLinkProperties = lp; 237 refreshEntityHeader(); 238 refreshButtons(); 239 refreshIpLayerInfo(); 240 } 241 } 242 243 private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) { 244 // If this is the first time we get NetworkCapabilities, report that something changed. 245 if (mNetworkCapabilities == null) return true; 246 247 // nc can never be null, see ConnectivityService#callCallbackForRequest. 248 return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap); 249 } 250 251 private boolean hasPrivateDnsStatusChanged(NetworkCapabilities nc) { 252 // If this is the first time that WifiDetailPreferenceController2 gets 253 // NetworkCapabilities, report that something has changed and assign nc to 254 // mNetworkCapabilities in onCapabilitiesChanged. Note that the NetworkCapabilities 255 // from onCapabilitiesChanged() will never be null, so calling 256 // mNetworkCapabilities.isPrivateDnsBroken() would be safe next time. 257 if (mNetworkCapabilities == null) { 258 return true; 259 } 260 261 return mNetworkCapabilities.isPrivateDnsBroken() != nc.isPrivateDnsBroken(); 262 } 263 264 @Override 265 public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { 266 // If the network just validated or lost Internet access or detected partial internet 267 // connectivity or private dns was broken, refresh network state. Don't do this on 268 // every NetworkCapabilities change because refreshEntityHeader sends IPCs to the 269 // system server from the UI thread, which can cause jank. 270 if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) { 271 if (hasPrivateDnsStatusChanged(nc) 272 || hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) 273 || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL) 274 || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 275 refreshEntityHeader(); 276 } 277 mNetworkCapabilities = nc; 278 refreshButtons(); 279 refreshIpLayerInfo(); 280 } 281 } 282 283 @Override 284 public void onLost(Network network) { 285 // Ephemeral network not a saved network, leave detail page once disconnected 286 if (!mWifiEntry.isSaved() && network.equals(mNetwork)) { 287 if (DEBUG) { 288 Log.d(TAG, "OnLost and exit WifiNetworkDetailsPage"); 289 } 290 mFragment.getActivity().finish(); 291 } 292 } 293 }; 294 295 /** 296 * To get an instance of {@link WifiDetailPreferenceController2} 297 */ newInstance( WifiEntry wifiEntry, ConnectivityManager connectivityManager, Context context, PreferenceFragmentCompat fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider)298 public static WifiDetailPreferenceController2 newInstance( 299 WifiEntry wifiEntry, 300 ConnectivityManager connectivityManager, 301 Context context, 302 PreferenceFragmentCompat fragment, 303 Handler handler, 304 Lifecycle lifecycle, 305 WifiManager wifiManager, 306 MetricsFeatureProvider metricsFeatureProvider) { 307 return new WifiDetailPreferenceController2( 308 wifiEntry, connectivityManager, context, fragment, handler, lifecycle, 309 wifiManager, metricsFeatureProvider, new IconInjector(context), new Clock()); 310 } 311 312 @VisibleForTesting WifiDetailPreferenceController2( WifiEntry wifiEntry, ConnectivityManager connectivityManager, Context context, PreferenceFragmentCompat fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider, IconInjector injector, Clock clock)313 /* package */ WifiDetailPreferenceController2( 314 WifiEntry wifiEntry, 315 ConnectivityManager connectivityManager, 316 Context context, 317 PreferenceFragmentCompat fragment, 318 Handler handler, 319 Lifecycle lifecycle, 320 WifiManager wifiManager, 321 MetricsFeatureProvider metricsFeatureProvider, 322 IconInjector injector, 323 Clock clock) { 324 super(context); 325 326 mWifiEntry = wifiEntry; 327 mWifiEntry.setListener(this); 328 mConnectivityManager = connectivityManager; 329 mFragment = fragment; 330 mHandler = handler; 331 mSignalStr = context.getResources().getStringArray(R.array.wifi_signal); 332 mWifiManager = wifiManager; 333 mMetricsFeatureProvider = metricsFeatureProvider; 334 mIconInjector = injector; 335 mClock = clock; 336 337 mLifecycle = lifecycle; 338 lifecycle.addObserver(this); 339 } 340 341 @Override isAvailable()342 public boolean isAvailable() { 343 return true; 344 } 345 346 @Override getPreferenceKey()347 public String getPreferenceKey() { 348 // Returns null since this controller contains more than one Preference 349 return null; 350 } 351 352 @Override displayPreference(PreferenceScreen screen)353 public void displayPreference(PreferenceScreen screen) { 354 super.displayPreference(screen); 355 356 setupEntityHeader(screen); 357 358 mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF)) 359 .setButton1Text(R.string.forget) 360 .setButton1Icon(R.drawable.ic_settings_delete) 361 .setButton1OnClickListener(view -> forgetNetwork()) 362 .setButton2Text(R.string.wifi_sign_in_button_text) 363 .setButton2Icon(R.drawable.ic_settings_sign_in) 364 .setButton2OnClickListener(view -> signIntoNetwork()) 365 .setButton3Text(getConnectDisconnectButtonTextResource()) 366 .setButton3Icon(getConnectDisconnectButtonIconResource()) 367 .setButton3OnClickListener(view -> connectDisconnectNetwork()) 368 .setButton4Text(R.string.share) 369 .setButton4Icon(R.drawable.ic_qrcode_24dp) 370 .setButton4OnClickListener(view -> shareNetwork()); 371 updateCaptivePortalButton(); 372 373 mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF); 374 mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED); 375 mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED); 376 mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF); 377 mSecurityPref = screen.findPreference(KEY_SECURITY_PREF); 378 379 mSsidPref = screen.findPreference(KEY_SSID_PREF); 380 mEapSimSubscriptionPref = screen.findPreference(KEY_EAP_SIM_SUBSCRIPTION_PREF); 381 mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF); 382 mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF); 383 mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF); 384 mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF); 385 mDnsPref = screen.findPreference(KEY_DNS_PREF); 386 387 mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY); 388 mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF); 389 390 mSecurityPref.setSummary(mWifiEntry.getSecurityString(false /* concise */)); 391 } 392 393 /** 394 * Update text, icon and listener of the captive portal button. 395 * @return True if the button should be shown. 396 */ updateCaptivePortalButton()397 private boolean updateCaptivePortalButton() { 398 final Uri venueInfoUrl = getCaptivePortalVenueInfoUrl(); 399 if (venueInfoUrl == null) { 400 mButtonsPref.setButton2Text(R.string.wifi_sign_in_button_text) 401 .setButton2Icon(R.drawable.ic_settings_sign_in) 402 .setButton2OnClickListener(view -> signIntoNetwork()); 403 return canSignIntoNetwork(); 404 } 405 406 mButtonsPref.setButton2Text(R.string.wifi_venue_website_button_text) 407 .setButton2Icon(R.drawable.ic_settings_sign_in) 408 .setButton2OnClickListener(view -> { 409 final Intent infoIntent = new Intent(Intent.ACTION_VIEW); 410 infoIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 411 infoIntent.setData(venueInfoUrl); 412 mContext.startActivity(infoIntent); 413 }); 414 // Only show the venue website when the network is connected. 415 return mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED; 416 } 417 getCaptivePortalVenueInfoUrl()418 private Uri getCaptivePortalVenueInfoUrl() { 419 final LinkProperties lp = mLinkProperties; 420 if (lp == null) { 421 return null; 422 } 423 final CaptivePortalData data = lp.getCaptivePortalData(); 424 if (data == null) { 425 return null; 426 } 427 return data.getVenueInfoUrl(); 428 } 429 setupEntityHeader(PreferenceScreen screen)430 private void setupEntityHeader(PreferenceScreen screen) { 431 LayoutPreference headerPref = screen.findPreference(KEY_HEADER); 432 433 if (usingDataUsageHeader(mContext)) { 434 headerPref.setVisible(false); 435 mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER); 436 mDataUsageSummaryPref.setVisible(true); 437 mSummaryHeaderController = 438 new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(), 439 mLifecycle, (PreferenceFragmentCompat) mFragment, 440 mWifiEntry.getTitle()); 441 return; 442 } 443 444 mEntityHeaderController = 445 EntityHeaderController.newInstance( 446 mFragment.getActivity(), mFragment, 447 headerPref.findViewById(R.id.entity_header)); 448 449 ImageView iconView = headerPref.findViewById(R.id.entity_header_icon); 450 451 iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 452 453 mEntityHeaderController.setLabel(mWifiEntry.getTitle()); 454 } 455 getExpiryTimeSummary()456 private String getExpiryTimeSummary() { 457 if (mLinkProperties == null || mLinkProperties.getCaptivePortalData() == null) { 458 return null; 459 } 460 461 final long expiryTimeMillis = mLinkProperties.getCaptivePortalData().getExpiryTimeMillis(); 462 if (expiryTimeMillis <= 0) { 463 return null; 464 } 465 final ZonedDateTime now = mClock.now(); 466 final ZonedDateTime expiryTime = ZonedDateTime.ofInstant( 467 Instant.ofEpochMilli(expiryTimeMillis), 468 now.getZone()); 469 470 if (now.isAfter(expiryTime)) { 471 return null; 472 } 473 474 if (now.plusDays(2).isAfter(expiryTime)) { 475 // Expiration within 2 days: show a duration 476 return mContext.getString(R.string.wifi_time_remaining, StringUtil.formatElapsedTime( 477 mContext, 478 Duration.between(now, expiryTime).getSeconds() * 1000, 479 false /* withSeconds */)); 480 } 481 482 // For more than 2 days, show the expiry date 483 return mContext.getString(R.string.wifi_expiry_time, 484 DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(expiryTime)); 485 } 486 refreshEntityHeader()487 private void refreshEntityHeader() { 488 if (usingDataUsageHeader(mContext)) { 489 mSummaryHeaderController.updateState(mDataUsageSummaryPref); 490 } else { 491 mEntityHeaderController 492 .setSummary(mWifiEntry.getSummary()) 493 .setSecondSummary(getExpiryTimeSummary()) 494 .setRecyclerView(mFragment.getListView(), mLifecycle) 495 .done(mFragment.getActivity(), true /* rebind */); 496 } 497 } 498 499 @VisibleForTesting updateNetworkInfo()500 void updateNetworkInfo() { 501 if (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { 502 mNetwork = mWifiManager.getCurrentNetwork(); 503 mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork); 504 mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork); 505 mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork); 506 mWifiInfo = mWifiManager.getConnectionInfo(); 507 } else { 508 mNetwork = null; 509 mLinkProperties = null; 510 mNetworkCapabilities = null; 511 mNetworkInfo = null; 512 mWifiInfo = null; 513 } 514 } 515 516 @Override onResume()517 public void onResume() { 518 // Ensure mNetwork is set before any callbacks above are delivered, since our 519 // NetworkCallback only looks at changes to mNetwork. 520 updateNetworkInfo(); 521 refreshPage(); 522 mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback, 523 mHandler); 524 } 525 526 @Override onPause()527 public void onPause() { 528 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 529 } 530 refreshPage()531 private void refreshPage() { 532 Log.d(TAG, "Update UI!"); 533 534 // refresh header 535 refreshEntityHeader(); 536 537 // refresh Buttons 538 refreshButtons(); 539 540 // Update Connection Header icon and Signal Strength Preference 541 refreshRssiViews(); 542 // Frequency Pref 543 refreshFrequency(); 544 // Transmit Link Speed Pref 545 refreshTxSpeed(); 546 // Receive Link Speed Pref 547 refreshRxSpeed(); 548 // IP related information 549 refreshIpLayerInfo(); 550 // SSID Pref 551 refreshSsid(); 552 // EAP SIM subscription 553 refreshEapSimSubscription(); 554 // MAC Address Pref 555 refreshMacAddress(); 556 } 557 refreshRssiViews()558 private void refreshRssiViews() { 559 final int signalLevel = mWifiEntry.getLevel(); 560 561 // Disappears signal view if not in range. e.g. for saved networks. 562 if (signalLevel == WifiEntry.WIFI_LEVEL_UNREACHABLE) { 563 mSignalStrengthPref.setVisible(false); 564 mRssiSignalLevel = -1; 565 return; 566 } 567 568 final boolean showX = mWifiEntry.shouldShowXLevelIcon(); 569 570 if (mRssiSignalLevel == signalLevel && mShowX == showX) { 571 return; 572 } 573 mRssiSignalLevel = signalLevel; 574 mShowX = showX; 575 Drawable wifiIcon = mIconInjector.getIcon(mShowX, mRssiSignalLevel); 576 577 if (mEntityHeaderController != null) { 578 mEntityHeaderController 579 .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(), 580 true /* rebind */); 581 } 582 583 Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate(); 584 wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal)); 585 mSignalStrengthPref.setIcon(wifiIconDark); 586 587 mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]); 588 mSignalStrengthPref.setVisible(true); 589 } 590 redrawIconForHeader(Drawable original)591 private Drawable redrawIconForHeader(Drawable original) { 592 final int iconSize = mContext.getResources().getDimensionPixelSize( 593 R.dimen.wifi_detail_page_header_image_size); 594 final int actualWidth = original.getMinimumWidth(); 595 final int actualHeight = original.getMinimumHeight(); 596 597 if ((actualWidth == iconSize && actualHeight == iconSize) 598 || !VectorDrawable.class.isInstance(original)) { 599 return original; 600 } 601 602 // clear tint list to make sure can set 87% black after enlarge 603 original.setTintList(null); 604 605 // enlarge icon size 606 final Bitmap bitmap = Utils.createBitmap(original, 607 iconSize /*width*/, 608 iconSize /*height*/); 609 Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap); 610 611 // config color for 87% black after enlarge 612 newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); 613 614 return newIcon; 615 } 616 refreshFrequency()617 private void refreshFrequency() { 618 final ConnectedInfo connectedInfo = mWifiEntry.getConnectedInfo(); 619 if (connectedInfo == null) { 620 mFrequencyPref.setVisible(false); 621 return; 622 } 623 624 final int frequency = connectedInfo.frequencyMhz; 625 String band = null; 626 if (frequency >= WifiEntryShell.LOWER_FREQ_24GHZ 627 && frequency < WifiEntryShell.HIGHER_FREQ_24GHZ) { 628 band = mContext.getResources().getString(R.string.wifi_band_24ghz); 629 } else if (frequency >= WifiEntryShell.LOWER_FREQ_5GHZ 630 && frequency < WifiEntryShell.HIGHER_FREQ_5GHZ) { 631 band = mContext.getResources().getString(R.string.wifi_band_5ghz); 632 } else { 633 // Connecting state is unstable, make it disappeared if unexpected 634 if (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTING) { 635 mFrequencyPref.setVisible(false); 636 } else { 637 Log.e(TAG, "Unexpected frequency " + frequency); 638 } 639 return; 640 } 641 mFrequencyPref.setSummary(band); 642 mFrequencyPref.setVisible(true); 643 } 644 refreshTxSpeed()645 private void refreshTxSpeed() { 646 if (mWifiInfo == null 647 || mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) { 648 mTxLinkSpeedPref.setVisible(false); 649 return; 650 } 651 652 int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps(); 653 mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0); 654 mTxLinkSpeedPref.setSummary(mContext.getString( 655 R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps())); 656 } 657 refreshRxSpeed()658 private void refreshRxSpeed() { 659 if (mWifiInfo == null 660 || mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED) { 661 mRxLinkSpeedPref.setVisible(false); 662 return; 663 } 664 665 int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps(); 666 mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0); 667 mRxLinkSpeedPref.setSummary(mContext.getString( 668 R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps())); 669 } 670 refreshSsid()671 private void refreshSsid() { 672 if (mWifiEntry.isSubscription() && mWifiEntry.getSsid() != null) { 673 mSsidPref.setVisible(true); 674 mSsidPref.setSummary(mWifiEntry.getSsid()); 675 } else { 676 mSsidPref.setVisible(false); 677 } 678 } 679 refreshEapSimSubscription()680 private void refreshEapSimSubscription() { 681 mEapSimSubscriptionPref.setVisible(false); 682 683 if (mWifiEntry.getSecurity() != WifiEntry.SECURITY_EAP) { 684 return; 685 } 686 final WifiConfiguration config = mWifiEntry.getWifiConfiguration(); 687 if (config == null || config.enterpriseConfig == null) { 688 return; 689 } 690 if (!config.enterpriseConfig.isAuthenticationSimBased()) { 691 return; 692 } 693 694 mEapSimSubscriptionPref.setVisible(true); 695 696 // Checks if the SIM subscription is active. 697 final List<SubscriptionInfo> activeSubscriptionInfos = mContext 698 .getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList(); 699 final int defaultDataSubscriptionId = SubscriptionManager.getDefaultDataSubscriptionId(); 700 if (activeSubscriptionInfos != null) { 701 for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) { 702 if (config.carrierId == subscriptionInfo.getCarrierId()) { 703 mEapSimSubscriptionPref.setSummary(subscriptionInfo.getDisplayName()); 704 return; 705 } 706 707 // When it's UNKNOWN_CARRIER_ID, devices connects it with the SIM subscription of 708 // defaultDataSubscriptionId. 709 if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID 710 && defaultDataSubscriptionId == subscriptionInfo.getSubscriptionId()) { 711 mEapSimSubscriptionPref.setSummary(subscriptionInfo.getDisplayName()); 712 return; 713 } 714 } 715 } 716 717 if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 718 mEapSimSubscriptionPref.setSummary(R.string.wifi_no_related_sim_card); 719 return; 720 } 721 722 // The Wi-Fi network has specified carrier id, query carrier name from CarrierIdProvider. 723 if (mCarrierIdAsyncQueryHandler == null) { 724 mCarrierIdAsyncQueryHandler = new CarrierIdAsyncQueryHandler(mContext); 725 } 726 mCarrierIdAsyncQueryHandler.cancelOperation(TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY); 727 mCarrierIdAsyncQueryHandler.startQuery(TOKEN_QUERY_CARRIER_ID_AND_UPDATE_SIM_SUMMARY, 728 null /* cookie */, 729 CarrierId.All.CONTENT_URI, 730 new String[]{CarrierId.CARRIER_NAME}, 731 CarrierId.CARRIER_ID + "=?", 732 new String[] {Integer.toString(config.carrierId)}, 733 null /* orderBy */); 734 } 735 refreshMacAddress()736 private void refreshMacAddress() { 737 final String macAddress = mWifiEntry.getMacAddress(); 738 if (TextUtils.isEmpty(macAddress)) { 739 mMacAddressPref.setVisible(false); 740 return; 741 } 742 743 mMacAddressPref.setVisible(true); 744 745 mMacAddressPref.setTitle((mWifiEntry.getPrivacy() == WifiEntry.PRIVACY_RANDOMIZED_MAC) 746 ? R.string.wifi_advanced_randomized_mac_address_title 747 : R.string.wifi_advanced_device_mac_address_title); 748 749 if (macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) { 750 mMacAddressPref.setSummary(R.string.device_info_not_available); 751 } else { 752 mMacAddressPref.setSummary(macAddress); 753 } 754 } 755 updatePreference(Preference pref, String detailText)756 private void updatePreference(Preference pref, String detailText) { 757 if (!TextUtils.isEmpty(detailText)) { 758 pref.setSummary(detailText); 759 pref.setVisible(true); 760 } else { 761 pref.setVisible(false); 762 } 763 } 764 refreshButtons()765 private void refreshButtons() { 766 final boolean canForgetNetwork = canForgetNetwork(); 767 final boolean showCaptivePortalButton = updateCaptivePortalButton(); 768 final boolean canConnectDisconnectNetwork = mWifiEntry.canConnect() 769 || mWifiEntry.canDisconnect(); 770 final boolean canShareNetwork = canShareNetwork(); 771 772 mButtonsPref.setButton1Visible(canForgetNetwork); 773 mButtonsPref.setButton2Visible(showCaptivePortalButton); 774 // Keep the connect/disconnected button visible if we can connect/disconnect, or if we are 775 // in the middle of connecting (greyed out). 776 mButtonsPref.setButton3Visible(canConnectDisconnectNetwork 777 || mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTING); 778 mButtonsPref.setButton3Enabled(canConnectDisconnectNetwork); 779 mButtonsPref.setButton3Text(getConnectDisconnectButtonTextResource()); 780 mButtonsPref.setButton3Icon(getConnectDisconnectButtonIconResource()); 781 mButtonsPref.setButton4Visible(canShareNetwork); 782 mButtonsPref.setVisible(canForgetNetwork 783 || showCaptivePortalButton 784 || canConnectDisconnectNetwork 785 || canShareNetwork); 786 } 787 getConnectDisconnectButtonTextResource()788 private int getConnectDisconnectButtonTextResource() { 789 switch (mWifiEntry.getConnectedState()) { 790 case WifiEntry.CONNECTED_STATE_DISCONNECTED: 791 return R.string.wifi_connect; 792 case WifiEntry.CONNECTED_STATE_CONNECTED: 793 return R.string.wifi_disconnect_button_text; 794 case WifiEntry.CONNECTED_STATE_CONNECTING: 795 return R.string.wifi_connecting; 796 default: 797 throw new IllegalStateException("Invalid WifiEntry connected state"); 798 } 799 } 800 getConnectDisconnectButtonIconResource()801 private int getConnectDisconnectButtonIconResource() { 802 switch (mWifiEntry.getConnectedState()) { 803 case WifiEntry.CONNECTED_STATE_DISCONNECTED: 804 case WifiEntry.CONNECTED_STATE_CONNECTING: 805 return R.drawable.ic_settings_wireless; 806 case WifiEntry.CONNECTED_STATE_CONNECTED: 807 return R.drawable.ic_settings_close; 808 default: 809 throw new IllegalStateException("Invalid WifiEntry connected state"); 810 } 811 } 812 refreshIpLayerInfo()813 private void refreshIpLayerInfo() { 814 // Hide IP layer info if not a connected network. 815 if (mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED 816 || mNetwork == null || mLinkProperties == null) { 817 mIpAddressPref.setVisible(false); 818 mSubnetPref.setVisible(false); 819 mGatewayPref.setVisible(false); 820 mDnsPref.setVisible(false); 821 mIpv6Category.setVisible(false); 822 return; 823 } 824 825 // Find IPv4 and IPv6 addresses. 826 String ipv4Address = null; 827 String subnet = null; 828 StringJoiner ipv6Addresses = new StringJoiner("\n"); 829 830 for (LinkAddress addr : mLinkProperties.getLinkAddresses()) { 831 if (addr.getAddress() instanceof Inet4Address) { 832 ipv4Address = addr.getAddress().getHostAddress(); 833 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength()); 834 } else if (addr.getAddress() instanceof Inet6Address) { 835 ipv6Addresses.add(addr.getAddress().getHostAddress()); 836 } 837 } 838 839 // Find IPv4 default gateway. 840 String gateway = null; 841 for (RouteInfo routeInfo : mLinkProperties.getRoutes()) { 842 if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { 843 gateway = routeInfo.getGateway().getHostAddress(); 844 break; 845 } 846 } 847 848 // Find all (IPv4 and IPv6) DNS addresses. 849 String dnsServers = mLinkProperties.getDnsServers().stream() 850 .map(InetAddress::getHostAddress) 851 .collect(Collectors.joining("\n")); 852 853 // Update UI. 854 updatePreference(mIpAddressPref, ipv4Address); 855 updatePreference(mSubnetPref, subnet); 856 updatePreference(mGatewayPref, gateway); 857 updatePreference(mDnsPref, dnsServers); 858 859 if (ipv6Addresses.length() > 0) { 860 mIpv6AddressPref.setSummary( 861 BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString())); 862 mIpv6Category.setVisible(true); 863 } else { 864 mIpv6Category.setVisible(false); 865 } 866 } 867 ipv4PrefixLengthToSubnetMask(int prefixLength)868 private static String ipv4PrefixLengthToSubnetMask(int prefixLength) { 869 try { 870 InetAddress all = InetAddress.getByAddress( 871 new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); 872 return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress(); 873 } catch (UnknownHostException e) { 874 return null; 875 } 876 } 877 878 /** 879 * Returns whether the network represented by this preference can be modified. 880 */ canModifyNetwork()881 public boolean canModifyNetwork() { 882 return mWifiEntry.isSaved() 883 && !WifiUtils.isNetworkLockedDown(mContext, mWifiEntry.getWifiConfiguration()); 884 } 885 886 /** 887 * Returns whether the network represented by this preference can be forgotten. 888 */ canForgetNetwork()889 public boolean canForgetNetwork() { 890 return mWifiEntry.canForget() 891 && !WifiUtils.isNetworkLockedDown(mContext, mWifiEntry.getWifiConfiguration()); 892 } 893 894 /** 895 * Returns whether the user can sign into the network represented by this preference. 896 */ canSignIntoNetwork()897 private boolean canSignIntoNetwork() { 898 return mWifiEntry.canSignIn(); 899 } 900 901 /** 902 * Returns whether the user can share the network represented by this preference with QR code. 903 */ canShareNetwork()904 private boolean canShareNetwork() { 905 return mWifiEntry.canShare(); 906 } 907 908 /** 909 * Forgets the wifi network associated with this preference. 910 */ forgetNetwork()911 private void forgetNetwork() { 912 if (mWifiEntry.isSubscription()) { 913 // Post a dialog to confirm if user really want to forget the passpoint network. 914 showConfirmForgetDialog(); 915 return; 916 } else { 917 mWifiEntry.forget(this); 918 } 919 920 mMetricsFeatureProvider.action( 921 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET); 922 mFragment.getActivity().finish(); 923 } 924 925 @VisibleForTesting showConfirmForgetDialog()926 protected void showConfirmForgetDialog() { 927 final AlertDialog dialog = new AlertDialog.Builder(mContext) 928 .setPositiveButton(R.string.forget, ((dialog1, which) -> { 929 try { 930 mWifiEntry.forget(this); 931 } catch (RuntimeException e) { 932 Log.e(TAG, "Failed to remove Passpoint configuration: " + e); 933 } 934 mMetricsFeatureProvider.action( 935 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET); 936 mFragment.getActivity().finish(); 937 })) 938 .setNegativeButton(R.string.cancel, null /* listener */) 939 .setTitle(R.string.wifi_forget_dialog_title) 940 .setMessage(R.string.forget_passpoint_dialog_message) 941 .create(); 942 dialog.show(); 943 } 944 945 /** 946 * Show QR code to share the network represented by this preference. 947 */ launchWifiDppConfiguratorActivity()948 private void launchWifiDppConfiguratorActivity() { 949 final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext, 950 mWifiManager, mWifiEntry); 951 952 if (intent == null) { 953 Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!"); 954 } else { 955 mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 956 SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE, 957 SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR, 958 /* key */ null, 959 /* value */ Integer.MIN_VALUE); 960 961 mContext.startActivity(intent); 962 } 963 } 964 965 /** 966 * Share the wifi network with QR code. 967 */ shareNetwork()968 private void shareNetwork() { 969 WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity()); 970 } 971 972 /** 973 * Sign in to the captive portal found on this wifi network associated with this preference. 974 */ signIntoNetwork()975 private void signIntoNetwork() { 976 mMetricsFeatureProvider.action( 977 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN); 978 mWifiEntry.signIn(this); 979 } 980 981 @Override onSubmit(WifiDialog2 dialog)982 public void onSubmit(WifiDialog2 dialog) { 983 if (dialog.getController() != null) { 984 mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() { 985 @Override 986 public void onSuccess() { 987 } 988 989 @Override 990 public void onFailure(int reason) { 991 Activity activity = mFragment.getActivity(); 992 if (activity != null) { 993 Toast.makeText(activity, 994 R.string.wifi_failed_save_message, 995 Toast.LENGTH_SHORT).show(); 996 } 997 } 998 }); 999 } 1000 } 1001 1002 /** 1003 * Wrapper for testing compatibility. 1004 */ 1005 @VisibleForTesting 1006 static class IconInjector { 1007 private final Context mContext; 1008 IconInjector(Context context)1009 IconInjector(Context context) { 1010 mContext = context; 1011 } 1012 getIcon(boolean showX, int level)1013 public Drawable getIcon(boolean showX, int level) { 1014 return mContext.getDrawable(Utils.getWifiIconResource(showX, level)).mutate(); 1015 } 1016 } 1017 1018 @VisibleForTesting 1019 static class Clock { now()1020 public ZonedDateTime now() { 1021 return ZonedDateTime.now(); 1022 } 1023 } 1024 usingDataUsageHeader(Context context)1025 private boolean usingDataUsageHeader(Context context) { 1026 return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER); 1027 } 1028 1029 @VisibleForTesting connectDisconnectNetwork()1030 void connectDisconnectNetwork() { 1031 if (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) { 1032 mWifiEntry.connect(this); 1033 } else { 1034 mWifiEntry.disconnect(this); 1035 } 1036 } 1037 1038 /** 1039 * Indicates the state of the WifiEntry has changed and clients may retrieve updates through 1040 * the WifiEntry getter methods. 1041 */ 1042 @Override onUpdated()1043 public void onUpdated() { 1044 updateNetworkInfo(); 1045 refreshPage(); 1046 1047 // Refresh the Preferences in fragment. 1048 ((WifiNetworkDetailsFragment2) mFragment).refreshPreferences(); 1049 } 1050 1051 /** 1052 * Result of the connect request indicated by the CONNECT_STATUS constants. 1053 */ 1054 @Override onConnectResult(@onnectStatus int status)1055 public void onConnectResult(@ConnectStatus int status) { 1056 if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) { 1057 Toast.makeText(mContext, 1058 mContext.getString(R.string.wifi_connected_to_message, mWifiEntry.getTitle()), 1059 Toast.LENGTH_SHORT).show(); 1060 } else if (mWifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) { 1061 Toast.makeText(mContext, 1062 R.string.wifi_not_in_range_message, 1063 Toast.LENGTH_SHORT).show(); 1064 } else { 1065 Toast.makeText(mContext, 1066 R.string.wifi_failed_connect_message, 1067 Toast.LENGTH_SHORT).show(); 1068 } 1069 } 1070 1071 /** 1072 * Result of the disconnect request indicated by the DISCONNECT_STATUS constants. 1073 */ 1074 @Override onDisconnectResult(@isconnectStatus int status)1075 public void onDisconnectResult(@DisconnectStatus int status) { 1076 if (status == DisconnectCallback.DISCONNECT_STATUS_SUCCESS) { 1077 final Activity activity = mFragment.getActivity(); 1078 if (activity != null) { 1079 Toast.makeText(activity, 1080 activity.getString(R.string.wifi_disconnected_from, mWifiEntry.getTitle()), 1081 Toast.LENGTH_SHORT).show(); 1082 } 1083 } else { 1084 Log.e(TAG, "Disconnect Wi-Fi network failed"); 1085 } 1086 } 1087 1088 /** 1089 * Result of the forget request indicated by the FORGET_STATUS constants. 1090 */ 1091 @Override onForgetResult(@orgetStatus int status)1092 public void onForgetResult(@ForgetStatus int status) { 1093 if (status != ForgetCallback.FORGET_STATUS_SUCCESS) { 1094 Log.e(TAG, "Forget Wi-Fi network failed"); 1095 } 1096 1097 mMetricsFeatureProvider.action(mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET); 1098 mFragment.getActivity().finish(); 1099 } 1100 1101 /** 1102 * Result of the sign-in request indicated by the SIGNIN_STATUS constants. 1103 */ 1104 @Override onSignInResult(@ignInStatus int status)1105 public void onSignInResult(@SignInStatus int status) { 1106 refreshPage(); 1107 } 1108 } 1109