• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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