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