1 /*
2  * Copyright (C) 2018 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 
17 package com.google.android.car.kitchensink.connectivity;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SuppressLint;
22 import android.bluetooth.BluetoothAdapter;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.location.LocationManager;
26 import android.net.ConnectivityManager;
27 import android.net.ConnectivityManager.NetworkCallback;
28 import android.net.LinkProperties;
29 import android.net.Network;
30 import android.net.NetworkCapabilities;
31 import android.net.NetworkInfo;
32 import android.net.NetworkRequest;
33 import android.net.wifi.WifiConfiguration;
34 import android.net.wifi.WifiManager;
35 import android.os.Bundle;
36 import android.os.Process;
37 import android.os.UserHandle;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.view.LayoutInflater;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.widget.ListView;
44 import android.widget.TextView;
45 import android.widget.Toast;
46 
47 import androidx.fragment.app.Fragment;
48 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
49 import androidx.viewpager.widget.ViewPager;
50 
51 import com.google.android.car.kitchensink.R;
52 import com.google.android.car.kitchensink.SimplePagerAdapter;
53 
54 import java.net.NetworkInterface;
55 import java.net.SocketException;
56 import java.util.Objects;
57 import java.util.Timer;
58 import java.util.TimerTask;
59 
60 @SuppressLint("SetTextI18n")
61 public class ConnectivityFragment extends Fragment {
62     private static final String TAG = ConnectivityFragment.class.getSimpleName();
63 
64     private ConnectivityManager mConnectivityManager;
65     private WifiManager mWifiManager;
66     private LocationManager mLocationManager;
67 
68     // Sort out current Network objects (NetId -> Network)
69     private SparseArray<Network> mNetworks = new SparseArray<Network>();
70 
71     private TextView mWifiStatusPolled;
72     private TextView mTetheringStatus;
73     private TextView mTetheringStatusPolled;
74     private TextView mLocalOnlyStatus;
75 
76     private Timer mWifiUpdater;
77 
78     /**
79      * Create our own network callback object to use with NetworkRequests. Contains a reference to
80      * a Network so we can be sure to only surface updates on the network we want to see them on.
81      * We have to do this because there isn't a way to say "give me this SPECIFIC network." There's
82      * only "give me A network with these capabilities/transports."
83      */
84     public class NetworkByIdCallback extends NetworkCallback {
85         private final Network mNetwork;
86 
NetworkByIdCallback(@onNull Network n)87         NetworkByIdCallback(@NonNull Network n) {
88             mNetwork = Objects.requireNonNull(n);
89         }
90 
91         @Override
onAvailable(Network n)92         public void onAvailable(Network n) {
93             if (mNetwork.equals(n)) {
94                 showToast("onAvailable(), netId: " + n);
95             }
96         }
97 
98         @Override
onLosing(Network n, int maxMsToLive)99         public void onLosing(Network n, int maxMsToLive) {
100             if (mNetwork.equals(n)) {
101                 showToast("onLosing(), netId: " + n);
102             }
103         }
104 
105         @Override
onLost(Network n)106         public void onLost(Network n) {
107             if (mNetwork.equals(n)) {
108                 showToast("onLost(), netId: " + n);
109             }
110         }
111     }
112 
113     // Map of NetId -> NetworkByIdCallback Objects -- Used to release requested networks
114     SparseArray<NetworkByIdCallback> mNetworkCallbacks = new SparseArray<NetworkByIdCallback>();
115 
116     /**
117      * Implement a swipe-to-refresh list of available networks. NetworkListAdapter takes an array
118      * of NetworkItems that it cascades to the view. SwipeRefreshLayout wraps the adapter.
119      */
120     public static class NetworkItem {
121         public int mNetId;
122         public String mType;
123         public String mState;
124         public String mConnected;
125         public String mAvailable;
126         public String mRoaming;
127         public String mInterfaceName;
128         public String mHwAddress;
129         public String mIpAddresses;
130         public String mDnsAddresses;
131         public String mDomains;
132         public String mRoutes;
133         public String mTransports;
134         public String mCapabilities;
135         public String mBandwidth;
136         public boolean mDefault;
137         public boolean mRequested;
138     }
139 
140     private NetworkItem[] mNetworkItems = new NetworkItem[0];
141     private NetworkListAdapter mNetworksAdapter;
142     private SwipeRefreshLayout mNetworkListRefresher;
143 
144     /**
145      * Builds a NetworkRequest fit to a given network in the hope that we just get updates on that
146      * one network. This is the best way to get single network updates right now, as the request
147      * system works only on transport and capability requirements. There aaaare "network
148      * specifiers" but those only work based on the transport (i.e "eth0" would ask type ETHERNET
149      * for the correct interface where as "GoogleGuest" might ask type WIFI for the Network on SSID
150      * "GoogleGuest"). Ends up being paired with the custom callback above to only surface events
151      * for the specific network in question as well.
152      */
getRequestForNetwork(Network n)153     private NetworkRequest getRequestForNetwork(Network n) {
154         NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(n);
155 
156         NetworkRequest.Builder b = new NetworkRequest.Builder();
157         b.clearCapabilities();
158 
159         for (int transportType : nc.getTransportTypes()) {
160             b.addTransportType(transportType);
161         }
162 
163         for (int capability : nc.getCapabilities()) {
164             // Not all capabilities are requestable. According to source, all mutable capabilities
165             // except trusted are not requestable. Trying to request them results in an error being
166             // thrown
167             if (isRequestableCapability(capability)) {
168                 b.addCapability(capability);
169             }
170         }
171 
172         return b.build();
173     }
174 
isRequestableCapability(int c)175     private boolean isRequestableCapability(int c) {
176         return !(c == NetworkCapabilities.NET_CAPABILITY_VALIDATED
177                 || c == NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
178                 || c == NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
179                 || c == NetworkCapabilities.NET_CAPABILITY_FOREGROUND
180                 || c == NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
181                 || c == NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
182     }
183 
requestNetworkById(int netId)184     public void requestNetworkById(int netId) {
185         if (mNetworkCallbacks.get(netId) != null) {
186             return;
187         }
188 
189         Network network = mNetworks.get(netId);
190         if (network == null) {
191             return;
192         }
193 
194         NetworkRequest request = getRequestForNetwork(network);
195         NetworkByIdCallback cb = new NetworkByIdCallback(network);
196         mNetworkCallbacks.put(netId, cb);
197         mConnectivityManager.requestNetwork(request, cb);
198         showToast("Requesting Network " + netId);
199     }
200 
releaseNetworkById(int netId)201     public void releaseNetworkById(int netId) {
202         NetworkByIdCallback cb = mNetworkCallbacks.get(netId);
203         if (cb != null) {
204             mConnectivityManager.unregisterNetworkCallback(cb);
205             mNetworkCallbacks.remove(netId);
206             showToast("Released Network " + netId);
207         }
208     }
209 
releaseAllNetworks()210     public void releaseAllNetworks() {
211         for (NetworkItem n : mNetworkItems) {
212             releaseNetworkById(n.mNetId);
213         }
214     }
215 
bindToNetwork(int netId)216     public void bindToNetwork(int netId) {
217         Network network = mNetworks.get(netId);
218         if (network == null) {
219             return;
220         }
221 
222         Network def = mConnectivityManager.getBoundNetworkForProcess();
223         if (def != null && def.getNetId() != netId) {
224             clearBoundNetwork();
225         }
226         mConnectivityManager.bindProcessToNetwork(network);
227         showToast("Set process default network " + netId);
228     }
229 
clearBoundNetwork()230     public void clearBoundNetwork() {
231         mConnectivityManager.bindProcessToNetwork(null);
232         showToast("Clear process default network");
233     }
234 
reportNetworkbyId(int netId)235     public void reportNetworkbyId(int netId) {
236         Network network = mNetworks.get(netId);
237         if (network == null) {
238             return;
239         }
240         mConnectivityManager.reportNetworkConnectivity(network, false);
241         showToast("Reporting Network " + netId);
242     }
243 
244     /**
245     * Maps of NET_CAPABILITY_* and TRANSPORT_* to string representations. A network having these
246     * capabilities will have the following strings print on their list entry.
247     */
248     private static final SparseArray<String> sTransportNames = new SparseArray<String>();
249     private static final SparseArray<String> sCapabilityNames = new SparseArray<String>();
250     static {
sTransportNames.put(NetworkCapabilities.TRANSPORT_LOWPAN, "[LOWPAN]")251         sTransportNames.put(NetworkCapabilities.TRANSPORT_LOWPAN, "[LOWPAN]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE, "[WIFI-AWARE]")252         sTransportNames.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE, "[WIFI-AWARE]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_VPN, "[VPN]")253         sTransportNames.put(NetworkCapabilities.TRANSPORT_VPN, "[VPN]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_ETHERNET, "[ETHERNET]")254         sTransportNames.put(NetworkCapabilities.TRANSPORT_ETHERNET, "[ETHERNET]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_BLUETOOTH, "[BLUETOOTH]")255         sTransportNames.put(NetworkCapabilities.TRANSPORT_BLUETOOTH, "[BLUETOOTH]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_WIFI, "[WIFI]")256         sTransportNames.put(NetworkCapabilities.TRANSPORT_WIFI, "[WIFI]");
sTransportNames.put(NetworkCapabilities.TRANSPORT_CELLULAR, "[CELLULAR]")257         sTransportNames.put(NetworkCapabilities.TRANSPORT_CELLULAR, "[CELLULAR]");
258 
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL, "[CAPTIVE PORTAL]")259         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL, "[CAPTIVE PORTAL]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_CBS, "[CBS]")260         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_CBS, "[CBS]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_DUN, "[DUN]")261         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_DUN, "[DUN]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_EIMS, "[EIMS]")262         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_EIMS, "[EIMS]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_FOREGROUND, "[FOREGROUND]")263         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_FOREGROUND, "[FOREGROUND]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_FOTA, "[FOTA]")264         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_FOTA, "[FOTA]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_IA, "[IA]")265         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_IA, "[IA]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_IMS, "[IMS]")266         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_IMS, "[IMS]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_INTERNET, "[INTERNET]")267         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_INTERNET, "[INTERNET]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_MMS, "[MMS]")268         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_MMS, "[MMS]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED, "[NOT CONGESTED]")269         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED, "[NOT CONGESTED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, "[NOT METERED]")270         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, "[NOT METERED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED, "[NOT RESTRICTED]")271         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED, "[NOT RESTRICTED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, "[NOT ROAMING]")272         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, "[NOT ROAMING]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED, "[NOT SUSPENDED]")273         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED, "[NOT SUSPENDED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_VPN, "[NOT VPN]")274         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_NOT_VPN, "[NOT VPN]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_RCS, "[RCS]")275         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_RCS, "[RCS]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_SUPL, "[SUPL]")276         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_SUPL, "[SUPL]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_TRUSTED, "[TRUSTED]")277         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_TRUSTED, "[TRUSTED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_VALIDATED, "[VALIDATED]")278         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_VALIDATED, "[VALIDATED]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P, "[WIFI P2P]")279         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P, "[WIFI P2P]");
sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_XCAP, "[XCAP]")280         sCapabilityNames.put(NetworkCapabilities.NET_CAPABILITY_XCAP, "[XCAP]");
281     }
282 
283     private static final SparseArray<String> sWifiStaStates = new SparseArray<>();
284     static {
sWifiStaStates.put(WifiManager.WIFI_STATE_DISABLING, "STA_DISABLING")285         sWifiStaStates.put(WifiManager.WIFI_STATE_DISABLING, "STA_DISABLING");
sWifiStaStates.put(WifiManager.WIFI_STATE_DISABLED, "STA_DISABLED")286         sWifiStaStates.put(WifiManager.WIFI_STATE_DISABLED, "STA_DISABLED");
sWifiStaStates.put(WifiManager.WIFI_STATE_ENABLING, "STA_ENABLING")287         sWifiStaStates.put(WifiManager.WIFI_STATE_ENABLING, "STA_ENABLING");
sWifiStaStates.put(WifiManager.WIFI_STATE_ENABLED, "STA_ENABLED")288         sWifiStaStates.put(WifiManager.WIFI_STATE_ENABLED, "STA_ENABLED");
sWifiStaStates.put(WifiManager.WIFI_STATE_UNKNOWN, "STA_UNKNOWN")289         sWifiStaStates.put(WifiManager.WIFI_STATE_UNKNOWN, "STA_UNKNOWN");
290     }
291 
292     private static final SparseArray<String> sWifiApStates = new SparseArray<>();
293     static {
sWifiApStates.put(WifiManager.WIFI_AP_STATE_DISABLING, "AP_DISABLING")294         sWifiApStates.put(WifiManager.WIFI_AP_STATE_DISABLING, "AP_DISABLING");
sWifiApStates.put(WifiManager.WIFI_AP_STATE_DISABLED, "AP_DISABLED")295         sWifiApStates.put(WifiManager.WIFI_AP_STATE_DISABLED, "AP_DISABLED");
sWifiApStates.put(WifiManager.WIFI_AP_STATE_ENABLING, "AP_ENABLING")296         sWifiApStates.put(WifiManager.WIFI_AP_STATE_ENABLING, "AP_ENABLING");
sWifiApStates.put(WifiManager.WIFI_AP_STATE_ENABLED, "AP_ENABLED")297         sWifiApStates.put(WifiManager.WIFI_AP_STATE_ENABLED, "AP_ENABLED");
sWifiApStates.put(WifiManager.WIFI_AP_STATE_FAILED, "AP_FAILED")298         sWifiApStates.put(WifiManager.WIFI_AP_STATE_FAILED, "AP_FAILED");
299     }
300 
301     /**
302      * Builds a string out of the possible transports that can be applied to a
303      * NetworkCapabilities object.
304      */
getTransportString(NetworkCapabilities nCaps)305     private String getTransportString(NetworkCapabilities nCaps) {
306         String transports = "";
307         for (int transport : nCaps.getTransportTypes()) {
308             transports += sTransportNames.get(transport, "");
309         }
310         return transports;
311     }
312 
313     /**
314      * Builds a string out of the possible capabilities that can be applied to
315      * a NetworkCapabilities object.
316     */
getCapabilitiesString(NetworkCapabilities nCaps)317     private String getCapabilitiesString(NetworkCapabilities nCaps) {
318         String caps = "";
319         for (int capability : nCaps.getCapabilities()) {
320             caps += sCapabilityNames.get(capability, "");
321         }
322         return caps;
323     }
324 
325     // Gets the string representation of a MAC address from a given NetworkInterface object
getMacAddress(NetworkInterface ni)326     private String getMacAddress(NetworkInterface ni) {
327         if (ni == null) {
328             return "??:??:??:??:??:??";
329         }
330 
331         byte[] mac = null;
332         try {
333             mac = ni.getHardwareAddress();
334         } catch (SocketException exception) {
335             Log.e(TAG, "SocketException -- Failed to get interface MAC address");
336             return "??:??:??:??:??:??";
337         }
338 
339         if (mac == null) {
340             return "??:??:??:??:??:??";
341         }
342 
343         StringBuilder sb = new StringBuilder(18);
344         for (byte b : mac) {
345             if (sb.length() > 0) {
346                 sb.append(':');
347             }
348             sb.append(String.format("%02x", b));
349         }
350         return sb.toString();
351     }
352 
353     /**
354      * Builds a NetworkItem object from a given Network object, aggregating info across Network,
355      * NetworkCapabilities, NetworkInfo, NetworkInterface, and LinkProperties objects and pass it
356      * all as a string for the UI to use
357      */
getNetworkItem(Network n)358     private NetworkItem getNetworkItem(Network n) {
359 
360         // Get default network to assign the button text correctly
361         // NOTE: activeNetwork != ProcessDefault when you set one, active is tracking the default
362         //       request regardless of your process's default
363         // Network defNetwork = mConnectivityManager.getActiveNetwork();
364         Network defNetwork = mConnectivityManager.getBoundNetworkForProcess();
365 
366         // Used to get network state
367         NetworkInfo nInfo = mConnectivityManager.getNetworkInfo(n);
368 
369         // Used to get transport type(s), capabilities
370         NetworkCapabilities nCaps = mConnectivityManager.getNetworkCapabilities(n);
371 
372         // Properties of the actual physical link
373         LinkProperties nLink = mConnectivityManager.getLinkProperties(n);
374 
375         // Object representing the actual interface
376         NetworkInterface nIface = null;
377         try {
378             nIface = NetworkInterface.getByName(nLink.getInterfaceName());
379         } catch (SocketException exception) {
380             Log.e(TAG, "SocketException -- Failed to get interface info");
381         }
382 
383         // Pack NetworkItem with all values
384         NetworkItem ni = new NetworkItem();
385 
386         // Row key
387         ni.mNetId = n.getNetId();
388 
389         // LinkProperties/NetworkInterface
390         ni.mInterfaceName = "Interface: " + nLink.getInterfaceName()
391                             + (nIface != null ? " (" + nIface.getName() + ")" : " ()");
392         ni.mHwAddress = "HwAddress: " + getMacAddress(nIface);
393         ni.mIpAddresses = "IP Addresses: " + nLink.getLinkAddresses().toString();
394         ni.mDnsAddresses = "DNS: " + nLink.getDnsServers().toString();
395         ni.mDomains = "Domains: " + nLink.getDomains();
396         ni.mRoutes = "Routes: " + nLink.getRoutes().toString();
397 
398         // NetworkInfo
399         ni.mType = "Type: " + nInfo.getTypeName() + " (" + nInfo.getSubtypeName() + ")";
400         ni.mState = "State: " + nInfo.getState().name() + "/" + nInfo.getDetailedState().name();
401         ni.mConnected = "Connected: " + (nInfo.isConnected() ? "Connected" : "Disconnected");
402         ni.mAvailable = "Available: " + (nInfo.isAvailable() ? "Yes" : "No");
403         ni.mRoaming = "Roaming: " + (nInfo.isRoaming() ? "Yes" : "No");
404 
405         // NetworkCapabilities
406         ni.mTransports = "Transports: " + getTransportString(nCaps);
407         ni.mCapabilities = "Capabilities: " + getCapabilitiesString(nCaps);
408         ni.mBandwidth = "Bandwidth (Down/Up): " + nCaps.getLinkDownstreamBandwidthKbps()
409                         + " Kbps/" + nCaps.getLinkUpstreamBandwidthKbps() + " Kbps";
410 
411         // Other inferred values
412         ni.mDefault = sameNetworkId(n, defNetwork);
413         ni.mRequested = (mNetworkCallbacks.get(n.getNetId()) != null);
414 
415         return ni;
416     }
417 
418     // Refresh the networks content and prompt the user that we did it
refreshNetworksAndPrompt()419     private void refreshNetworksAndPrompt() {
420         refreshNetworks();
421         showToast("Refreshed Networks (" + mNetworkItems.length + ")");
422     }
423 
424     /**
425      * Gets the current set of networks from the connectivity manager and 1) stores the network
426      * objects 2) builds NetworkItem objects for the view to render and 3) If a network we were
427      * tracking disappears then it kills its callback.
428      */
refreshNetworks()429     private void refreshNetworks() {
430         Log.i(TAG, "refreshNetworks()");
431         Network[] networks = mConnectivityManager.getAllNetworks();
432         mNetworkItems = new NetworkItem[networks.length];
433         mNetworks.clear();
434 
435         // Add each network to the network info set, turning each field to a string
436         for (int i = 0; i < networks.length; i++) {
437             mNetworkItems[i] = getNetworkItem(networks[i]);
438             mNetworks.put(networks[i].getNetId(), networks[i]);
439         }
440 
441         // Check for callbacks that belong to networks that don't exist anymore
442         for (int i = 0; i < mNetworkCallbacks.size(); i++) {
443             int key = mNetworkCallbacks.keyAt(i);
444             if (mNetworks.get(key) == null) {
445                 mNetworkCallbacks.remove(key);
446             }
447         }
448 
449         // Update the view
450         mNetworksAdapter.refreshNetworks(mNetworkItems);
451     }
452 
453     @Override
onCreate(Bundle savedInstanceState)454     public void onCreate(Bundle savedInstanceState) {
455         super.onCreate(savedInstanceState);
456 
457         Context ctx = getContext();
458         mConnectivityManager = ctx.getSystemService(ConnectivityManager.class);
459         mWifiManager = ctx.getSystemService(WifiManager.class);
460         mLocationManager = ctx.getSystemService(LocationManager.class);
461 
462         mConnectivityManager.addDefaultNetworkActiveListener(() -> refreshNetworks());
463     }
464 
465     @Nullable
466     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)467     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
468             @Nullable Bundle savedInstanceState) {
469         View view = inflater.inflate(R.layout.connectivity_fragment, container, false);
470 
471         ViewPager pager = view.findViewById(R.id.connectivity_pager);
472         pager.setAdapter(new SimplePagerAdapter(pager));
473 
474         // Create the ListView of all networks
475         ListView networksView = view.findViewById(R.id.networks);
476         mNetworksAdapter = new NetworkListAdapter(getContext(), mNetworkItems, this);
477         networksView.setAdapter(mNetworksAdapter);
478 
479         // Find all networks ListView refresher and set the refresh callback
480         mNetworkListRefresher = (SwipeRefreshLayout) view.findViewById(R.id.refreshNetworksList);
481         mNetworkListRefresher.setOnRefreshListener(() -> {
482             refreshNetworksAndPrompt();
483             mNetworkListRefresher.setRefreshing(false);
484         });
485 
486         view.findViewById(R.id.startWifi).setOnClickListener(v -> setWifiEnabled(true));
487         view.findViewById(R.id.stopWifi).setOnClickListener(v -> setWifiEnabled(false));
488         view.findViewById(R.id.startTethering).setOnClickListener(v -> startTethering());
489         view.findViewById(R.id.stopTethering).setOnClickListener(v -> stopTethering());
490         view.findViewById(R.id.startLocalOnly).setOnClickListener(v -> startLocalOnly());
491         view.findViewById(R.id.stopLocalOnly).setOnClickListener(v -> stopLocalOnly());
492         mWifiStatusPolled = (TextView) view.findViewById(R.id.wifiStatusPolled);
493         mTetheringStatus = (TextView) view.findViewById(R.id.tetheringStatus);
494         mTetheringStatusPolled = (TextView) view.findViewById(R.id.tetheringStatusPolled);
495         mLocalOnlyStatus = (TextView) view.findViewById(R.id.localOnlyStatus);
496 
497         view.findViewById(R.id.networkEnableWifiIntent).setOnClickListener(v -> enableWifiIntent());
498         view.findViewById(R.id.networkDisableWifiIntent)
499                 .setOnClickListener(v -> disableWifiIntent());
500         view.findViewById(R.id.networkEnableBluetoothIntent)
501                 .setOnClickListener(v -> enableBluetoothIntent());
502         view.findViewById(R.id.networkDisableBluetoothIntent)
503                 .setOnClickListener(v -> disableBluetoothIntent());
504         view.findViewById(R.id.networkDiscoverableBluetoothIntent)
505                 .setOnClickListener(v -> discoverableBluetoothIntent());
506 
507         return view;
508     }
509 
enableWifiIntent()510     private void enableWifiIntent() {
511         Intent enableWifi = new Intent(WifiManager.ACTION_REQUEST_ENABLE);
512         enableWifi.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
513         startActivity(enableWifi);
514     }
515 
disableWifiIntent()516     private void disableWifiIntent() {
517         Intent disableWifi = new Intent(WifiManager.ACTION_REQUEST_DISABLE);
518         disableWifi.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
519         startActivity(disableWifi);
520     }
521 
enableBluetoothIntent()522     private void enableBluetoothIntent() {
523         Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
524         enableBluetooth.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
525         startActivity(enableBluetooth);
526     }
527 
disableBluetoothIntent()528     private void disableBluetoothIntent() {
529         Intent disableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_DISABLE);
530         disableBluetooth.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
531         startActivity(disableBluetooth);
532     }
533 
discoverableBluetoothIntent()534     private void discoverableBluetoothIntent() {
535         Intent discoverableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
536         discoverableBluetooth.putExtra(Intent.EXTRA_PACKAGE_NAME, getContext().getPackageName());
537         startActivity(discoverableBluetooth);
538     }
539 
540     @Override
onResume()541     public void onResume() {
542         super.onResume();
543         refreshNetworks();
544         mWifiUpdater = new Timer();
545         mWifiUpdater.scheduleAtFixedRate(new TimerTask() {
546 
547             @Override
548             public void run() {
549                 updateApState();
550             }
551         }, 0, 500);
552     }
553 
554     @Override
onPause()555     public void onPause() {
556         super.onPause();
557         releaseAllNetworks();
558         mWifiUpdater.cancel();
559         mWifiUpdater = null;
560     }
561 
updateApState()562     private void updateApState() {
563         int apState = mWifiManager.getWifiApState();
564         String apStateTmp = sWifiApStates.get(apState, "?");
565         final String staStateStr = sWifiStaStates.get(mWifiManager.getWifiState(), "?");
566 
567         WifiConfiguration config = mWifiManager.getWifiApConfiguration();
568         if (config != null && config.SSID != null && apState == WifiManager.WIFI_AP_STATE_ENABLED) {
569             apStateTmp += " (" + config.SSID + "/" + config.preSharedKey + ")";
570         }
571 
572         final String apStateStr = apStateTmp;
573         mTetheringStatusPolled.post(() -> {
574             mTetheringStatusPolled.setText(apStateStr);
575             mWifiStatusPolled.setText(staStateStr);
576         });
577     }
578 
setTetheringStatus(String status)579     private void setTetheringStatus(String status) {
580         mTetheringStatus.post(() -> mTetheringStatus.setText(status));
581     }
582 
setLocalOnlyStatus(String status)583     private void setLocalOnlyStatus(String status) {
584         mLocalOnlyStatus.post(() -> mLocalOnlyStatus.setText(status));
585     }
586 
showToast(String text)587     public void showToast(String text) {
588         Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show();
589     }
590 
sameNetworkId(Network net1, Network net2)591     private static boolean sameNetworkId(Network net1, Network net2) {
592         return net1 != null && net2 != null && net1.getNetId() == net2.getNetId();
593     }
594 
setWifiEnabled(boolean enabled)595     private void setWifiEnabled(boolean enabled) {
596         mWifiManager.setWifiEnabled(enabled);
597     }
598 
startTethering()599     private void startTethering() {
600         setTetheringStatus("starting...");
601 
602         ConnectivityManager.OnStartTetheringCallback cb =
603                 new ConnectivityManager.OnStartTetheringCallback() {
604             public void onTetheringStarted() {
605                 setTetheringStatus("started");
606             }
607 
608             public void onTetheringFailed() {
609                 setTetheringStatus("failed");
610             }
611         };
612 
613         mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false, cb);
614     }
615 
stopTethering()616     private void stopTethering() {
617         setTetheringStatus("stopping...");
618         mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
619         setTetheringStatus("stopped");
620     }
621 
622     private WifiManager.LocalOnlyHotspotReservation mLocalOnlyReservation;
623 
startLocalOnly()624     private void startLocalOnly() {
625         setLocalOnlyStatus("starting...");
626 
627         UserHandle user = Process.myUserHandle();
628         if (!mLocationManager.isLocationEnabledForUser(user)) {
629             setLocalOnlyStatus("enabling location...");
630             mLocationManager.setLocationEnabledForUser(true, user);
631             setLocalOnlyStatus("location enabled; starting...");
632         }
633 
634         WifiManager.LocalOnlyHotspotCallback cb = new WifiManager.LocalOnlyHotspotCallback() {
635             public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
636                 mLocalOnlyReservation = reservation;
637                 WifiConfiguration config = reservation.getWifiConfiguration();
638                 setLocalOnlyStatus("started ("
639                         + config.SSID + "/" + config.preSharedKey + ")");
640             };
641 
642             public void onStopped() {
643                 setLocalOnlyStatus("stopped");
644             };
645 
646             public void onFailed(int reason) {
647                 setLocalOnlyStatus("failed " + reason);
648             };
649         };
650 
651         try {
652             mWifiManager.startLocalOnlyHotspot(cb, null);
653         } catch (IllegalStateException ex) {
654             setLocalOnlyStatus(ex.getMessage());
655         }
656     }
657 
stopLocalOnly()658     private void stopLocalOnly() {
659         setLocalOnlyStatus("stopping...");
660 
661         WifiManager.LocalOnlyHotspotReservation reservation = mLocalOnlyReservation;
662         mLocalOnlyReservation = null;
663 
664         if (reservation == null) {
665             setLocalOnlyStatus("no reservation");
666             return;
667         }
668 
669         reservation.close();
670         setLocalOnlyStatus("stopped");
671     }
672 }
673