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