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 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 }
    585