/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.wifi.details; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import android.app.Fragment; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkBadging; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; import com.android.settings.applications.LayoutPreference; import com.android.settings.core.PreferenceController; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.core.lifecycle.Lifecycle; import com.android.settings.core.lifecycle.LifecycleObserver; import com.android.settings.core.lifecycle.events.OnPause; import com.android.settings.core.lifecycle.events.OnResume; import com.android.settings.vpn2.ConnectivityManagerWrapper; import com.android.settings.wifi.WifiDetailPreference; import com.android.settingslib.wifi.AccessPoint; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.StringJoiner; import java.util.stream.Collectors; /** * Controller for logic pertaining to displaying Wifi information for the * {@link WifiNetworkDetailsFragment}. */ public class WifiDetailPreferenceController extends PreferenceController implements LifecycleObserver, OnPause, OnResume { private static final String TAG = "WifiDetailsPrefCtrl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @VisibleForTesting static final String KEY_CONNECTION_DETAIL_PREF = "connection_detail"; @VisibleForTesting static final String KEY_BUTTONS_PREF = "buttons"; @VisibleForTesting static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength"; @VisibleForTesting static final String KEY_LINK_SPEED = "link_speed"; @VisibleForTesting static final String KEY_FREQUENCY_PREF = "frequency"; @VisibleForTesting static final String KEY_SECURITY_PREF = "security"; @VisibleForTesting static final String KEY_MAC_ADDRESS_PREF = "mac_address"; @VisibleForTesting static final String KEY_IP_ADDRESS_PREF = "ip_address"; @VisibleForTesting static final String KEY_GATEWAY_PREF = "gateway"; @VisibleForTesting static final String KEY_SUBNET_MASK_PREF = "subnet_mask"; @VisibleForTesting static final String KEY_DNS_PREF = "dns"; @VisibleForTesting static final String KEY_IPV6_CATEGORY = "ipv6_category"; @VisibleForTesting static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; private AccessPoint mAccessPoint; private final ConnectivityManagerWrapper mConnectivityManagerWrapper; private final ConnectivityManager mConnectivityManager; private final Fragment mFragment; private final Handler mHandler; private LinkProperties mLinkProperties; private Network mNetwork; private NetworkInfo mNetworkInfo; private NetworkCapabilities mNetworkCapabilities; private Context mPrefContext; private int mRssi; private String[] mSignalStr; private final WifiConfiguration mWifiConfig; private WifiInfo mWifiInfo; private final WifiManager mWifiManager; private final MetricsFeatureProvider mMetricsFeatureProvider; // UI elements - in order of appearance private Preference mConnectionDetailPref; private LayoutPreference mButtonsPref; private Button mForgetButton; private Button mSignInButton; private WifiDetailPreference mSignalStrengthPref; private WifiDetailPreference mLinkSpeedPref; private WifiDetailPreference mFrequencyPref; private WifiDetailPreference mSecurityPref; private WifiDetailPreference mMacAddressPref; private WifiDetailPreference mIpAddressPref; private WifiDetailPreference mGatewayPref; private WifiDetailPreference mSubnetPref; private WifiDetailPreference mDnsPref; private PreferenceCategory mIpv6Category; private Preference mIpv6AddressPref; private final IntentFilter mFilter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case WifiManager.NETWORK_STATE_CHANGED_ACTION: case WifiManager.RSSI_CHANGED_ACTION: updateInfo(); } } }; private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); // Must be run on the UI thread since it directly manipulates UI state. private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onLinkPropertiesChanged(Network network, LinkProperties lp) { if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { mLinkProperties = lp; updateIpLayerInfo(); } } private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) { // If this is the first time we get NetworkCapabilities, report that something changed. if (mNetworkCapabilities == null) return true; // nc can never be null, see ConnectivityService#callCallbackForRequest. return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap); } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { // If the network just validated or lost Internet access, refresh network state. // Don't do this on every NetworkCapabilities change because refreshNetworkState // sends IPCs to the system server from the UI thread, which can cause jank. if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) { if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) { refreshNetworkState(); } mNetworkCapabilities = nc; updateIpLayerInfo(); } } @Override public void onLost(Network network) { if (network.equals(mNetwork)) { exitActivity(); } } }; public WifiDetailPreferenceController( AccessPoint accessPoint, ConnectivityManagerWrapper connectivityManagerWrapper, Context context, Fragment fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider) { super(context); mAccessPoint = accessPoint; mConnectivityManager = connectivityManagerWrapper.getConnectivityManager(); mConnectivityManagerWrapper = connectivityManagerWrapper; mFragment = fragment; mHandler = handler; mSignalStr = context.getResources().getStringArray(R.array.wifi_signal); mWifiConfig = accessPoint.getConfig(); mWifiManager = wifiManager; mMetricsFeatureProvider = metricsFeatureProvider; mFilter = new IntentFilter(); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); lifecycle.addObserver(this); } @Override public boolean isAvailable() { return true; } @Override public String getPreferenceKey() { // Returns null since this controller contains more than one Preference return null; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPrefContext = screen.getPreferenceManager().getContext(); mConnectionDetailPref = screen.findPreference(KEY_CONNECTION_DETAIL_PREF); mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF); mSignInButton = (Button) mButtonsPref.findViewById(R.id.signin_button); mSignInButton.setText(R.string.support_sign_in_button_text); mSignInButton.setOnClickListener( view -> mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork)); mSignalStrengthPref = (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF); mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED); mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF); mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF); mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF); mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF); mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF); mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF); mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF); mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY); mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF); mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */)); mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button); mForgetButton.setText(R.string.forget); mForgetButton.setOnClickListener(view -> forgetNetwork()); } @Override public void onResume() { // Ensure mNetwork is set before any callbacks above are delivered, since our // NetworkCallback only looks at changes to mNetwork. mNetwork = mWifiManager.getCurrentNetwork(); mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork); mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork); updateInfo(); mContext.registerReceiver(mReceiver, mFilter); mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback, mHandler); } @Override public void onPause() { mNetwork = null; mLinkProperties = null; mNetworkCapabilities = null; mNetworkInfo = null; mWifiInfo = null; mContext.unregisterReceiver(mReceiver); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } private void updateInfo() { // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the // callbacks. mNetwork doesn't change except in onResume. mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork); mWifiInfo = mWifiManager.getConnectionInfo(); if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) { exitActivity(); return; } // Update whether the forgot button should be displayed. mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE); refreshNetworkState(); // Update Connection Header icon and Signal Strength Preference mRssi = mWifiInfo.getRssi(); refreshRssiViews(); // MAC Address Pref mMacAddressPref.setDetailText(mWifiInfo.getMacAddress()); // Link Speed Pref int linkSpeedMbps = mWifiInfo.getLinkSpeed(); mLinkSpeedPref.setVisible(linkSpeedMbps >= 0); mLinkSpeedPref.setDetailText(mContext.getString( R.string.link_speed, mWifiInfo.getLinkSpeed())); // Frequency Pref final int frequency = mWifiInfo.getFrequency(); String band = null; if (frequency >= AccessPoint.LOWER_FREQ_24GHZ && frequency < AccessPoint.HIGHER_FREQ_24GHZ) { band = mContext.getResources().getString(R.string.wifi_band_24ghz); } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ && frequency < AccessPoint.HIGHER_FREQ_5GHZ) { band = mContext.getResources().getString(R.string.wifi_band_5ghz); } else { Log.e(TAG, "Unexpected frequency " + frequency); } mFrequencyPref.setDetailText(band); updateIpLayerInfo(); } private void exitActivity() { if (DEBUG) { Log.d(TAG, "Exiting the WifiNetworkDetailsPage"); } mFragment.getActivity().finish(); } private void refreshNetworkState() { mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo); mConnectionDetailPref.setTitle(mAccessPoint.getSettingsSummary()); } private void refreshRssiViews() { int iconSignalLevel = WifiManager.calculateSignalLevel( mRssi, WifiManager.RSSI_LEVELS); Drawable wifiIcon = NetworkBadging.getWifiIcon( iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate(); mConnectionDetailPref.setIcon(wifiIcon); Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate(); wifiIconDark.setTint(mContext.getResources().getColor( R.color.wifi_details_icon_color, mContext.getTheme())); mSignalStrengthPref.setIcon(wifiIconDark); int summarySignalLevel = mAccessPoint.getLevel(); mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]); } private void updatePreference(WifiDetailPreference pref, String detailText) { if (!TextUtils.isEmpty(detailText)) { pref.setDetailText(detailText); pref.setVisible(true); } else { pref.setVisible(false); } } private void updateIpLayerInfo() { mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE); mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE || mSignInButton.getVisibility() == View.VISIBLE); if (mNetwork == null || mLinkProperties == null) { mIpAddressPref.setVisible(false); mSubnetPref.setVisible(false); mGatewayPref.setVisible(false); mDnsPref.setVisible(false); mIpv6Category.setVisible(false); return; } // Find IPv4 and IPv6 addresses. String ipv4Address = null; String subnet = null; StringJoiner ipv6Addresses = new StringJoiner("\n"); for (LinkAddress addr : mLinkProperties.getLinkAddresses()) { if (addr.getAddress() instanceof Inet4Address) { ipv4Address = addr.getAddress().getHostAddress(); subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength()); } else if (addr.getAddress() instanceof Inet6Address) { ipv6Addresses.add(addr.getAddress().getHostAddress()); } } // Find IPv4 default gateway. String gateway = null; for (RouteInfo routeInfo : mLinkProperties.getRoutes()) { if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { gateway = routeInfo.getGateway().getHostAddress(); break; } } // Find all (IPv4 and IPv6) DNS addresses. String dnsServers = mLinkProperties.getDnsServers().stream() .map(InetAddress::getHostAddress) .collect(Collectors.joining("\n")); // Update UI. updatePreference(mIpAddressPref, ipv4Address); updatePreference(mSubnetPref, subnet); updatePreference(mGatewayPref, gateway); updatePreference(mDnsPref, dnsServers); if (ipv6Addresses.length() > 0) { mIpv6AddressPref.setSummary(ipv6Addresses.toString()); mIpv6Category.setVisible(true); } else { mIpv6Category.setVisible(false); } } private static String ipv4PrefixLengthToSubnetMask(int prefixLength) { try { InetAddress all = InetAddress.getByAddress( new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress(); } catch (UnknownHostException e) { return null; } } /** * Returns whether the network represented by this preference can be forgotten. */ private boolean canForgetNetwork() { return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null; } /** * Returns whether the user can sign into the network represented by this preference. */ private boolean canSignIntoNetwork() { return mNetworkCapabilities != null && mNetworkCapabilities.hasCapability( NET_CAPABILITY_CAPTIVE_PORTAL); } /** * Forgets the wifi network associated with this preference. */ private void forgetNetwork() { if (mWifiInfo != null && mWifiInfo.isEphemeral()) { mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID()); } else if (mWifiConfig != null) { if (mWifiConfig.isPasspoint()) { mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN); } else { mWifiManager.forget(mWifiConfig.networkId, null /* action listener */); } } mMetricsFeatureProvider.action( mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); mFragment.getActivity().finish(); } }