OSDN Git Service

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