OSDN Git Service

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