• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 org.chromium.latency.walt;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.HandlerThread;
22 import android.util.Log;
23 
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.net.InetAddress;
28 import java.net.InetSocketAddress;
29 import java.net.Socket;
30 import java.net.SocketTimeoutException;
31 
32 
33 public class WaltTcpConnection implements WaltConnection {
34 
35     // Use a "reverse" port over adb. The server is running on the host to which we're attached.
36     private static final String SERVER_IP = "127.0.0.1";
37     private static final int SERVER_PORT = 50007;
38     private static final int TCP_READ_TIMEOUT_MS = 200;
39 
40     private final SimpleLogger logger;
41     private HandlerThread networkThread;
42     private Handler networkHandler;
43     private final Object readLock = new Object();
44     private boolean messageReceived = false;
45     private Utils.ListenerState connectionState = Utils.ListenerState.STOPPED;
46     private int lastRetVal;
47     static final int BUFF_SIZE = 1024 * 4;
48     private byte[] buffer = new byte[BUFF_SIZE];
49 
50     private final Handler mainHandler = new Handler();
51     private RemoteClockInfo remoteClock = new RemoteClockInfo();
52 
53     private Socket socket;
54     private OutputStream outputStream = null;
55     private InputStream inputStream = null;
56 
57     private WaltConnection.ConnectionStateListener connectionStateListener;
58 
59     // Singleton stuff
60     private static WaltTcpConnection instance;
61     private static final Object LOCK = new Object();
62 
getInstance(Context context)63     public static WaltTcpConnection getInstance(Context context) {
64         synchronized (LOCK) {
65             if (instance == null) {
66                 instance = new WaltTcpConnection(context.getApplicationContext());
67             }
68             return instance;
69         }
70     }
71 
WaltTcpConnection(Context context)72     private WaltTcpConnection(Context context) {
73         logger = SimpleLogger.getInstance(context);
74     }
75 
connect()76     public void connect() {
77         // If the singleton is already connected, do not kill the connection.
78         if (isConnected()) {
79             return;
80         }
81         connectionState = Utils.ListenerState.STARTING;
82         networkThread = new HandlerThread("NetworkThread");
83         networkThread.start();
84         networkHandler = new Handler(networkThread.getLooper());
85         logger.log("Started network thread for TCP bridge");
86         networkHandler.post(new Runnable() {
87             @Override
88             public void run() {
89                 try {
90                     InetAddress serverAddr = InetAddress.getByName(SERVER_IP);
91                     socket = new Socket(serverAddr, SERVER_PORT);
92                     socket.setKeepAlive(true);
93                     socket.setSoTimeout(TCP_READ_TIMEOUT_MS);
94                     outputStream = socket.getOutputStream();
95                     inputStream = socket.getInputStream();
96                     logger.log("TCP connection established");
97                     connectionState = Utils.ListenerState.RUNNING;
98                 } catch (Exception e) {
99                     e.printStackTrace();
100                     logger.log("Can't connect to TCP bridge: " + e.getMessage());
101                     connectionState = Utils.ListenerState.STOPPED;
102                     return;
103                 }
104 
105                 // Run the onConnect callback, but on main thread.
106                 mainHandler.post(new Runnable() {
107                     @Override
108                     public void run() {
109                         WaltTcpConnection.this.onConnect();
110                     }
111                 });
112             }
113         });
114 
115     }
116 
onConnect()117     public void onConnect() {
118         if (connectionStateListener != null) {
119             connectionStateListener.onConnect();
120         }
121     }
122 
isConnected()123     public synchronized boolean isConnected() {
124         return connectionState == Utils.ListenerState.RUNNING;
125     }
126 
sendByte(final char c)127     public void sendByte(final char c) throws IOException {
128         // All network accesses must occur on a separate thread.
129         networkHandler.post(new Runnable() {
130             @Override
131             public void run() {
132                 try {
133                     outputStream.write(Utils.char2byte(c));
134                 } catch (IOException e) {
135                     e.printStackTrace();
136                 }
137             }
138         });
139     }
140 
sendString(final String s)141     public void sendString(final String s) throws IOException {
142         // All network accesses must occur on a separate thread.
143         networkHandler.post(new Runnable() {
144             @Override
145             public void run() {
146                 try {
147                     outputStream.write(s.getBytes("UTF-8"));
148                 } catch (IOException e) {
149                     e.printStackTrace();
150                 }
151             }
152         });
153     }
154 
blockingRead(byte[] buff)155     public synchronized int blockingRead(byte[] buff) {
156 
157         messageReceived = false;
158 
159         // All network accesses must occur on a separate thread.
160         networkHandler.post(new Runnable() {
161             @Override
162             public void run() {
163                 lastRetVal = -1;
164                 try {
165                     synchronized (readLock) {
166                         lastRetVal = inputStream.read(buffer);
167                         messageReceived = true;
168                         readLock.notifyAll();
169                     }
170                 } catch (SocketTimeoutException e) {
171                     messageReceived = true;
172                     lastRetVal = -2;
173                 }
174                 catch (Exception e) {
175                     e.printStackTrace();
176                     messageReceived = true;
177                     lastRetVal = -1;
178                     // TODO: better messaging / error handling here
179                 }
180             }
181         });
182 
183         // TODO: make sure length is ok
184         // This blocks on readLock which is taken by the blocking read operation
185         try {
186             synchronized (readLock) {
187                 while (!messageReceived) readLock.wait(TCP_READ_TIMEOUT_MS);
188             }
189         } catch (InterruptedException e) {
190             return -1;
191         }
192 
193         if (lastRetVal > 0) {
194             System.arraycopy(buffer, 0, buff, 0, lastRetVal);
195         }
196 
197         return lastRetVal;
198     }
199 
updateClock(String cmd)200     private synchronized void updateClock(String cmd) throws IOException {
201         sendString(cmd);
202         int retval = blockingRead(buffer);
203         if (retval <= 0) {
204             throw new IOException("WaltTcpConnection, can't sync clocks");
205         }
206         String s = new String(buffer, 0, retval);
207         String[] parts = s.trim().split("\\s+");
208         // TODO: make sure reply starts with "clock"
209         // The bridge sends the time difference between when it sent the reply and when it zeroed
210         // the WALT's clock. We assume here that the reply transit time is negligible.
211         remoteClock.baseTime = RemoteClockInfo.microTime() - Long.parseLong(parts[1]);
212         remoteClock.minLag = Integer.parseInt(parts[2]);
213         remoteClock.maxLag = Integer.parseInt(parts[3]);
214     }
215 
syncClock()216     public RemoteClockInfo syncClock() throws IOException {
217         updateClock("bridge sync");
218         logger.log("Synced clocks via TCP bridge:\n" + remoteClock);
219         return remoteClock;
220     }
221 
updateLag()222     public void updateLag() {
223         try {
224             updateClock("bridge update");
225         } catch (IOException e) {
226             logger.log("Failed to update clock lag: " + e.getMessage());
227         }
228     }
229 
setConnectionStateListener(ConnectionStateListener connectionStateListener)230     public void setConnectionStateListener(ConnectionStateListener connectionStateListener) {
231         this.connectionStateListener = connectionStateListener;
232     }
233 
234     // A way to test if there is a TCP bridge to decide whether to use it.
235     // Some thread dancing to get around the Android strict policy for no network on main thread.
probe()236     public static boolean probe() {
237         ProbeThread probeThread = new ProbeThread();
238         probeThread.start();
239         try {
240             probeThread.join();
241         } catch (Exception e) {
242             e.printStackTrace();
243         }
244         return probeThread.isReachable;
245     }
246 
247     private static class ProbeThread extends Thread {
248         public boolean isReachable = false;
249         private final String TAG = "ProbeThread";
250 
251         @Override
run()252         public void run() {
253             Socket socket = new Socket();
254             try {
255                 InetSocketAddress remoteAddr = new InetSocketAddress(SERVER_IP, SERVER_PORT);
256                 socket.connect(remoteAddr, 50 /* timeout in milliseconds */);
257                 isReachable = true;
258                 socket.close();
259             } catch (Exception e) {
260                 Log.i(TAG, "Probing TCP connection failed: " + e.getMessage());
261             }
262         }
263     }
264 }
265