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