• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.snippet.wifi.aware;
17 
18 import android.content.Context;
19 import android.net.ConnectivityManager;
20 import android.net.Network;
21 import android.net.NetworkCapabilities;
22 import android.net.LinkProperties;
23 import android.net.NetworkRequest;
24 import android.net.TransportInfo;
25 import android.net.wifi.aware.WifiAwareChannelInfo;
26 import android.net.wifi.aware.WifiAwareNetworkInfo;
27 
28 import androidx.annotation.NonNull;
29 import androidx.test.core.app.ApplicationProvider;
30 
31 import com.google.android.mobly.snippet.Snippet;
32 import com.google.android.mobly.snippet.event.EventCache;
33 import com.google.android.mobly.snippet.event.SnippetEvent;
34 import com.google.android.mobly.snippet.rpc.AsyncRpc;
35 import com.google.android.mobly.snippet.rpc.Rpc;
36 import com.google.android.mobly.snippet.util.Log;
37 
38 import org.json.JSONException;
39 
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.OutputStream;
43 import java.net.Inet6Address;
44 import java.net.InetAddress;
45 import java.net.NetworkInterface;
46 import java.net.ServerSocket;
47 import java.net.SocketException;
48 import java.net.Socket;
49 import java.nio.charset.StandardCharsets;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.concurrent.ConcurrentHashMap;
53 import java.util.Enumeration;
54 
55 public class ConnectivityManagerSnippet implements Snippet {
56     private static final String EVENT_KEY_CB_NAME = "callbackName";
57     private static final String EVENT_KEY_NETWORK = "network";
58     private static final String EVENT_KEY_NETWORK_CAP = "networkCapabilities";
59     private static final String EVENT_KEY_NETWORK_INTERFACE ="interfaceName";
60     private static final String EVENT_KEY_TRANSPORT_INFO_CLASS = "transportInfoClassName";
61     private static final String EVENT_KEY_TRANSPORT_INFO_CHANNEL_IN_MHZ = "channelInMhz";
62     private static final int CLOSE_SOCKET_TIMEOUT = 15 * 1000;
63     private static final int ACCEPT_TIMEOUT = 30 * 1000;
64     private static final int SOCKET_SO_TIMEOUT = 30 * 1000;
65     private static final int TRANSPORT_PROTOCOL_TCP = 6;
66 
67     private final Context mContext;
68     private final ConnectivityManager mConnectivityManager;
69 
70     private final ConcurrentHashMap<String, ServerSocket> mServerSockets =
71             new ConcurrentHashMap<>();
72     private final ConcurrentHashMap<String, NetworkCallback> mNetworkCallBacks =
73             new ConcurrentHashMap<>();
74     private final ConcurrentHashMap<String, Socket> mSockets = new ConcurrentHashMap<>();
75     private final ConcurrentHashMap<String, OutputStream> mOutputStreams =
76             new ConcurrentHashMap<>();
77     private final ConcurrentHashMap<String, InputStream> mInputStreams = new ConcurrentHashMap<>();
78     private final ConcurrentHashMap<String, Thread> mSocketThreads = new ConcurrentHashMap<>();
79 
80     /**
81      * Custom exception class for handling specific errors related to the ConnectivityManagerSnippet
82      * operations.
83      */
84     class ConnectivityManagerSnippetException extends Exception {
ConnectivityManagerSnippetException(String msg)85         ConnectivityManagerSnippetException(String msg) {
86             super(msg);
87         }
88     }
89 
ConnectivityManagerSnippet()90     public ConnectivityManagerSnippet() throws ConnectivityManagerSnippetException {
91         mContext = ApplicationProvider.getApplicationContext();
92         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
93         if (mConnectivityManager == null) {
94             throw new ConnectivityManagerSnippetException(
95                     "ConnectivityManager not " + "available.");
96         }
97     }
98 
99     public class NetworkCallback extends ConnectivityManager.NetworkCallback {
100 
101 
102         String mCallBackId;
103         Network mNetWork;
104         NetworkCapabilities mNetworkCapabilities;
105 
106 
NetworkCallback(String callBackId)107         NetworkCallback(String callBackId) {
108             mCallBackId = callBackId;
109         }
110 
111         @Override
onUnavailable()112         public void onUnavailable() {
113             SnippetEvent event = new SnippetEvent(mCallBackId, "NetworkCallback");
114             event.getData().putString(EVENT_KEY_CB_NAME, "onUnavailable");
115             EventCache.getInstance().postEvent(event);
116         }
117 
118         @Override
onCapabilitiesChanged(@onNull Network network, @NonNull NetworkCapabilities networkCapabilities)119         public void onCapabilitiesChanged(@NonNull Network network,
120                 @NonNull NetworkCapabilities networkCapabilities) {
121             SnippetEvent event = new SnippetEvent(mCallBackId, "NetworkCallback");
122             event.getData().putString(EVENT_KEY_CB_NAME, "onCapabilitiesChanged");
123             event.getData().putParcelable(EVENT_KEY_NETWORK, network);
124             event.getData().putParcelable(EVENT_KEY_NETWORK_CAP, networkCapabilities);
125             mNetWork = network;
126             mNetworkCapabilities = networkCapabilities;
127             TransportInfo transportInfo = networkCapabilities.getTransportInfo();
128             String transportInfoClassName = "";
129             if (transportInfo != null) {
130                 transportInfoClassName = transportInfo.getClass().getName();
131                 event.getData().putString(EVENT_KEY_TRANSPORT_INFO_CLASS, transportInfoClassName);
132             }
133             if (networkCapabilities.getTransportInfo() instanceof WifiAwareNetworkInfo) {
134                 WifiAwareNetworkInfo
135                         newWorkInfo =
136                         (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
137                 List<WifiAwareChannelInfo> channelInfoList = newWorkInfo.getChannelInfoList();
138                 ArrayList<Integer> channelFrequencies = new ArrayList<>();
139                 if (!channelInfoList.isEmpty()) {
140                     for (WifiAwareChannelInfo info : channelInfoList) {
141                         channelFrequencies.add(info.getChannelFrequencyMhz());
142                     }
143                 }
144                 event.getData().putIntegerArrayList(
145                     EVENT_KEY_TRANSPORT_INFO_CHANNEL_IN_MHZ, channelFrequencies
146                 );
147                 String ipv6 = newWorkInfo.getPeerIpv6Addr().toString();
148                 if (ipv6.charAt(0) == '/') {
149                     ipv6 = ipv6.substring(1);
150                 }
151                 event.getData().putString("aware_ipv6", ipv6);
152                 int port = newWorkInfo.getPort();
153                 if (port != 0) {
154                     event.getData().putInt("port", port);
155                 }
156                 if (newWorkInfo.getTransportProtocol() != -1) {
157                     event.getData().putInt("aware_transport_protocol",
158                     newWorkInfo.getTransportProtocol());
159                 }
160             }
161             EventCache.getInstance().postEvent(event);
162         }
163 
164         @Override
onLinkPropertiesChanged(Network network, LinkProperties linkProperties)165         public void onLinkPropertiesChanged(Network network,
166                LinkProperties linkProperties) {
167             Log.v("NetworkCallback onLinkPropertiesChanged");
168             SnippetEvent event = new SnippetEvent(mCallBackId, "NetworkCallback");
169             event.getData().putString(EVENT_KEY_CB_NAME, "onLinkPropertiesChanged");
170             event.getData().putParcelable(EVENT_KEY_NETWORK, network);
171             event.getData().putString(EVENT_KEY_NETWORK_INTERFACE,
172                    linkProperties.getInterfaceName());
173             EventCache.getInstance().postEvent(event);
174         }
175 
176         @Override
onLost(@onNull Network network)177         public void onLost(@NonNull Network network) {
178             Log.v("Network onLost");
179             SnippetEvent event = new SnippetEvent(mCallBackId, "CallbackLost");
180             event.getData().putString(EVENT_KEY_CB_NAME, "Lost");
181             event.getData().putParcelable(EVENT_KEY_NETWORK, network);
182             EventCache.getInstance().postEvent(event);
183         }
184     }
185 
getInetAddrsForInterface(String ifaceName)186     private Enumeration<InetAddress> getInetAddrsForInterface(String ifaceName) {
187         NetworkInterface iface = null;
188         try {
189             iface = NetworkInterface.getByName(ifaceName);
190         } catch (SocketException e) {
191             return null;
192         }
193 
194         if (iface == null)
195             return null;
196         return iface.getInetAddresses();
197     }
198     /**
199      * Returns the link local IPv6 address of the interface.
200      *
201      * @param ifaceName network interface name.
202      */
203     @Rpc(description = "Returns the link local IPv6 address of the interface.")
connectivityGetLinkLocalIpv6Address(String ifaceName)204     public String connectivityGetLinkLocalIpv6Address(String ifaceName) {
205         Inet6Address inet6Address = null;
206         Enumeration<InetAddress> inetAddresses = getInetAddrsForInterface(ifaceName);
207         if (inetAddresses == null) {
208             return null;
209         }
210 
211         while (inetAddresses.hasMoreElements()) {
212             InetAddress addr = inetAddresses.nextElement();
213             if (addr instanceof Inet6Address) {
214                 if (((Inet6Address) addr).isLinkLocalAddress()) {
215                     inet6Address = (Inet6Address) addr;
216                     break;
217                 }
218             }
219         }
220 
221         if (inet6Address == null) {
222             return null;
223         }
224 
225         return inet6Address.getHostAddress();
226     }
227     /**
228      * Requests a network with the specified network request and sets a callback for network
229      * events.
230      *
231      * @param callBackId              A unique identifier assigned automatically by Mobly. This is
232      *                                used as the request ID for further operations and event
233      *                                handling.
234      * @param request                 The NetworkRequest object that specifies the desired network
235      *                                characteristics.
236      * @param requestNetWorkId        A unique ID to support managing multiple network sessions.
237      * @param requestNetworkTimeoutMs The timeout period (in milliseconds) after which the network
238      *                                request will expire if no suitable network is found.
239      */
240     @AsyncRpc(description = "Request a network.")
connectivityRequestNetwork(String callBackId, String requestNetWorkId, NetworkRequest request, int requestNetworkTimeoutMs)241     public void connectivityRequestNetwork(String callBackId, String requestNetWorkId,
242             NetworkRequest request, int requestNetworkTimeoutMs) {
243         Log.v("Requesting network with request: " + request.toString());
244         NetworkCallback callback = new NetworkCallback(callBackId);
245         mNetworkCallBacks.put(requestNetWorkId, callback);
246         mConnectivityManager.requestNetwork(request, callback, requestNetworkTimeoutMs);
247     }
248 
249     /**
250      * Unregisters the registered network callback and possibly releases requested networks.
251      *
252      * @param requestId Id of the network request.
253      */
254     @Rpc(description = "Unregister a network request")
connectivityUnregisterNetwork(String requestId)255     public void connectivityUnregisterNetwork(String requestId) {
256         NetworkCallback callback = mNetworkCallBacks.get(requestId);
257         if (callback == null) {
258             return;
259         }
260         if (mConnectivityManager == null) {
261             return;
262         }
263         mConnectivityManager.unregisterNetworkCallback(callback);
264     }
265 
266     /**
267      * Starts a server socket on a random available port and waits for incoming connections. A
268      * separate thread is started to handle the socket accept operation asynchronously. The accepted
269      * socket is stored and used for further communication (read/write).
270      *
271      * @param callbackId A unique identifier assigned automatically by Mobly to track the event and
272      *                   response.
273      * @return The port number assigned by the local system.
274      */
275     @AsyncRpc(description = "Start a server socket to accept incoming connections.")
connectivityServerSocketAccept(String callbackId)276     public int connectivityServerSocketAccept(String callbackId)
277             throws ConnectivityManagerSnippetException, IOException {
278         if (mServerSockets.containsKey(callbackId) && mServerSockets.get(callbackId) != null) {
279             throw new ConnectivityManagerSnippetException("Server socket is already created.");
280         }
281         ServerSocket serverSocket = new ServerSocket(0);
282         int localPort = serverSocket.getLocalPort();
283         mServerSockets.put(callbackId, serverSocket);
284         // https://developer.callbackId.com/reference/java/net/ServerSocket#setSoTimeout(int)
285         // A call to accept() for this ServerSocket will block for only this amount of time.
286         serverSocket.setSoTimeout(ACCEPT_TIMEOUT);
287         if (mSocketThreads.get(callbackId) != null) {
288             throw new ConnectivityManagerSnippetException(
289                     "Server socket thread is already running.");
290         }
291         Thread socketThread = new Thread(() -> {
292             try {
293                 Socket tempSocket = mServerSockets.get(callbackId).accept();
294                 mSockets.put(callbackId, tempSocket);
295                 mInputStreams.put(callbackId, tempSocket.getInputStream());
296                 mOutputStreams.put(callbackId, tempSocket.getOutputStream());
297                 SnippetEvent event = new SnippetEvent(callbackId, "ServerSocketAccept");
298                 event.getData().putBoolean("isAccept", true);
299                 EventCache.getInstance().postEvent(event);
300             } catch (IOException e) {
301                 Log.e("Socket accept error", e);
302                 SnippetEvent event = new SnippetEvent(callbackId, "ServerSocketAccept");
303                 event.getData().putBoolean("isAccept", false);
304                 event.getData().putString("error", e.getMessage());
305                 EventCache.getInstance().postEvent(event);
306             }
307         });
308         mSocketThreads.put(callbackId, socketThread);
309         socketThread.start();
310         return localPort;
311     }
312 
313     /**
314      * Check if the server socket thread is alive.
315      *
316      * @param sessionId To support multiple network requests happening simultaneously
317      * @return True if the server socket thread is alive.
318      */
connectivityIsSocketThreadAlive(String sessionId)319     public boolean connectivityIsSocketThreadAlive(String sessionId) {
320         Thread thread = mSocketThreads.get(sessionId);
321         if (thread != null) {
322             return thread.isAlive();
323         } else {
324             return false;
325         }
326     }
327 
328     /**
329      * Stops the server socket thread if it's running.
330      *
331      * @param sessionId To support multiple network requests happening simultaneously
332      */
333     @Rpc(description = "Stop the server socket thread if it's running.")
connectivityStopAcceptThread(String sessionId)334     public void connectivityStopAcceptThread(String sessionId) throws IOException {
335         if (connectivityIsSocketThreadAlive(sessionId)) {
336             Thread thread = mSocketThreads.get(sessionId);
337 
338             try {
339                 connectivityCloseServerSocket(sessionId);
340                 thread.join(CLOSE_SOCKET_TIMEOUT);  // Wait for the thread to terminate
341                 if (thread.isAlive()) {
342                     throw new RuntimeException("Server socket thread did not terminate in time");
343                 }
344             } catch (InterruptedException e) {
345                 throw new RuntimeException("Error stopping server socket thread", e);
346             } finally {
347                 connectivityCloseSocket(sessionId);
348                 mSocketThreads.remove(sessionId);
349             }
350         } else {
351             connectivityCloseSocket(sessionId);
352             mSocketThreads.remove(sessionId);
353         }
354     }
355 
356     /**
357      * Reads from a socket.
358      *
359      * @param sessionId To support multiple network requests happening simultaneously
360      * @param len       The number of bytes to read.
361      */
362     @Rpc(description = "Reads from a socket.")
connectivityReadSocket(String sessionId, int len)363     public String connectivityReadSocket(String sessionId, int len)
364             throws ConnectivityManagerSnippetException, JSONException, IOException {
365         checkInputStream(sessionId);
366         // Read the specified number of bytes from the input stream
367         byte[] buffer = new byte[len];
368         InputStream inputStream = mInputStreams.get(sessionId);
369         int bytesReadLength = inputStream.read(buffer, 0, len); // Read up to len bytes
370         if (bytesReadLength == -1) { // End of stream reached unexpectedly
371             throw new ConnectivityManagerSnippetException(
372                     "End of stream reached before reading expected bytes.");
373         }
374         // Convert the bytes read to a String
375         String receiveStrMsg = new String(buffer, 0, bytesReadLength, StandardCharsets.UTF_8);
376         return receiveStrMsg;
377     }
378 
379     /**
380      * Writes to a socket.
381      *
382      * @param sessionId To support multiple network requests happening simultaneously
383      * @param message   The message to send.
384      * @throws ConnectivityManagerSnippetException
385      */
386     @Rpc(description = "Writes to a socket.")
connectivityWriteSocket(String sessionId, String message)387     public Boolean connectivityWriteSocket(String sessionId, String message)
388             throws ConnectivityManagerSnippetException, IOException {
389         checkOutputStream(sessionId);
390         byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
391         // Write the message to the output stream
392         OutputStream outputStream = mOutputStreams.get(sessionId);
393         outputStream.write(bytes, 0, bytes.length);
394         outputStream.flush();
395         return true;
396 
397 
398     }
399 
400     /**
401      * Closes the socket.
402      *
403      * @param sessionId To support multiple network requests happening simultaneously
404      * @throws ConnectivityManagerSnippetException
405      */
connectivityCloseSocket(String sessionId)406     public void connectivityCloseSocket(String sessionId) throws IOException {
407         Socket socket = mSockets.get(sessionId);
408         if (socket != null && !socket.isClosed()) {
409             socket.close();
410         }
411         mSockets.remove(sessionId);
412 
413     }
414 
415     /**
416      * Closes the server socket.
417      *
418      * @param sessionId To support multiple network requests happening simultaneously
419      * @throws IOException
420      */
connectivityCloseServerSocket(String sessionId)421     public void connectivityCloseServerSocket(String sessionId) throws IOException {
422         ServerSocket serverSocket = mServerSockets.get(sessionId);
423         if (serverSocket != null && !serverSocket.isClosed()) {
424             serverSocket.close();
425         }
426         mServerSockets.remove(sessionId);
427     }
428 
429     /**
430      * Closes the outputStream.
431      *
432      * @throws ConnectivityManagerSnippetException
433      */
434     @Rpc(description = "Close the outputStream.")
connectivityCloseWrite(String sessionId)435     public void connectivityCloseWrite(String sessionId)
436             throws IOException, ConnectivityManagerSnippetException {
437         OutputStream outputStream = mOutputStreams.get(sessionId);
438         if (outputStream != null) {
439             outputStream.close();
440         }
441         mOutputStreams.remove(sessionId);
442 
443 
444     }
445 
446     /**
447      * Closes the inputStream.
448      *
449      * @throws ConnectivityManagerSnippetException
450      */
451     @Rpc(description = "Close the inputStream.")
connectivityCloseRead(String sessionId)452     public void connectivityCloseRead(String sessionId)
453             throws IOException, ConnectivityManagerSnippetException {
454         InputStream inputStream = mInputStreams.get(sessionId);
455         if (inputStream != null) {
456             inputStream.close();
457         }
458         mInputStreams.remove(sessionId);
459     }
460 
checkOutputStream(String sessionId)461     private void checkOutputStream(String sessionId) throws ConnectivityManagerSnippetException {
462         OutputStream outputStream = mOutputStreams.get(sessionId);
463         if (outputStream == null) {
464             throw new ConnectivityManagerSnippetException("Output stream is not created.Please "
465                     + "call connectivityCreateSocketOverWiFiAware() or "
466                     + "connectivityServerSocketAccept() first.");
467         }
468     }
469 
checkInputStream(String sessionId)470     private void checkInputStream(String sessionId) throws ConnectivityManagerSnippetException {
471         InputStream inputStream = mInputStreams.get(sessionId);
472         if (inputStream == null) {
473             throw new ConnectivityManagerSnippetException("Input stream is not created.Please "
474                     + "call connectivityCreateSocketOverWiFiAware() or "
475                     + "connectivityServerSocketAccept() first.");
476         }
477     }
478 
479     /**
480      * Creates a socket using Wi-Fi Aware's peer-to-peer connection capabilities. Only TCP transport
481      * protocol is supported. The method uses the session ID to track and manage the socket.
482      *
483      * @param sessionId     A unique ID to manage multiple network requests simultaneously.
484      * @param peerLocalPort The port number of the peer device.
485      */
486     @Rpc(description = "Create to a socket.")
connectivityCreateSocketOverWiFiAware(String sessionId, int peerLocalPort)487     public void connectivityCreateSocketOverWiFiAware(String sessionId, int peerLocalPort)
488             throws ConnectivityManagerSnippetException, IOException {
489         NetworkCallback netWorkCallBackBySessionId = getNetWorkCallbackBySessionId(sessionId);
490         NetworkCapabilities networkCapabilities = netWorkCallBackBySessionId.mNetworkCapabilities;
491         Network netWork = netWorkCallBackBySessionId.mNetWork;
492         checkNetworkCapabilities(networkCapabilities);
493         checkNetwork(netWork);
494         Socket socket = mSockets.get(sessionId);
495         if (socket != null) {
496             throw new ConnectivityManagerSnippetException("Socket is already created"
497                     + ".Please call connectivityCloseSocket(String sessionId) or "
498                     + "connectivityStopAcceptThread" + "(String sessionId) " + "to release first.");
499         }
500 
501         checkNetworkCapabilities(networkCapabilities);
502         WifiAwareNetworkInfo peerAwareInfo =
503                 (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
504         if (peerAwareInfo == null) {
505             throw new ConnectivityManagerSnippetException("PeerAwareInfo is null.");
506         }
507         int peerPort = peerAwareInfo.getPort();
508         Inet6Address peerIpv6Addr = peerAwareInfo.getPeerIpv6Addr();
509         if (peerPort == 0) {
510             peerPort = peerLocalPort;
511             if (peerPort == 0) {
512                 throw new ConnectivityManagerSnippetException("Invalid port number.");
513             }
514         } else {
515 
516             int transportProtocol = peerAwareInfo.getTransportProtocol();
517             if (transportProtocol != TRANSPORT_PROTOCOL_TCP) {
518                 throw new ConnectivityManagerSnippetException(
519                         "Only support TCP transport protocol.");
520             }
521         }
522 
523 
524         Socket createSocket = netWork.getSocketFactory().createSocket(peerIpv6Addr, peerPort);
525         createSocket.setSoTimeout(SOCKET_SO_TIMEOUT);
526         mSockets.put(sessionId, createSocket);
527         mInputStreams.put(sessionId, createSocket.getInputStream());
528         mOutputStreams.put(sessionId, createSocket.getOutputStream());
529     }
530 
531 
getNetWorkCallbackBySessionId(String sessionId)532     private NetworkCallback getNetWorkCallbackBySessionId(String sessionId)
533             throws ConnectivityManagerSnippetException {
534         NetworkCallback callback = mNetworkCallBacks.get(sessionId);
535         if (callback == null) {
536             throw new ConnectivityManagerSnippetException("Network callback is not created.Please "
537                     + "call connectivityRequestNetwork() first.");
538 
539         }
540         return callback;
541     }
542 
543     /**
544      * Check if the network capabilities is created.
545      *
546      * @throws ConnectivityManagerSnippetException
547      */
checkNetworkCapabilities(NetworkCapabilities networkCapabilities)548     private void checkNetworkCapabilities(NetworkCapabilities networkCapabilities)
549             throws ConnectivityManagerSnippetException {
550         if (networkCapabilities == null) {
551             throw new ConnectivityManagerSnippetException("Network capabilities is not created.");
552         }
553     }
554 
555     /**
556      * Check if the network is created.
557      *
558      * @throws ConnectivityManagerSnippetException
559      */
checkNetwork(Network network)560     private void checkNetwork(Network network) throws ConnectivityManagerSnippetException {
561         if (network == null) {
562             throw new ConnectivityManagerSnippetException("Network is not created.");
563         }
564     }
565 
566     /**
567      * Check if the server socket is created.
568      *
569      * @throws ConnectivityManagerSnippetException
570      */
checkServerSocket(String sessionId)571     private void checkServerSocket(String sessionId) throws ConnectivityManagerSnippetException {
572         if (mServerSockets.get(sessionId) == null) {
573             throw new ConnectivityManagerSnippetException("Server socket is not created"
574                     + ".Please call connectivityInitServerSocket() first.");
575         }
576     }
577 
578     /**
579      * Close all sockets.
580      *
581      * @param sessionId To support multiple network requests happening simultaneously
582      * @throws IOException
583      */
584     @Rpc(description = "Close all sockets.")
connectivityCloseAllSocket(String sessionId)585     public void connectivityCloseAllSocket(String sessionId)
586             throws IOException, ConnectivityManagerSnippetException {
587         connectivityStopAcceptThread(sessionId);
588         connectivityCloseServerSocket(sessionId);
589         connectivityCloseRead(sessionId);
590         connectivityCloseWrite(sessionId);
591     }
592 
593     @Override
shutdown()594     public void shutdown() throws Exception {
595         try {
596             for (NetworkCallback callback : mNetworkCallBacks.values()) {
597                 mConnectivityManager.unregisterNetworkCallback(callback);
598             }
599             mNetworkCallBacks.clear();
600 
601         } catch (Exception e) {
602             Log.e("Error unregistering network callback", e);
603         }
604         try {
605             connectivityReleaseAllSockets();
606         } catch (Exception e) {
607             Log.e("Error closing sockets", e);
608         }
609         Snippet.super.shutdown();
610     }
611 
612     /**
613      * Close all sockets.
614      *
615      * @throws IOException
616      */
617     @Rpc(description = "Close all sockets.")
connectivityReleaseAllSockets()618     public void connectivityReleaseAllSockets() {
619         for (Socket socket : mSockets.values()) {
620             try {
621                 if (socket != null && !socket.isClosed()) {
622                     socket.close();
623                 }
624             } catch (IOException e) {
625                 Log.e("Error closing socket", e);
626             }
627         }
628         mSockets.clear();
629         for (ServerSocket serverSocket : mServerSockets.values()) {
630             try {
631                 if (serverSocket != null && !serverSocket.isClosed()) {
632                     serverSocket.close();
633                 }
634             } catch (IOException e) {
635                 Log.e("Error closing server socket", e);
636             }
637         }
638         mServerSockets.clear();
639         for (OutputStream outputStream : mOutputStreams.values()) {
640             try {
641                 if (outputStream != null) {
642                     outputStream.close();
643                 }
644             } catch (IOException e) {
645                 Log.e("Error closing output stream", e);
646             }
647         }
648         mOutputStreams.clear();
649         for (InputStream inputStream : mInputStreams.values()) {
650             try {
651                 if (inputStream != null) {
652                     inputStream.close();
653                 }
654             } catch (IOException e) {
655                 Log.e("Error closing input stream", e);
656             }
657         }
658         mInputStreams.clear();
659     }
660 }
661