OSDN Git Service

Merge "Set max num of slices allowed in panel view" into qt-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / wifi / details / WifiDetailPreferenceController.java
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.wifi.details;
17
18 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_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.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
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.ConnectivityManager;
35 import android.net.ConnectivityManager.NetworkCallback;
36 import android.net.LinkAddress;
37 import android.net.LinkProperties;
38 import android.net.Network;
39 import android.net.NetworkCapabilities;
40 import android.net.NetworkInfo;
41 import android.net.NetworkRequest;
42 import android.net.NetworkUtils;
43 import android.net.RouteInfo;
44 import android.net.wifi.WifiConfiguration;
45 import android.net.wifi.WifiInfo;
46 import android.net.wifi.WifiManager;
47 import android.os.CountDownTimer;
48 import android.os.Handler;
49 import android.text.TextUtils;
50 import android.util.FeatureFlagUtils;
51 import android.util.Log;
52 import android.widget.ImageView;
53 import android.widget.Toast;
54
55 import androidx.annotation.VisibleForTesting;
56 import androidx.core.text.BidiFormatter;
57 import androidx.fragment.app.Fragment;
58 import androidx.preference.Preference;
59 import androidx.preference.PreferenceCategory;
60 import androidx.preference.PreferenceFragmentCompat;
61 import androidx.preference.PreferenceScreen;
62
63 import com.android.settings.R;
64 import com.android.settings.Utils;
65 import com.android.settings.core.FeatureFlags;
66 import com.android.settings.core.PreferenceControllerMixin;
67 import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
68 import com.android.settings.development.featureflags.FeatureFlagPersistent;
69 import com.android.settings.widget.EntityHeaderController;
70 import com.android.settings.wifi.WifiDialog;
71 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
72 import com.android.settings.wifi.WifiUtils;
73 import com.android.settings.wifi.dpp.WifiDppUtils;
74 import com.android.settingslib.core.AbstractPreferenceController;
75 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
76 import com.android.settingslib.core.lifecycle.Lifecycle;
77 import com.android.settingslib.core.lifecycle.LifecycleObserver;
78 import com.android.settingslib.core.lifecycle.events.OnPause;
79 import com.android.settingslib.core.lifecycle.events.OnResume;
80 import com.android.settingslib.widget.ActionButtonsPreference;
81 import com.android.settingslib.widget.LayoutPreference;
82 import com.android.settingslib.wifi.AccessPoint;
83 import com.android.settingslib.wifi.WifiTracker;
84 import com.android.settingslib.wifi.WifiTrackerFactory;
85
86 import java.net.Inet4Address;
87 import java.net.Inet6Address;
88 import java.net.InetAddress;
89 import java.net.UnknownHostException;
90 import java.time.Duration;
91 import java.util.StringJoiner;
92 import java.util.stream.Collectors;
93
94 /**
95  * Controller for logic pertaining to displaying Wifi information for the
96  * {@link WifiNetworkDetailsFragment}.
97  */
98 public class WifiDetailPreferenceController extends AbstractPreferenceController
99         implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
100         OnResume {
101
102     private static final String TAG = "WifiDetailsPrefCtrl";
103     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
104
105     @VisibleForTesting
106     static final String KEY_HEADER = "connection_header";
107     @VisibleForTesting
108     static final String KEY_DATA_USAGE_HEADER = "status_header";
109     @VisibleForTesting
110     static final String KEY_BUTTONS_PREF = "buttons";
111     @VisibleForTesting
112     static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
113     @VisibleForTesting
114     static final String KEY_TX_LINK_SPEED = "tx_link_speed";
115     @VisibleForTesting
116     static final String KEY_RX_LINK_SPEED = "rx_link_speed";
117     @VisibleForTesting
118     static final String KEY_FREQUENCY_PREF = "frequency";
119     @VisibleForTesting
120     static final String KEY_SECURITY_PREF = "security";
121     @VisibleForTesting
122     static final String KEY_SSID_PREF = "ssid";
123     @VisibleForTesting
124     static final String KEY_MAC_ADDRESS_PREF = "mac_address";
125     @VisibleForTesting
126     static final String KEY_IP_ADDRESS_PREF = "ip_address";
127     @VisibleForTesting
128     static final String KEY_GATEWAY_PREF = "gateway";
129     @VisibleForTesting
130     static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
131     @VisibleForTesting
132     static final String KEY_DNS_PREF = "dns";
133     @VisibleForTesting
134     static final String KEY_IPV6_CATEGORY = "ipv6_category";
135     @VisibleForTesting
136     static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
137
138     private static final int STATE_NONE = 1;
139     private static final int STATE_ENABLE_WIFI = 2;
140     private static final int STATE_ENABLE_WIFI_FAILED = 3;
141     private static final int STATE_CONNECTING = 4;
142     private static final int STATE_CONNECTED = 5;
143     private static final int STATE_FAILED = 6;
144     private static final int STATE_NOT_IN_RANGE = 7;
145     private static final int STATE_DISCONNECTED = 8;
146     private static final long TIMEOUT = Duration.ofSeconds(10).toMillis();
147
148     // Be static to avoid too much object not be reset.
149     private static CountDownTimer mTimer;
150
151     private AccessPoint mAccessPoint;
152     private final ConnectivityManager mConnectivityManager;
153     private final Fragment mFragment;
154     private final Handler mHandler;
155     private LinkProperties mLinkProperties;
156     private Network mNetwork;
157     private NetworkInfo mNetworkInfo;
158     private NetworkCapabilities mNetworkCapabilities;
159     private int mRssiSignalLevel = -1;
160     private String[] mSignalStr;
161     private WifiConfiguration mWifiConfig;
162     private WifiInfo mWifiInfo;
163     private final WifiManager mWifiManager;
164     private final WifiTracker mWifiTracker;
165     private final MetricsFeatureProvider mMetricsFeatureProvider;
166     private boolean mIsOutOfRange;
167     private boolean mIsEphemeral;
168     private boolean mConnected;
169     private int mConnectingState;
170     private WifiManager.ActionListener mConnectListener;
171
172     // UI elements - in order of appearance
173     private ActionButtonsPreference mButtonsPref;
174     private EntityHeaderController mEntityHeaderController;
175     private Preference mSignalStrengthPref;
176     private Preference mTxLinkSpeedPref;
177     private Preference mRxLinkSpeedPref;
178     private Preference mFrequencyPref;
179     private Preference mSecurityPref;
180     private Preference mSsidPref;
181     private Preference mMacAddressPref;
182     private Preference mIpAddressPref;
183     private Preference mGatewayPref;
184     private Preference mSubnetPref;
185     private Preference mDnsPref;
186     private PreferenceCategory mIpv6Category;
187     private Preference mIpv6AddressPref;
188     private Lifecycle mLifecycle;
189     Preference mDataUsageSummaryPref;
190     WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
191
192     private final IconInjector mIconInjector;
193     private final IntentFilter mFilter;
194     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
195         @Override
196         public void onReceive(Context context, Intent intent) {
197             switch (intent.getAction()) {
198                 case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
199                     if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
200                             false /* defaultValue */)) {
201                         // only one network changed
202                         WifiConfiguration wifiConfiguration = intent
203                                 .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
204                         if (mAccessPoint.matches(wifiConfiguration)) {
205                             mWifiConfig = wifiConfiguration;
206                         }
207                     }
208                     // fall through
209                 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
210                 case WifiManager.RSSI_CHANGED_ACTION:
211                     refreshPage();
212                     break;
213             }
214         }
215     };
216
217     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
218             .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
219
220     // Must be run on the UI thread since it directly manipulates UI state.
221     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
222         @Override
223         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
224             if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
225                 mLinkProperties = lp;
226                 refreshIpLayerInfo();
227             }
228         }
229
230         private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
231             // If this is the first time we get NetworkCapabilities, report that something changed.
232             if (mNetworkCapabilities == null) return true;
233
234             // nc can never be null, see ConnectivityService#callCallbackForRequest.
235             return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
236         }
237
238         @Override
239         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
240             // If the network just validated or lost Internet access or detected partial internet
241             // connectivity, refresh network state. Don't do this on every NetworkCapabilities
242             // change because refreshNetworkState sends IPCs to the system server from the UI
243             // thread, which can cause jank.
244             if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
245                 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED)
246                         || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)
247                         || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
248                     mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
249                     refreshEntityHeader();
250                 }
251                 mNetworkCapabilities = nc;
252                 refreshButtons();
253                 refreshIpLayerInfo();
254             }
255         }
256
257         @Override
258         public void onLost(Network network) {
259             final boolean lostCurrentNetwork = network.equals(mNetwork);
260             if (lostCurrentNetwork) {
261                 // Should update as disconnect but not exit. Except for ephemeral network which
262                 // should not show on saved network list.
263                 if (!mIsEphemeral) {
264                     return;
265                 }
266
267                 exitActivity();
268             }
269         }
270     };
271
272     private final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
273         /** Called when the state of Wifi has changed. */
274         public void onWifiStateChanged(int state) {
275             Log.d(TAG, "onWifiStateChanged(" + state + ")");
276             if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) {
277                 updateConnectingState(STATE_CONNECTING);
278             } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) {
279                 // update as disconnected once Wi-Fi disabled since may not received
280                 // onConnectedChanged for this case.
281                 updateConnectingState(STATE_DISCONNECTED);
282             }
283         }
284
285         /** Called when the connection state of wifi has changed. */
286         public void onConnectedChanged() {
287             updateAccessPointFromScannedList();
288             if (mConnected != mAccessPoint.isActive()) {
289                 Log.d(TAG, "Connection state changed!");
290                 mConnected = mAccessPoint.isActive();
291                 if (mAccessPoint.isActive()) {
292                     updateConnectingState(STATE_CONNECTED);
293                 } else {
294                     updateConnectingState(STATE_DISCONNECTED);
295                 }
296             }
297         }
298
299         /**
300          * Called to indicate the list of AccessPoints has been updated and
301          * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
302          */
303         public void onAccessPointsChanged() {
304             refreshPage();
305         }
306     };
307
308     public static WifiDetailPreferenceController newInstance(
309             AccessPoint accessPoint,
310             ConnectivityManager connectivityManager,
311             Context context,
312             Fragment fragment,
313             Handler handler,
314             Lifecycle lifecycle,
315             WifiManager wifiManager,
316             MetricsFeatureProvider metricsFeatureProvider) {
317         return new WifiDetailPreferenceController(
318                 accessPoint, connectivityManager, context, fragment, handler, lifecycle,
319                 wifiManager, metricsFeatureProvider, new IconInjector(context));
320     }
321
322     @VisibleForTesting
323         /* package */ WifiDetailPreferenceController(
324             AccessPoint accessPoint,
325             ConnectivityManager connectivityManager,
326             Context context,
327             Fragment fragment,
328             Handler handler,
329             Lifecycle lifecycle,
330             WifiManager wifiManager,
331             MetricsFeatureProvider metricsFeatureProvider,
332             IconInjector injector) {
333         super(context);
334
335         mAccessPoint = accessPoint;
336         mConnectivityManager = connectivityManager;
337         mFragment = fragment;
338         mHandler = handler;
339         mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
340         mWifiConfig = accessPoint.getConfig();
341         mWifiManager = wifiManager;
342         mMetricsFeatureProvider = metricsFeatureProvider;
343         mIconInjector = injector;
344
345         mFilter = new IntentFilter();
346         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
347         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
348         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
349
350         mLifecycle = lifecycle;
351         lifecycle.addObserver(this);
352
353         mWifiTracker = WifiTrackerFactory.create(
354                 mFragment.getActivity(),
355                 mWifiListener,
356                 mLifecycle,
357                 true /*includeSaved*/,
358                 true /*includeScans*/);
359         mConnected = mAccessPoint.isActive();
360         // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we
361         // could not check if the AccessPoint is ephemeral. Need to cache it in first.
362         mIsEphemeral = mAccessPoint.isEphemeral();
363         mConnectingState = STATE_NONE;
364         mConnectListener = new WifiManager.ActionListener() {
365             @Override
366             public void onSuccess() {
367                 // Do nothing
368             }
369
370             @Override
371             public void onFailure(int reason) {
372                 updateConnectingState(STATE_FAILED);
373             }
374         };
375     }
376
377     @Override
378     public boolean isAvailable() {
379         return true;
380     }
381
382     @Override
383     public String getPreferenceKey() {
384         // Returns null since this controller contains more than one Preference
385         return null;
386     }
387
388     @Override
389     public void displayPreference(PreferenceScreen screen) {
390         super.displayPreference(screen);
391
392         setupEntityHeader(screen);
393
394         mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF))
395                 .setButton1Text(R.string.forget)
396                 .setButton1Icon(R.drawable.ic_settings_delete)
397                 .setButton1OnClickListener(view -> forgetNetwork())
398                 .setButton2Text(R.string.wifi_sign_in_button_text)
399                 .setButton2Icon(R.drawable.ic_settings_sign_in)
400                 .setButton2OnClickListener(view -> signIntoNetwork())
401                 .setButton3Text(R.string.wifi_connect)
402                 .setButton3Icon(R.drawable.ic_settings_wireless)
403                 .setButton3OnClickListener(view -> connectNetwork())
404                 .setButton3Enabled(true)
405                 .setButton4Text(R.string.share)
406                 .setButton4Icon(R.drawable.ic_qrcode_24dp)
407                 .setButton4OnClickListener(view -> shareNetwork());
408
409         mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
410         mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
411         mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED);
412         mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF);
413         mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
414
415         mSsidPref = screen.findPreference(KEY_SSID_PREF);
416         mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
417         mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
418         mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
419         mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
420         mDnsPref = screen.findPreference(KEY_DNS_PREF);
421
422         mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
423         mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
424
425         mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
426     }
427
428     private void setupEntityHeader(PreferenceScreen screen) {
429         LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
430
431         if (usingDataUsageHeader(mContext)) {
432             headerPref.setVisible(false);
433             mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER);
434             mDataUsageSummaryPref.setVisible(true);
435             mSummaryHeaderController =
436                 new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(),
437                         mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid());
438             return;
439         }
440
441         mEntityHeaderController =
442                 EntityHeaderController.newInstance(
443                         mFragment.getActivity(), mFragment,
444                         headerPref.findViewById(R.id.entity_header));
445
446         ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
447
448         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
449
450         mEntityHeaderController.setLabel(mAccessPoint.getTitle());
451     }
452
453     private void refreshEntityHeader() {
454         if (usingDataUsageHeader(mContext)) {
455             mSummaryHeaderController.updateState(mDataUsageSummaryPref);
456         } else {
457             mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
458                     .done(mFragment.getActivity(), true /* rebind */);
459         }
460     }
461
462     private void updateNetworkInfo() {
463         mNetwork = mWifiManager.getCurrentNetwork();
464         mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
465         mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
466     }
467
468     @Override
469     public void onResume() {
470         // Ensure mNetwork is set before any callbacks above are delivered, since our
471         // NetworkCallback only looks at changes to mNetwork.
472         updateNetworkInfo();
473         refreshPage();
474         mContext.registerReceiver(mReceiver, mFilter);
475         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
476                 mHandler);
477     }
478
479     @Override
480     public void onPause() {
481         mNetwork = null;
482         mLinkProperties = null;
483         mNetworkCapabilities = null;
484         mNetworkInfo = null;
485         mWifiInfo = null;
486         mContext.unregisterReceiver(mReceiver);
487         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
488     }
489
490     private void refreshPage() {
491         if(!updateAccessPoint()) {
492             return;
493         }
494
495         Log.d(TAG, "Update UI!");
496
497         // refresh header
498         refreshEntityHeader();
499
500         // refresh Buttons
501         refreshButtons();
502
503         // Update Connection Header icon and Signal Strength Preference
504         refreshRssiViews();
505         // Frequency Pref
506         refreshFrequency();
507         // Transmit Link Speed Pref
508         refreshTxSpeed();
509         // Receive Link Speed Pref
510         refreshRxSpeed();
511         // IP related information
512         refreshIpLayerInfo();
513         // SSID Pref
514         refreshSsid();
515         // MAC Address Pref
516         refreshMacAddress();
517     }
518
519     private boolean updateAccessPoint() {
520         boolean changed = false;
521         if (mWifiTracker != null) {
522             // remember mIsOutOfRange as old before updated
523             boolean oldState = mIsOutOfRange;
524             updateAccessPointFromScannedList();
525             // refresh UI if signal level changed for disconnect network.
526             changed = mRssiSignalLevel != mAccessPoint.getLevel();
527             changed |= oldState != mIsOutOfRange;
528         }
529
530         if (mAccessPoint.isActive()) {
531             // Sometimes {@link WifiManager#getCurrentNetwork()} return null after connected,
532             // refresh it if needed.
533             if (mNetwork == null) {
534                 updateNetworkInfo();
535             }
536             mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
537             mWifiInfo = mWifiManager.getConnectionInfo();
538             if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
539                 // Once connected, can't get mNetworkInfo immediately, return false and wait for
540                 // next time to update UI.
541                 return false;
542             }
543
544             changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
545             // If feature for saved network not enabled, always return true.
546             return mWifiTracker == null || changed;
547         }
548
549         return changed;
550     }
551
552     private void updateAccessPointFromScannedList() {
553         if (mWifiTracker == null) return;
554
555         mIsOutOfRange = true;
556
557         for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
558             if (mAccessPoint.matches(ap)) {
559                 mAccessPoint = ap;
560                 mWifiConfig = ap.getConfig();
561                 mIsOutOfRange = !mAccessPoint.isReachable();
562                 return;
563             }
564         }
565     }
566
567     private void exitActivity() {
568         if (DEBUG) {
569             Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
570         }
571         mFragment.getActivity().finish();
572     }
573
574     private void refreshRssiViews() {
575         int signalLevel = mAccessPoint.getLevel();
576
577         // Disappears signal view if not in range. e.g. for saved networks.
578         if (mIsOutOfRange) {
579             mSignalStrengthPref.setVisible(false);
580             mRssiSignalLevel = -1;
581             return;
582         }
583
584         if (mRssiSignalLevel == signalLevel) {
585             return;
586         }
587         mRssiSignalLevel = signalLevel;
588         Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
589
590         if (mEntityHeaderController != null) {
591             mEntityHeaderController
592                     .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(),
593                             true /* rebind */);
594         }
595
596         Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
597         wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
598         mSignalStrengthPref.setIcon(wifiIconDark);
599
600         mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
601         mSignalStrengthPref.setVisible(true);
602     }
603
604     private Drawable redrawIconForHeader(Drawable original) {
605         final int iconSize = mContext.getResources().getDimensionPixelSize(
606                 R.dimen.wifi_detail_page_header_image_size);
607         final int actualWidth = original.getMinimumWidth();
608         final int actualHeight = original.getMinimumHeight();
609
610         if ((actualWidth == iconSize && actualHeight == iconSize)
611                 || !VectorDrawable.class.isInstance(original)) {
612             return original;
613         }
614
615         // clear tint list to make sure can set 87% black after enlarge
616         original.setTintList(null);
617
618         // enlarge icon size
619         final Bitmap bitmap = Utils.createBitmap(original,
620                 iconSize /*width*/,
621                 iconSize /*height*/);
622         Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap);
623
624         // config color for 87% black after enlarge
625         newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
626
627         return newIcon;
628     }
629
630     private void refreshFrequency() {
631         if (mWifiInfo == null) {
632             mFrequencyPref.setVisible(false);
633             return;
634         }
635
636         final int frequency = mWifiInfo.getFrequency();
637         String band = null;
638         if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
639                 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
640             band = mContext.getResources().getString(R.string.wifi_band_24ghz);
641         } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
642                 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
643             band = mContext.getResources().getString(R.string.wifi_band_5ghz);
644         } else {
645             Log.e(TAG, "Unexpected frequency " + frequency);
646             // Connecting state is unstable, make it disappeared if unexpected
647             if (mConnectingState == STATE_CONNECTING) {
648                 mFrequencyPref.setVisible(false);
649             }
650             return;
651         }
652         mFrequencyPref.setSummary(band);
653         mFrequencyPref.setVisible(true);
654     }
655
656     private void refreshTxSpeed() {
657         if (mWifiInfo == null) {
658             mTxLinkSpeedPref.setVisible(false);
659             return;
660         }
661
662         int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
663         mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
664         mTxLinkSpeedPref.setSummary(mContext.getString(
665                 R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
666     }
667
668     private void refreshRxSpeed() {
669         if (mWifiInfo == null) {
670             mRxLinkSpeedPref.setVisible(false);
671             return;
672         }
673
674         int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
675         mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
676         mRxLinkSpeedPref.setSummary(mContext.getString(
677                 R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
678     }
679
680     private void refreshSsid() {
681         if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
682             mSsidPref.setVisible(true);
683             mSsidPref.setSummary(mAccessPoint.getSsidStr());
684         } else {
685             mSsidPref.setVisible(false);
686         }
687     }
688
689     private void refreshMacAddress() {
690         String macAddress = getMacAddress();
691         if (macAddress == null) {
692             mMacAddressPref.setVisible(false);
693             return;
694         }
695
696         mMacAddressPref.setVisible(true);
697         mMacAddressPref.setSummary(macAddress);
698     }
699
700     private String getMacAddress() {
701         if (mWifiInfo != null) {
702             // get MAC address from connected network information
703             return mWifiInfo.getMacAddress();
704         }
705
706         // return randomized MAC address
707         if (mWifiConfig != null &&
708                 mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
709             return mWifiConfig.getRandomizedMacAddress().toString();
710         }
711
712         // return device MAC address
713         final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
714         if (macAddresses != null && macAddresses.length > 0) {
715             return macAddresses[0];
716         }
717
718         Log.e(TAG, "Can't get device MAC address!");
719         return null;
720     }
721
722     private void updatePreference(Preference pref, String detailText) {
723         if (!TextUtils.isEmpty(detailText)) {
724             pref.setSummary(detailText);
725             pref.setVisible(true);
726         } else {
727             pref.setVisible(false);
728         }
729     }
730
731     private void refreshButtons() {
732         // Ephemeral network won't be removed permanently, but be putted in blacklist.
733         mButtonsPref.setButton1Text(
734                 mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
735
736         boolean canForgetNetwork = canForgetNetwork();
737         boolean canSignIntoNetwork = canSignIntoNetwork();
738         boolean canConnectNetwork = canConnectNetwork();
739         boolean canShareNetwork = canShareNetwork();
740
741         mButtonsPref.setButton1Visible(canForgetNetwork);
742         mButtonsPref.setButton2Visible(canSignIntoNetwork);
743         mButtonsPref.setButton3Visible(canConnectNetwork);
744         mButtonsPref.setButton4Visible(canShareNetwork);
745         mButtonsPref.setVisible(canForgetNetwork
746                 || canSignIntoNetwork
747                 || canConnectNetwork
748                 || canShareNetwork);
749     }
750
751     private boolean canConnectNetwork() {
752         // Display connect button for disconnected AP even not in the range.
753         return !mAccessPoint.isActive();
754     }
755
756     private void refreshIpLayerInfo() {
757         // Hide IP layer info if not a connected network.
758         if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
759             mIpAddressPref.setVisible(false);
760             mSubnetPref.setVisible(false);
761             mGatewayPref.setVisible(false);
762             mDnsPref.setVisible(false);
763             mIpv6Category.setVisible(false);
764             return;
765         }
766
767         // Find IPv4 and IPv6 addresses.
768         String ipv4Address = null;
769         String subnet = null;
770         StringJoiner ipv6Addresses = new StringJoiner("\n");
771
772         for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
773             if (addr.getAddress() instanceof Inet4Address) {
774                 ipv4Address = addr.getAddress().getHostAddress();
775                 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
776             } else if (addr.getAddress() instanceof Inet6Address) {
777                 ipv6Addresses.add(addr.getAddress().getHostAddress());
778             }
779         }
780
781         // Find IPv4 default gateway.
782         String gateway = null;
783         for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
784             if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
785                 gateway = routeInfo.getGateway().getHostAddress();
786                 break;
787             }
788         }
789
790         // Find all (IPv4 and IPv6) DNS addresses.
791         String dnsServers = mLinkProperties.getDnsServers().stream()
792                 .map(InetAddress::getHostAddress)
793                 .collect(Collectors.joining("\n"));
794
795         // Update UI.
796         updatePreference(mIpAddressPref, ipv4Address);
797         updatePreference(mSubnetPref, subnet);
798         updatePreference(mGatewayPref, gateway);
799         updatePreference(mDnsPref, dnsServers);
800
801         if (ipv6Addresses.length() > 0) {
802             mIpv6AddressPref.setSummary(
803                     BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
804             mIpv6Category.setVisible(true);
805         } else {
806             mIpv6Category.setVisible(false);
807         }
808     }
809
810     private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
811         try {
812             InetAddress all = InetAddress.getByAddress(
813                     new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
814             return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
815         } catch (UnknownHostException e) {
816             return null;
817         }
818     }
819
820     /**
821      * Returns whether the network represented by this preference can be forgotten.
822      */
823     private boolean canForgetNetwork() {
824         return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork()
825                 || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig();
826     }
827
828     /**
829      * Returns whether the network represented by this preference can be modified.
830      */
831     public boolean canModifyNetwork() {
832         return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
833     }
834
835     /**
836      * Returns whether the user can sign into the network represented by this preference.
837      */
838     private boolean canSignIntoNetwork() {
839         return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
840     }
841
842     /**
843      * Returns whether the user can share the network represented by this preference with QR code.
844      */
845     private boolean canShareNetwork() {
846         return mAccessPoint.getConfig() != null &&
847                 WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint);
848     }
849
850     /**
851      * Forgets the wifi network associated with this preference.
852      */
853     private void forgetNetwork() {
854         if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
855             mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
856         } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
857             // Post a dialog to confirm if user really want to forget the passpoint network.
858             if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) {
859                 showConfirmForgetDialog();
860                 return;
861             }
862
863             try {
864                 mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
865             } catch (RuntimeException e) {
866                 Log.e(TAG, "Failed to remove Passpoint configuration for "
867                         + mAccessPoint.getPasspointFqdn());
868             }
869         } else if (mWifiConfig != null) {
870             mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
871         }
872
873         mMetricsFeatureProvider.action(
874                 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
875         mFragment.getActivity().finish();
876     }
877
878     @VisibleForTesting
879     protected void showConfirmForgetDialog() {
880         final AlertDialog dialog = new AlertDialog.Builder(mContext)
881                 .setPositiveButton(R.string.forget, ((dialog1, which) -> {
882                     try {
883                         mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
884                     } catch (RuntimeException e) {
885                         Log.e(TAG, "Failed to remove Passpoint configuration for "
886                                 + mAccessPoint.getPasspointFqdn());
887                     }
888                     mMetricsFeatureProvider.action(
889                             mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
890                     mFragment.getActivity().finish();
891                 }))
892                 .setNegativeButton(R.string.cancel, null /* listener */)
893                 .setTitle(R.string.wifi_forget_dialog_title)
894                 .setMessage(R.string.forget_passpoint_dialog_message)
895                 .create();
896         dialog.show();
897     }
898
899     /**
900      * Show QR code to share the network represented by this preference.
901      */
902     public void launchWifiDppConfiguratorActivity() {
903         final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext,
904                 mWifiManager, mAccessPoint);
905
906         if (intent == null) {
907             Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!");
908         } else {
909             mContext.startActivity(intent);
910         }
911     }
912
913     /**
914      * Share the wifi network with QR code.
915      */
916     private void shareNetwork() {
917         WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
918     }
919
920     /**
921      * Sign in to the captive portal found on this wifi network associated with this preference.
922      */
923     private void signIntoNetwork() {
924         mMetricsFeatureProvider.action(
925                 mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN);
926         mConnectivityManager.startCaptivePortalApp(mNetwork);
927     }
928
929     @Override
930     public void onSubmit(WifiDialog dialog) {
931         if (dialog.getController() != null) {
932             mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
933                 @Override
934                 public void onSuccess() {
935                 }
936
937                 @Override
938                 public void onFailure(int reason) {
939                     Activity activity = mFragment.getActivity();
940                     if (activity != null) {
941                         Toast.makeText(activity,
942                                 R.string.wifi_failed_save_message,
943                                 Toast.LENGTH_SHORT).show();
944                     }
945                 }
946             });
947         }
948     }
949
950     /**
951      * Wrapper for testing compatibility.
952      */
953     @VisibleForTesting
954     static class IconInjector {
955         private final Context mContext;
956
957         public IconInjector(Context context) {
958             mContext = context;
959         }
960
961         public Drawable getIcon(int level) {
962             return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
963         }
964     }
965
966     private boolean usingDataUsageHeader(Context context) {
967         return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
968     }
969
970     private void connectNetwork() {
971         final Activity activity = mFragment.getActivity();
972         // error handling, connected/saved network should have mWifiConfig.
973         if (mWifiConfig == null) {
974             Toast.makeText(activity,
975                     R.string.wifi_failed_connect_message,
976                     Toast.LENGTH_SHORT).show();
977             return;
978         }
979
980         // init state before connect
981         mConnectingState = STATE_NONE;
982
983         if (mWifiManager.isWifiEnabled()) {
984             updateConnectingState(STATE_CONNECTING);
985         } else {
986             // Enable Wi-Fi automatically to connect AP
987             updateConnectingState(STATE_ENABLE_WIFI);
988         }
989     }
990
991     private void updateConnectingState(int state) {
992         final Activity activity = mFragment.getActivity();
993         Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state);
994         switch (mConnectingState) {
995             case STATE_NONE:
996             case STATE_ENABLE_WIFI:
997                 if (state == STATE_ENABLE_WIFI) {
998                     Log.d(TAG, "Turn on Wi-Fi automatically!");
999                     updateConnectedButton(STATE_ENABLE_WIFI);
1000                     Toast.makeText(activity,
1001                             R.string.wifi_turned_on_message,
1002                             Toast.LENGTH_SHORT).show();
1003                     mWifiManager.setWifiEnabled(true);
1004                     // start timer for error handling
1005                     startTimer();
1006                 } else if (state == STATE_CONNECTING) {
1007                     Log.d(TAG, "connecting...");
1008                     updateConnectedButton(STATE_CONNECTING);
1009                     if (mAccessPoint.isPasspoint()) {
1010                         mWifiManager.connect(mWifiConfig, mConnectListener);
1011                     } else {
1012                         mWifiManager.connect(mWifiConfig.networkId, mConnectListener);
1013                     }
1014                     // start timer for error handling since framework didn't call back if failed
1015                     startTimer();
1016                 } else if (state == STATE_ENABLE_WIFI_FAILED) {
1017                     Log.e(TAG, "Wi-Fi failed to enable network!");
1018                     stopTimer();
1019                     // reset state
1020                     state = STATE_NONE;
1021                     Toast.makeText(activity,
1022                             R.string.wifi_failed_connect_message,
1023                             Toast.LENGTH_SHORT).show();
1024                     updateConnectedButton(STATE_ENABLE_WIFI_FAILED);
1025                 }
1026                 // Do not break here for disconnected event.
1027             case STATE_CONNECTED:
1028                 if (state == STATE_DISCONNECTED) {
1029                     Log.d(TAG, "disconnected");
1030                     // reset state
1031                     state = STATE_NONE;
1032                     updateConnectedButton(STATE_DISCONNECTED);
1033                     refreshPage();
1034                     // clear for getting MAC Address from saved configuration
1035                     mWifiInfo = null;
1036                 }
1037                 break;
1038             case STATE_CONNECTING:
1039                 if (state == STATE_CONNECTED) {
1040                     Log.d(TAG, "connected");
1041                     stopTimer();
1042                     updateConnectedButton(STATE_CONNECTED);
1043                     Toast.makeText(activity,
1044                             mContext.getString(R.string.wifi_connected_to_message,
1045                                     mAccessPoint.getTitle()),
1046                             Toast.LENGTH_SHORT).show();
1047
1048                     updateNetworkInfo();
1049                     refreshPage();
1050                 } else if (state == STATE_NOT_IN_RANGE) {
1051                     Log.d(TAG, "AP not in range");
1052                     stopTimer();
1053                     // reset state
1054                     state = STATE_NONE;
1055                     Toast.makeText(activity,
1056                             R.string.wifi_not_in_range_message,
1057                             Toast.LENGTH_SHORT).show();
1058                     updateConnectedButton(STATE_NOT_IN_RANGE);
1059                 } else if (state == STATE_FAILED) {
1060                     Log.d(TAG, "failed");
1061                     stopTimer();
1062                     // reset state
1063                     state = STATE_NONE;
1064                     Toast.makeText(activity,
1065                             R.string.wifi_failed_connect_message,
1066                             Toast.LENGTH_SHORT).show();
1067                     updateConnectedButton(STATE_FAILED);
1068                 }
1069                 break;
1070             default:
1071                 Log.e(TAG, "Invalid state : " + mConnectingState);
1072                 // don't update invalid state
1073                 return;
1074         }
1075
1076         mConnectingState = state;
1077     }
1078
1079     private void updateConnectedButton(int state) {
1080         switch (state) {
1081             case STATE_ENABLE_WIFI:
1082             case STATE_CONNECTING:
1083                 mButtonsPref.setButton3Text(R.string.wifi_connecting)
1084                         .setButton3Enabled(false);
1085                 break;
1086             case STATE_CONNECTED:
1087                 mButtonsPref.setButton3Visible(false);
1088                 break;
1089             case STATE_DISCONNECTED:
1090             case STATE_NOT_IN_RANGE:
1091             case STATE_FAILED:
1092             case STATE_ENABLE_WIFI_FAILED:
1093                 mButtonsPref.setButton3Text(R.string.wifi_connect)
1094                         .setButton3Icon(R.drawable.ic_settings_wireless)
1095                         .setButton3Enabled(true)
1096                         .setButton3Visible(true);
1097                 break;
1098             default:
1099                 Log.e(TAG, "Invalid connect button state : " + state);
1100                 break;
1101         }
1102     }
1103
1104     private void startTimer() {
1105         if (mTimer != null) {
1106             stopTimer();
1107         }
1108
1109         mTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) {
1110             @Override
1111             public void onTick(long millisUntilFinished) {
1112                 // Do nothing
1113             }
1114             @Override
1115             public void onFinish() {
1116                 Log.e(TAG, "Timeout for state:" + mConnectingState);
1117                 if (mConnectingState == STATE_ENABLE_WIFI) {
1118                     updateConnectingState(STATE_ENABLE_WIFI_FAILED);
1119                 } else if (mConnectingState == STATE_CONNECTING) {
1120                     updateAccessPointFromScannedList();
1121                     if (mIsOutOfRange) {
1122                         updateConnectingState(STATE_NOT_IN_RANGE);
1123                     } else {
1124                         updateConnectingState(STATE_FAILED);
1125                     }
1126                 }
1127             }
1128         };
1129         mTimer.start();
1130     }
1131
1132     private void stopTimer() {
1133         if (mTimer == null) return;
1134
1135         mTimer.cancel();
1136         mTimer = null;
1137     }
1138 }