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.Fragment;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.graphics.drawable.Drawable;
28 import android.net.ConnectivityManager;
29 import android.net.ConnectivityManager.NetworkCallback;
30 import android.net.IpPrefix;
31 import android.net.LinkAddress;
32 import android.net.LinkProperties;
33 import android.net.Network;
34 import android.net.NetworkBadging;
35 import android.net.NetworkCapabilities;
36 import android.net.NetworkInfo;
37 import android.net.NetworkRequest;
38 import android.net.NetworkUtils;
39 import android.net.RouteInfo;
40 import android.net.wifi.WifiConfiguration;
41 import android.net.wifi.WifiInfo;
42 import android.net.wifi.WifiManager;
43 import android.os.Handler;
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.view.View;
50 import android.widget.Button;
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.applications.LayoutPreference;
56 import com.android.settings.core.PreferenceController;
57 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
58 import com.android.settings.core.lifecycle.Lifecycle;
59 import com.android.settings.core.lifecycle.LifecycleObserver;
60 import com.android.settings.core.lifecycle.events.OnPause;
61 import com.android.settings.core.lifecycle.events.OnResume;
62 import com.android.settings.vpn2.ConnectivityManagerWrapper;
63 import com.android.settings.wifi.WifiDetailPreference;
64 import com.android.settingslib.wifi.AccessPoint;
65 
66 import java.net.Inet4Address;
67 import java.net.Inet6Address;
68 import java.net.InetAddress;
69 import java.net.UnknownHostException;
70 import java.util.List;
71 import java.util.StringJoiner;
72 import java.util.stream.Collectors;
73 
74 /**
75  * Controller for logic pertaining to displaying Wifi information for the
76  * {@link WifiNetworkDetailsFragment}.
77  */
78 public class WifiDetailPreferenceController extends PreferenceController implements
79         LifecycleObserver, OnPause, OnResume {
80     private static final String TAG = "WifiDetailsPrefCtrl";
81     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
82 
83     @VisibleForTesting
84     static final String KEY_CONNECTION_DETAIL_PREF = "connection_detail";
85     @VisibleForTesting
86     static final String KEY_BUTTONS_PREF = "buttons";
87     @VisibleForTesting
88     static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
89     @VisibleForTesting
90     static final String KEY_LINK_SPEED = "link_speed";
91     @VisibleForTesting
92     static final String KEY_FREQUENCY_PREF = "frequency";
93     @VisibleForTesting
94     static final String KEY_SECURITY_PREF = "security";
95     @VisibleForTesting
96     static final String KEY_MAC_ADDRESS_PREF = "mac_address";
97     @VisibleForTesting
98     static final String KEY_IP_ADDRESS_PREF = "ip_address";
99     @VisibleForTesting
100     static final String KEY_GATEWAY_PREF = "gateway";
101     @VisibleForTesting
102     static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
103     @VisibleForTesting
104     static final String KEY_DNS_PREF = "dns";
105     @VisibleForTesting
106     static final String KEY_IPV6_CATEGORY = "ipv6_category";
107     @VisibleForTesting
108     static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
109 
110     private AccessPoint mAccessPoint;
111     private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
112     private final ConnectivityManager mConnectivityManager;
113     private final Fragment mFragment;
114     private final Handler mHandler;
115     private LinkProperties mLinkProperties;
116     private Network mNetwork;
117     private NetworkInfo mNetworkInfo;
118     private NetworkCapabilities mNetworkCapabilities;
119     private Context mPrefContext;
120     private int mRssi;
121     private String[] mSignalStr;
122     private final WifiConfiguration mWifiConfig;
123     private WifiInfo mWifiInfo;
124     private final WifiManager mWifiManager;
125     private final MetricsFeatureProvider mMetricsFeatureProvider;
126 
127     // UI elements - in order of appearance
128     private Preference mConnectionDetailPref;
129     private LayoutPreference mButtonsPref;
130     private Button mForgetButton;
131     private Button mSignInButton;
132     private WifiDetailPreference mSignalStrengthPref;
133     private WifiDetailPreference mLinkSpeedPref;
134     private WifiDetailPreference mFrequencyPref;
135     private WifiDetailPreference mSecurityPref;
136     private WifiDetailPreference mMacAddressPref;
137     private WifiDetailPreference mIpAddressPref;
138     private WifiDetailPreference mGatewayPref;
139     private WifiDetailPreference mSubnetPref;
140     private WifiDetailPreference mDnsPref;
141     private PreferenceCategory mIpv6Category;
142     private Preference mIpv6AddressPref;
143 
144     private final IntentFilter mFilter;
145     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
146         @Override
147         public void onReceive(Context context, Intent intent) {
148             switch (intent.getAction()) {
149                 case WifiManager.NETWORK_STATE_CHANGED_ACTION:
150                 case WifiManager.RSSI_CHANGED_ACTION:
151                     updateInfo();
152             }
153         }
154     };
155 
156     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
157             .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
158 
159     // Must be run on the UI thread since it directly manipulates UI state.
160     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
161         @Override
162         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
163             if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
164                 mLinkProperties = lp;
165                 updateIpLayerInfo();
166             }
167         }
168 
169         private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
170             // If this is the first time we get NetworkCapabilities, report that something changed.
171             if (mNetworkCapabilities == null) return true;
172 
173             // nc can never be null, see ConnectivityService#callCallbackForRequest.
174             return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
175         }
176 
177         @Override
178         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
179             // If the network just validated or lost Internet access, refresh network state.
180             // Don't do this on every NetworkCapabilities change because refreshNetworkState
181             // sends IPCs to the system server from the UI thread, which can cause jank.
182             if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
183                 if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
184                         hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
185                     refreshNetworkState();
186                 }
187                 mNetworkCapabilities = nc;
188                 updateIpLayerInfo();
189             }
190         }
191 
192         @Override
193         public void onLost(Network network) {
194             if (network.equals(mNetwork)) {
195                 exitActivity();
196             }
197         }
198     };
199 
WifiDetailPreferenceController( AccessPoint accessPoint, ConnectivityManagerWrapper connectivityManagerWrapper, Context context, Fragment fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider)200     public WifiDetailPreferenceController(
201             AccessPoint accessPoint,
202             ConnectivityManagerWrapper connectivityManagerWrapper,
203             Context context,
204             Fragment fragment,
205             Handler handler,
206             Lifecycle lifecycle,
207             WifiManager wifiManager,
208             MetricsFeatureProvider metricsFeatureProvider) {
209         super(context);
210 
211         mAccessPoint = accessPoint;
212         mConnectivityManager = connectivityManagerWrapper.getConnectivityManager();
213         mConnectivityManagerWrapper = connectivityManagerWrapper;
214         mFragment = fragment;
215         mHandler = handler;
216         mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
217         mWifiConfig = accessPoint.getConfig();
218         mWifiManager = wifiManager;
219         mMetricsFeatureProvider = metricsFeatureProvider;
220 
221         mFilter = new IntentFilter();
222         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
223         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
224 
225         lifecycle.addObserver(this);
226     }
227 
228     @Override
isAvailable()229     public boolean isAvailable() {
230         return true;
231     }
232 
233     @Override
getPreferenceKey()234     public String getPreferenceKey() {
235         // Returns null since this controller contains more than one Preference
236         return null;
237     }
238 
239     @Override
displayPreference(PreferenceScreen screen)240     public void displayPreference(PreferenceScreen screen) {
241         super.displayPreference(screen);
242 
243         mPrefContext = screen.getPreferenceManager().getContext();
244 
245         mConnectionDetailPref = screen.findPreference(KEY_CONNECTION_DETAIL_PREF);
246 
247         mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
248         mSignInButton = (Button) mButtonsPref.findViewById(R.id.signin_button);
249         mSignInButton.setText(R.string.support_sign_in_button_text);
250         mSignInButton.setOnClickListener(
251             view -> mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork));
252 
253         mSignalStrengthPref =
254                 (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
255         mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
256         mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
257         mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
258 
259         mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
260         mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
261         mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
262         mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
263         mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
264 
265         mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
266         mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
267 
268         mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
269         mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button);
270         mForgetButton.setText(R.string.forget);
271         mForgetButton.setOnClickListener(view -> forgetNetwork());
272     }
273 
274     @Override
onResume()275     public void onResume() {
276         // Ensure mNetwork is set before any callbacks above are delivered, since our
277         // NetworkCallback only looks at changes to mNetwork.
278         mNetwork = mWifiManager.getCurrentNetwork();
279         mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
280         mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
281         updateInfo();
282         mContext.registerReceiver(mReceiver, mFilter);
283         mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
284                 mHandler);
285     }
286 
287     @Override
onPause()288     public void onPause() {
289         mNetwork = null;
290         mLinkProperties = null;
291         mNetworkCapabilities = null;
292         mNetworkInfo = null;
293         mWifiInfo = null;
294         mContext.unregisterReceiver(mReceiver);
295         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
296     }
297 
updateInfo()298     private void updateInfo() {
299         // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
300         // callbacks. mNetwork doesn't change except in onResume.
301         mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
302         mWifiInfo = mWifiManager.getConnectionInfo();
303         if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
304             exitActivity();
305             return;
306         }
307 
308         // Update whether the forgot button should be displayed.
309         mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
310 
311         refreshNetworkState();
312 
313         // Update Connection Header icon and Signal Strength Preference
314         mRssi = mWifiInfo.getRssi();
315         refreshRssiViews();
316 
317         // MAC Address Pref
318         mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
319 
320         // Link Speed Pref
321         int linkSpeedMbps = mWifiInfo.getLinkSpeed();
322         mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
323         mLinkSpeedPref.setDetailText(mContext.getString(
324                 R.string.link_speed, mWifiInfo.getLinkSpeed()));
325 
326         // Frequency Pref
327         final int frequency = mWifiInfo.getFrequency();
328         String band = null;
329         if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
330                 && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
331             band = mContext.getResources().getString(R.string.wifi_band_24ghz);
332         } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
333                 && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
334             band = mContext.getResources().getString(R.string.wifi_band_5ghz);
335         } else {
336             Log.e(TAG, "Unexpected frequency " + frequency);
337         }
338         mFrequencyPref.setDetailText(band);
339 
340         updateIpLayerInfo();
341     }
342 
exitActivity()343     private void exitActivity() {
344         if (DEBUG) {
345             Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
346         }
347         mFragment.getActivity().finish();
348     }
349 
refreshNetworkState()350     private void refreshNetworkState() {
351         mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
352         mConnectionDetailPref.setTitle(mAccessPoint.getSettingsSummary());
353     }
354 
refreshRssiViews()355     private void refreshRssiViews() {
356         int iconSignalLevel = WifiManager.calculateSignalLevel(
357                 mRssi, WifiManager.RSSI_LEVELS);
358         Drawable wifiIcon = NetworkBadging.getWifiIcon(
359                 iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
360 
361         mConnectionDetailPref.setIcon(wifiIcon);
362 
363         Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
364         wifiIconDark.setTint(mContext.getResources().getColor(
365                 R.color.wifi_details_icon_color, mContext.getTheme()));
366         mSignalStrengthPref.setIcon(wifiIconDark);
367 
368         int summarySignalLevel = mAccessPoint.getLevel();
369         mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
370     }
371 
updatePreference(WifiDetailPreference pref, String detailText)372     private void updatePreference(WifiDetailPreference pref, String detailText) {
373         if (!TextUtils.isEmpty(detailText)) {
374             pref.setDetailText(detailText);
375             pref.setVisible(true);
376         } else {
377             pref.setVisible(false);
378         }
379     }
380 
updateIpLayerInfo()381     private void updateIpLayerInfo() {
382         mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
383         mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
384                 || mSignInButton.getVisibility() == View.VISIBLE);
385 
386         if (mNetwork == null || mLinkProperties == null) {
387             mIpAddressPref.setVisible(false);
388             mSubnetPref.setVisible(false);
389             mGatewayPref.setVisible(false);
390             mDnsPref.setVisible(false);
391             mIpv6Category.setVisible(false);
392             return;
393         }
394 
395         // Find IPv4 and IPv6 addresses.
396         String ipv4Address = null;
397         String subnet = null;
398         StringJoiner ipv6Addresses = new StringJoiner("\n");
399 
400         for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
401             if (addr.getAddress() instanceof Inet4Address) {
402                 ipv4Address = addr.getAddress().getHostAddress();
403                 subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
404             } else if (addr.getAddress() instanceof Inet6Address) {
405                 ipv6Addresses.add(addr.getAddress().getHostAddress());
406             }
407         }
408 
409         // Find IPv4 default gateway.
410         String gateway = null;
411         for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
412             if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
413                 gateway = routeInfo.getGateway().getHostAddress();
414                 break;
415             }
416         }
417 
418         // Find IPv4 DNS addresses.
419         String dnsServers = mLinkProperties.getDnsServers().stream()
420                 .filter(Inet4Address.class::isInstance)
421                 .map(InetAddress::getHostAddress)
422                 .collect(Collectors.joining(","));
423 
424         // Update UI.
425         updatePreference(mIpAddressPref, ipv4Address);
426         updatePreference(mSubnetPref, subnet);
427         updatePreference(mGatewayPref, gateway);
428         updatePreference(mDnsPref, dnsServers);
429 
430         if (ipv6Addresses.length() > 0) {
431             mIpv6AddressPref.setSummary(ipv6Addresses.toString());
432             mIpv6Category.setVisible(true);
433         } else {
434             mIpv6Category.setVisible(false);
435         }
436     }
437 
ipv4PrefixLengthToSubnetMask(int prefixLength)438     private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
439         try {
440             InetAddress all = InetAddress.getByAddress(
441                     new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
442             return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
443         } catch (UnknownHostException e) {
444             return null;
445         }
446     }
447 
448     /**
449      * Returns whether the network represented by this preference can be forgotten.
450      */
canForgetNetwork()451     private boolean canForgetNetwork() {
452         return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null;
453     }
454 
455     /**
456      * Returns whether the user can sign into the network represented by this preference.
457      */
canSignIntoNetwork()458     private boolean canSignIntoNetwork() {
459         return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
460                 NET_CAPABILITY_CAPTIVE_PORTAL);
461     }
462 
463     /**
464      * Forgets the wifi network associated with this preference.
465      */
forgetNetwork()466     private void forgetNetwork() {
467         if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
468             mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
469         } else if (mWifiConfig != null) {
470             if (mWifiConfig.isPasspoint()) {
471                 mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
472             } else {
473                 mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
474             }
475         }
476         mMetricsFeatureProvider.action(
477                 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
478         mFragment.getActivity().finish();
479     }
480 }
481