• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
newInstance( AccessPoint accessPoint, ConnectivityManager connectivityManager, Context context, Fragment fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider)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
WifiDetailPreferenceController( AccessPoint accessPoint, ConnectivityManager connectivityManager, Context context, Fragment fragment, Handler handler, Lifecycle lifecycle, WifiManager wifiManager, MetricsFeatureProvider metricsFeatureProvider, IconInjector injector)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
isAvailable()262     public boolean isAvailable() {
263         return true;
264     }
265 
266     @Override
getPreferenceKey()267     public String getPreferenceKey() {
268         // Returns null since this controller contains more than one Preference
269         return null;
270     }
271 
272     @Override
displayPreference(PreferenceScreen screen)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 
setupEntityHeader(PreferenceScreen screen)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
onResume()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
onPause()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 
updateInfo()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 
exitActivity()387     private void exitActivity() {
388         if (DEBUG) {
389             Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
390         }
391         mFragment.getActivity().finish();
392     }
393 
refreshNetworkState()394     private void refreshNetworkState() {
395         mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
396         mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
397                 .done(mFragment.getActivity(), true /* rebind */);
398     }
399 
refreshRssiViews()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 
updatePreference(WifiDetailPreference pref, String detailText)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 
updateIpLayerInfo()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 
ipv4PrefixLengthToSubnetMask(int prefixLength)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      */
canForgetNetwork()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      */
canModifyNetwork()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      */
canSignIntoNetwork()512     private boolean canSignIntoNetwork() {
513         return WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
514     }
515 
516     /**
517      * Forgets the wifi network associated with this preference.
518      */
forgetNetwork()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      */
signIntoNetwork()537     private void signIntoNetwork() {
538         mMetricsFeatureProvider.action(
539                 mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
540         mConnectivityManager.startCaptivePortalApp(mNetwork);
541     }
542 
543     @Override
onForget(WifiDialog dialog)544     public void onForget(WifiDialog dialog) {
545         // can't forget network from a 'modify' dialog
546     }
547 
548     @Override
onSubmit(WifiDialog dialog)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 
IconInjector(Context context)576         public IconInjector(Context context) {
577             mContext = context;
578         }
579 
getIcon(int level)580         public Drawable getIcon(int level) {
581             return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
582         }
583     }
584 }
585