OSDN Git Service

[automerger skipped] [automerger] [RESTRICT AUTOMERGE] Make ScreenPinningSettings...
[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_VALIDATED;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21
22 import android.app.Activity;
23 import android.app.Fragment;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.graphics.drawable.Drawable;
29 import android.net.ConnectivityManager;
30 import android.net.ConnectivityManager.NetworkCallback;
31 import android.net.LinkAddress;
32 import android.net.LinkProperties;
33 import android.net.Network;
34 import android.net.NetworkCapabilities;
35 import android.net.NetworkInfo;
36 import android.net.NetworkRequest;
37 import android.net.NetworkUtils;
38 import android.net.RouteInfo;
39 import android.net.wifi.WifiConfiguration;
40 import android.net.wifi.WifiInfo;
41 import android.net.wifi.WifiManager;
42 import android.os.Handler;
43 import android.support.v4.text.BidiFormatter;
44 import android.support.v7.preference.Preference;
45 import android.support.v7.preference.PreferenceCategory;
46 import android.support.v7.preference.PreferenceScreen;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.widget.ImageView;
50 import android.widget.Toast;
51
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.logging.nano.MetricsProto;
54 import com.android.settings.R;
55 import com.android.settings.Utils;
56 import com.android.settings.applications.LayoutPreference;
57 import com.android.settings.core.PreferenceControllerMixin;
58 import com.android.settings.widget.ActionButtonPreference;
59 import com.android.settings.widget.EntityHeaderController;
60 import com.android.settings.wifi.WifiDetailPreference;
61 import com.android.settings.wifi.WifiDialog;
62 import com.android.settings.wifi.WifiDialog.WifiDialogListener;
63 import com.android.settings.wifi.WifiUtils;
64 import com.android.settingslib.core.AbstractPreferenceController;
65 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
66 import com.android.settingslib.core.lifecycle.Lifecycle;
67 import com.android.settingslib.core.lifecycle.LifecycleObserver;
68 import com.android.settingslib.core.lifecycle.events.OnPause;
69 import com.android.settingslib.core.lifecycle.events.OnResume;
70 import com.android.settingslib.wifi.AccessPoint;
71
72 import java.net.Inet4Address;
73 import java.net.Inet6Address;
74 import java.net.InetAddress;
75 import java.net.UnknownHostException;
76 import java.util.StringJoiner;
77 import java.util.stream.Collectors;
78
79 /**
80  * Controller for logic pertaining to displaying Wifi information for the
81  * {@link WifiNetworkDetailsFragment}.
82  */
83 public class WifiDetailPreferenceController extends AbstractPreferenceController
84         implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
85         OnResume {
86
87     private static final String TAG = "WifiDetailsPrefCtrl";
88     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
89
90     @VisibleForTesting
91     static final String KEY_HEADER = "connection_header";
92     @VisibleForTesting
93     static final String KEY_BUTTONS_PREF = "buttons";
94     @VisibleForTesting
95     static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
96     @VisibleForTesting
97     static final String KEY_LINK_SPEED = "link_speed";
98     @VisibleForTesting
99     static final String KEY_FREQUENCY_PREF = "frequency";
100     @VisibleForTesting
101     static final String KEY_SECURITY_PREF = "security";
102     @VisibleForTesting
103     static final String KEY_MAC_ADDRESS_PREF = "mac_address";
104     @VisibleForTesting
105     static final String KEY_IP_ADDRESS_PREF = "ip_address";
106     @VisibleForTesting
107     static final String KEY_GATEWAY_PREF = "gateway";
108     @VisibleForTesting
109     static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
110     @VisibleForTesting
111     static final String KEY_DNS_PREF = "dns";
112     @VisibleForTesting
113     static final String KEY_IPV6_CATEGORY = "ipv6_category";
114     @VisibleForTesting
115     static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
116
117     private AccessPoint mAccessPoint;
118     private final ConnectivityManager mConnectivityManager;
119     private final Fragment mFragment;
120     private final Handler mHandler;
121     private LinkProperties mLinkProperties;
122     private Network mNetwork;
123     private NetworkInfo mNetworkInfo;
124     private NetworkCapabilities mNetworkCapabilities;
125     private int mRssiSignalLevel = -1;
126     private String[] mSignalStr;
127     private WifiConfiguration mWifiConfig;
128     private WifiInfo mWifiInfo;
129     private final WifiManager mWifiManager;
130     private final MetricsFeatureProvider mMetricsFeatureProvider;
131
132     // UI elements - in order of appearance
133     private ActionButtonPreference mButtonsPref;
134     private EntityHeaderController mEntityHeaderController;
135     private WifiDetailPreference mSignalStrengthPref;
136     private WifiDetailPreference mLinkSpeedPref;
137     private WifiDetailPreference mFrequencyPref;
138     private WifiDetailPreference mSecurityPref;
139     private WifiDetailPreference mMacAddressPref;
140     private WifiDetailPreference mIpAddressPref;
141     private WifiDetailPreference mGatewayPref;
142     private WifiDetailPreference mSubnetPref;
143     private WifiDetailPreference mDnsPref;
144     private PreferenceCategory mIpv6Category;
145     private Preference mIpv6AddressPref;
146
147     private final IconInjector mIconInjector;
148     private final IntentFilter mFilter;
149     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
150         @Override
151         public void onReceive(Context context, Intent intent) {
152             switch (intent.getAction()) {
153                 case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
154                     if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
155                             false /* defaultValue */)) {
156                         // only one network changed
157                         WifiConfiguration wifiConfiguration = intent
158                                 .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
159                         if (mAccessPoint.matches(wifiConfiguration)) {
160                             mWifiConfig = wifiConfiguration;
161                         }
162                     }
163                     // fall through
164                 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
165                 case WifiManager.RSSI_CHANGED_ACTION:
166                     updateInfo();
167                     break;
168             }
169         }
170     };
171
172     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
173             .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
174
175     // Must be run on the UI thread since it directly manipulates UI state.
176     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
177         @Override
178         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
179             if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
180                 mLinkProperties = lp;
181                 updateIpLayerInfo();
182             }
183         }
184
185         private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
186             // If this is the first time we get NetworkCapabilities, report that something changed.
187             if (mNetworkCapabilities == null) return true;
188
189             // nc can never be null, see ConnectivityService#callCallbackForRequest.
190             return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
191         }
192
193         @Override
194         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
195             // If the network just validated or lost Internet access, refresh network state.
196             // Don't do this on every NetworkCapabilities change because refreshNetworkState
197             // sends IPCs to the system server from the UI thread, which can cause jank.
198             if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
199                 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
200                         hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
201                     refreshNetworkState();
202                 }
203                 mNetworkCapabilities = nc;
204                 updateIpLayerInfo();
205             }
206         }
207
208         @Override
209         public void onLost(Network network) {
210             if (network.equals(mNetwork)) {
211                 exitActivity();
212             }
213         }
214     };
215
216     public static WifiDetailPreferenceController newInstance(
217             AccessPoint accessPoint,
218             ConnectivityManager connectivityManager,
219             Context context,
220             Fragment fragment,
221             Handler handler,
222             Lifecycle lifecycle,
223             WifiManager wifiManager,
224             MetricsFeatureProvider metricsFeatureProvider) {
225         return new WifiDetailPreferenceController(
226                 accessPoint, connectivityManager, context, fragment, handler, lifecycle,
227                 wifiManager, metricsFeatureProvider, new IconInjector(context));
228     }
229
230     @VisibleForTesting
231         /* package */ WifiDetailPreferenceController(
232             AccessPoint accessPoint,
233             ConnectivityManager connectivityManager,
234             Context context,
235             Fragment fragment,
236             Handler handler,
237             Lifecycle lifecycle,
238             WifiManager wifiManager,
239             MetricsFeatureProvider metricsFeatureProvider,
240             IconInjector injector) {
241         super(context);
242
243         mAccessPoint = accessPoint;
244         mConnectivityManager = connectivityManager;
245         mFragment = fragment;
246         mHandler = handler;
247         mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
248         mWifiConfig = accessPoint.getConfig();
249         mWifiManager = wifiManager;
250         mMetricsFeatureProvider = metricsFeatureProvider;
251         mIconInjector = injector;
252
253         mFilter = new IntentFilter();
254         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
255         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
256         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
257
258         lifecycle.addObserver(this);
259     }
260
261     @Override
262     public boolean isAvailable() {
263         return true;
264     }
265
266     @Override
267     public String getPreferenceKey() {
268         // Returns null since this controller contains more than one Preference
269         return null;
270     }
271
272     @Override
273     public void displayPreference(PreferenceScreen screen) {
274         super.displayPreference(screen);
275
276         setupEntityHeader(screen);
277
278         mButtonsPref = ((ActionButtonPreference) screen.findPreference(KEY_BUTTONS_PREF))
279                 .setButton1Text(R.string.forget)
280                 .setButton1Positive(false)
281                 .setButton1OnClickListener(view -> forgetNetwork())
282                 .setButton2Text(R.string.wifi_sign_in_button_text)
283                 .setButton2Positive(true)
284                 .setButton2OnClickListener(view -> signIntoNetwork());
285
286         mSignalStrengthPref =
287                 (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
288         mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
289         mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
290         mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
291
292         mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
293         mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
294         mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
295         mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
296         mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
297
298         mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
299         mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
300
301         mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
302     }
303
304     private void setupEntityHeader(PreferenceScreen screen) {
305         LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
306         mEntityHeaderController =
307                 EntityHeaderController.newInstance(
308                         mFragment.getActivity(), mFragment,
309                         headerPref.findViewById(R.id.entity_header));
310
311         ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
312         iconView.setBackground(
313                 mContext.getDrawable(R.drawable.ic_settings_widget_background));
314         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
315
316         mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
317     }
318
319     @Override
320     public void onResume() {
321         // Ensure mNetwork is set before any callbacks above are delivered, since our
322         // NetworkCallback only looks at changes to mNetwork.
323         mNetwork = mWifiManager.getCurrentNetwork();
324         mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
325         mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
326         updateInfo();
327         mContext.registerReceiver(mReceiver, mFilter);
328         mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
329                 mHandler);
330     }
331
332     @Override
333     public void onPause() {
334         mNetwork = null;
335         mLinkProperties = null;
336         mNetworkCapabilities = null;
337         mNetworkInfo = null;
338         mWifiInfo = null;
339         mContext.unregisterReceiver(mReceiver);
340         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
341     }
342
343     private void updateInfo() {
344         // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
345         // callbacks. mNetwork doesn't change except in onResume.
346         mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
347         mWifiInfo = mWifiManager.getConnectionInfo();
348         if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
349             exitActivity();
350             return;
351         }
352
353         // Update whether the forget button should be displayed.
354         mButtonsPref.setButton1Visible(canForgetNetwork());
355
356         refreshNetworkState();
357
358         // Update Connection Header icon and Signal Strength Preference
359         refreshRssiViews();
360
361         // MAC Address Pref
362         mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
363
364         // Link Speed Pref
365         int linkSpeedMbps = mWifiInfo.getLinkSpeed();
366         mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
367         mLinkSpeedPref.setDetailText(mContext.getString(
368                 R.string.link_speed, mWifiInfo.getLinkSpeed()));
369
370         // Frequency Pref
371         final int frequency = mWifiInfo.getFrequency();
372         String band = null;
373         if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
374                 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
375             band = mContext.getResources().getString(R.string.wifi_band_24ghz);
376         } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
377                 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
378             band = mContext.getResources().getString(R.string.wifi_band_5ghz);
379         } else {
380             Log.e(TAG, "Unexpected frequency " + frequency);
381         }
382         mFrequencyPref.setDetailText(band);
383
384         updateIpLayerInfo();
385     }
386
387     private void exitActivity() {
388         if (DEBUG) {
389             Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
390         }
391         mFragment.getActivity().finish();
392     }
393
394     private void refreshNetworkState() {
395         mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
396         mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
397                 .done(mFragment.getActivity(), true /* rebind */);
398     }
399
400     private void refreshRssiViews() {
401         int signalLevel = mAccessPoint.getLevel();
402
403         if (mRssiSignalLevel == signalLevel) {
404             return;
405         }
406         mRssiSignalLevel = signalLevel;
407         Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
408
409         wifiIcon.setTint(Utils.getColorAccent(mContext));
410         mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
411
412         Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
413         wifiIconDark.setTint(mContext.getResources().getColor(
414                 R.color.wifi_details_icon_color, mContext.getTheme()));
415         mSignalStrengthPref.setIcon(wifiIconDark);
416
417         mSignalStrengthPref.setDetailText(mSignalStr[mRssiSignalLevel]);
418     }
419
420     private void updatePreference(WifiDetailPreference pref, String detailText) {
421         if (!TextUtils.isEmpty(detailText)) {
422             pref.setDetailText(detailText);
423             pref.setVisible(true);
424         } else {
425             pref.setVisible(false);
426         }
427     }
428
429     private void updateIpLayerInfo() {
430         mButtonsPref.setButton2Visible(canSignIntoNetwork());
431         mButtonsPref.setVisible(canSignIntoNetwork() || canForgetNetwork());
432
433         if (mNetwork == null || mLinkProperties == null) {
434             mIpAddressPref.setVisible(false);
435             mSubnetPref.setVisible(false);
436             mGatewayPref.setVisible(false);
437             mDnsPref.setVisible(false);
438             mIpv6Category.setVisible(false);
439             return;
440         }
441
442         // Find IPv4 and IPv6 addresses.
443         String ipv4Address = null;
444         String subnet = null;
445         StringJoiner ipv6Addresses = new StringJoiner("\n");
446
447         for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
448             if (addr.getAddress() instanceof Inet4Address) {
449                 ipv4Address = addr.getAddress().getHostAddress();
450                 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
451             } else if (addr.getAddress() instanceof Inet6Address) {
452                 ipv6Addresses.add(addr.getAddress().getHostAddress());
453             }
454         }
455
456         // Find IPv4 default gateway.
457         String gateway = null;
458         for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
459             if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
460                 gateway = routeInfo.getGateway().getHostAddress();
461                 break;
462             }
463         }
464
465         // Find all (IPv4 and IPv6) DNS addresses.
466         String dnsServers = mLinkProperties.getDnsServers().stream()
467                 .map(InetAddress::getHostAddress)
468                 .collect(Collectors.joining("\n"));
469
470         // Update UI.
471         updatePreference(mIpAddressPref, ipv4Address);
472         updatePreference(mSubnetPref, subnet);
473         updatePreference(mGatewayPref, gateway);
474         updatePreference(mDnsPref, dnsServers);
475
476         if (ipv6Addresses.length() > 0) {
477             mIpv6AddressPref.setSummary(
478                     BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
479             mIpv6Category.setVisible(true);
480         } else {
481             mIpv6Category.setVisible(false);
482         }
483     }
484
485     private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
486         try {
487             InetAddress all = InetAddress.getByAddress(
488                     new byte[] {(byte) 255, (byte) 255, (byte) 255, (byte) 255});
489             return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
490         } catch (UnknownHostException e) {
491             return null;
492         }
493     }
494
495     /**
496      * Returns whether the network represented by this preference can be forgotten.
497      */
498     private boolean canForgetNetwork() {
499         return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork();
500     }
501
502     /**
503      * Returns whether the network represented by this preference can be modified.
504      */
505     public boolean canModifyNetwork() {
506         return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
507     }
508
509     /**
510      * Returns whether the user can sign into the network represented by this preference.
511      */
512     private boolean canSignIntoNetwork() {
513         return WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
514     }
515
516     /**
517      * Forgets the wifi network associated with this preference.
518      */
519     private void forgetNetwork() {
520         if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
521             mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
522         } else if (mWifiConfig != null) {
523             if (mWifiConfig.isPasspoint()) {
524                 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
525             } else {
526                 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
527             }
528         }
529         mMetricsFeatureProvider.action(
530                 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
531         mFragment.getActivity().finish();
532     }
533
534     /**
535      * Sign in to the captive portal found on this wifi network associated with this preference.
536      */
537     private void signIntoNetwork() {
538         mMetricsFeatureProvider.action(
539                 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
540         mConnectivityManager.startCaptivePortalApp(mNetwork);
541     }
542
543     @Override
544     public void onForget(WifiDialog dialog) {
545         // can't forget network from a 'modify' dialog
546     }
547
548     @Override
549     public void onSubmit(WifiDialog dialog) {
550         if (dialog.getController() != null) {
551             mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
552                 @Override
553                 public void onSuccess() {
554                 }
555
556                 @Override
557                 public void onFailure(int reason) {
558                     Activity activity = mFragment.getActivity();
559                     if (activity != null) {
560                         Toast.makeText(activity,
561                                 R.string.wifi_failed_save_message,
562                                 Toast.LENGTH_SHORT).show();
563                     }
564                 }
565             });
566         }
567     }
568
569     /**
570      * Wrapper for testing compatibility.
571      */
572     @VisibleForTesting
573     static class IconInjector {
574         private final Context mContext;
575
576         public IconInjector(Context context) {
577             mContext = context;
578         }
579
580         public Drawable getIcon(int level) {
581             return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
582         }
583     }
584 }