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