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