From 2d3f8a1b548faefee8484ad5dcf5cfc698bcd443 Mon Sep 17 00:00:00 2001 From: Andrew Lehmer Date: Tue, 9 May 2017 10:33:40 -0700 Subject: walt: modify for use with Salad Fingers Bug: 37779957 Test: end-to-end tests with uncommited PTS changes for salad_fingers Change-Id: I4d0a59b04fb92d45899400ca36eb93fb8249c086 --- .../chromium/latency/walt/DragLatencyFragment.java | 13 ++- .../org/chromium/latency/walt/MainActivity.java | 93 ++++++++++++++++++++-- .../org/chromium/latency/walt/RemoteClockInfo.java | 68 ++-------------- .../latency/walt/RobotAutomationListener.java | 27 +++++++ .../latency/walt/ScreenResponseFragment.java | 22 +++-- .../chromium/latency/walt/TapLatencyFragment.java | 17 +++- .../java/org/chromium/latency/walt/WaltDevice.java | 37 ++++++--- .../chromium/latency/walt/WaltTcpConnection.java | 46 ++++++++--- 8 files changed, 227 insertions(+), 96 deletions(-) create mode 100644 android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java (limited to 'android/WALT/app/src/main') diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java index 109fcf8..af03e36 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/DragLatencyFragment.java @@ -39,7 +39,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Locale; -public class DragLatencyFragment extends Fragment implements View.OnClickListener { +public class DragLatencyFragment extends Fragment + implements View.OnClickListener, RobotAutomationListener { private SimpleLogger logger; private WaltDevice waltDevice; @@ -332,6 +333,16 @@ public class DragLatencyFragment extends Fragment implements View.OnClickListene } } + public void onRobotAutomationEvent(String event) { + if (event.equals(RobotAutomationListener.RESTART_EVENT)) { + onClick(restartButton); + } else if (event.equals(RobotAutomationListener.START_EVENT)) { + onClick(startButton); + } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) { + onClick(finishButton); + } + } + private WaltDevice.TriggerHandler triggerHandler = new WaltDevice.TriggerHandler() { @Override public void onReceive(WaltDevice.TriggerMessage tmsg) { diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java index 7efee00..ac1df47 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/MainActivity.java @@ -65,6 +65,10 @@ public class MainActivity extends AppCompatActivity { private static final String TAG = "WALT"; private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG = 2; private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SYSTRACE = 3; + private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG = 4; + private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG = 5; + + private static final String LOG_FILENAME = "qstep_log.txt"; private Toolbar toolbar; LocalBroadcastManager broadcastManager; @@ -74,6 +78,8 @@ public class MainActivity extends AppCompatActivity { public Handler handler = new Handler(); + private Fragment mRobotAutomationFragment; + /** * A method to display exceptions on screen. This is very useful because our USB port is taken @@ -132,6 +138,41 @@ public class MainActivity extends AppCompatActivity { autoRunFragment.setArguments(intent.getExtras()); switchScreen(autoRunFragment, "Automated Test"); } + + // Handle robot automation originating from adb shell am + if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) { + Log.e(TAG, "Received Intent: " + intent.toString()); + String test = intent.getStringExtra("StartTest"); + if (test != null) { + Log.e(TAG, "Extras \"StartTest\" = " + test); + if ("TapLatencyTest".equals(test)) { + mRobotAutomationFragment = new TapLatencyFragment(); + switchScreen(mRobotAutomationFragment, "Tap Latency"); + } else if ("ScreenResponseTest".equals(test)) { + mRobotAutomationFragment = new ScreenResponseFragment(); + switchScreen(mRobotAutomationFragment, "Screen Response"); + } else if ("DragLatencyTest".equals(test)) { + mRobotAutomationFragment = new DragLatencyFragment(); + switchScreen(mRobotAutomationFragment, "Drag Latency"); + } + } + + String robotEvent = intent.getStringExtra("RobotAutomationEvent"); + if (robotEvent != null && mRobotAutomationFragment != null) { + Log.e(TAG, "Received robot automation event=\"" + robotEvent + "\", Fragment = " + + mRobotAutomationFragment); + // Writing and clearing the log is not fragment-specific, so handle them here. + if (robotEvent.equals(RobotAutomationListener.WRITE_LOG_EVENT)) { + attemptSaveLog(); + } else if (robotEvent.equals(RobotAutomationListener.CLEAR_LOG_EVENT)) { + attemptClearLog(); + } else { + // All other robot automation events are forwarded to the current fragment. + ((RobotAutomationListener) mRobotAutomationFragment) + .onRobotAutomationEvent(robotEvent); + } + } + } } @Override @@ -389,6 +430,30 @@ public class MainActivity extends AppCompatActivity { } } + private void attemptSaveLog() { + int currentPermission = ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (currentPermission == PackageManager.PERMISSION_GRANTED) { + saveLogToFile(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG); + } + } + + private void attemptClearLog() { + int currentPermission = ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (currentPermission == PackageManager.PERMISSION_GRANTED) { + clearLogFile(); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG); + } + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -401,6 +466,12 @@ public class MainActivity extends AppCompatActivity { case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_SHARE_LOG: attemptSaveAndShareLog(); break; + case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_WRITE_LOG: + attemptSaveLog(); + break; + case PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE_CLEAR_LOG: + attemptClearLog(); + break; } } @@ -414,22 +485,19 @@ public class MainActivity extends AppCompatActivity { // is frowned upon, but deliberately giving permissions as part of the intent is // way too cumbersome. - String fname = "qstep_log.txt"; // A reasonable world readable location,on many phones it's /storage/emulated/Documents // TODO: make this location configurable? File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); File file = null; FileOutputStream outStream = null; - Date now = new Date(); - logger.log("Saving log to:\n" + path.getPath() + "/" + fname); - logger.log("On: " + now.toString()); - try { if (!path.exists()) { path.mkdirs(); } - file = new File(path, fname); + file = new File(path, LOG_FILENAME); + logger.log("Saving log to: " + file + " at " + new Date()); + outStream = new FileOutputStream(file); outStream.write(logger.getLogText().getBytes()); @@ -437,11 +505,22 @@ public class MainActivity extends AppCompatActivity { logger.log("Log saved"); } catch (Exception e) { e.printStackTrace(); - logger.log("Exception:\n" + e.getMessage()); + logger.log("Failed to write log: " + e.getMessage()); } return file.getPath(); } + public void clearLogFile() { + File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + try { + File file = new File(path, LOG_FILENAME); + file.delete(); + } catch (Exception e) { + e.printStackTrace(); + logger.log("Failed to clear log: " + e.getMessage()); + } + } + public void shareLogFile(String filepath) { File file = new File(filepath); logger.log("Firing Intent.ACTION_SEND for file:"); diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java index 1a42eb5..4db9358 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RemoteClockInfo.java @@ -16,10 +16,6 @@ package org.chromium.latency.walt; -import android.util.Log; - -import java.lang.reflect.Method; - /** * Representation of our best knowledge of the remote clock. * All time variables here are stored in microseconds. @@ -27,18 +23,18 @@ import java.lang.reflect.Method; * Which time reporting function is used locally on Android: * This app uses SystemClock.uptimeMillis() for keeping local time which, up to * units, is the same time reported by System.nanoTime() and by - * clock_gettime(CLOCK_MONOTONIC, &ts) from time.h and is, roughly, the time + * clock_gettime(CLOCK_MONOTONIC, &ts) from time.h and is, roughly, the time * elapsed since last boot, excluding sleep time. * - * base_time is the local Android time when remote clock was zeroed. + * base_time is the local monotonic clock time when remote clock was zeroed. * * micros() is our best available approximation of the current reading of the remote clock. * - * Immediately after synchronization minLag is set to zero and the remote clock guaranteed to lag - * behind what micros() reports by at most maxLag. + * Immediately after synchronization, minLag is set to zero and the remote clock is guaranteed to + * lag behind what micros() reports by at most maxLag. * * Immediately after synchronization or an update of the bounds (minLag, maxLag) the following holds - * t_remote + minLag < micros() < t_rmote + maxLag + * t_remote + minLag < micros() < t_remote + maxLag * * For more details about clock synchronization refer to * https://github.com/google/walt/blob/master/android/WALT/app/src/main/jni/README.md @@ -50,67 +46,19 @@ public class RemoteClockInfo { public int maxLag; public long baseTime; - - public long micros() { - return microTime() - baseTime; - } - public static long microTime() { return System.nanoTime() / 1000; } - - /** - Find the wall time when uptime was zero = CLOCK_REALTIME - CLOCK_MONOTONIC - - Needed for TCP bridge because Python prior to 3.3 has no direct access to CLOCK_MONOTONIC - so the bridge returns timestamps as wall time and we need to convert them to CLOCK_MONOTONIC. - - See: - [1] https://docs.python.org/3/library/time.html#time.CLOCK_MONOTONIC - [2] http://stackoverflow.com/questions/14270300/what-is-the-difference-between-clock-monotonic-clock-monotonic-raw - [3] http://stackoverflow.com/questions/1205722/how-do-i-get-monotonic-time-durations-in-python - - android.os.SystemClock.currentTimeMicros() is hidden by @hide which means it can't be called - directly - calling it via reflection. - - See: - http://stackoverflow.com/questions/17035271/what-does-hide-mean-in-the-android-source-code - */ - public static long uptimeZero() { - long t = -1; - long dt = Long.MAX_VALUE; - try { - Class cls = Class.forName("android.os.SystemClock"); - Method myTimeGetter = cls.getMethod("currentTimeMicro"); - t = (long) myTimeGetter.invoke(null); - dt = t - microTime(); - } catch (Exception e) { - Log.i("WALT.uptimeZero", e.getMessage()); - } - - return dt; - } - - public static long currentTimeMicro() { - - long t = -1; - try { - Class cls = Class.forName("android.os.SystemClock"); - Method myTimeGetter = cls.getMethod("currentTimeMicro"); - t = (long) myTimeGetter.invoke(null); - } catch (Exception e) { - Log.i("WALT.currentTimeMicro", e.getMessage()); - } - - return t; + public long micros() { + return microTime() - baseTime; } public int getMeanLag() { return (minLag + maxLag) / 2; } - public String toString(){ + public String toString() { return "Remote clock [us]: current time = " + micros() + " baseTime = " + baseTime + " lagBounds = (" + minLag + ", " + maxLag + ")"; } diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java new file mode 100644 index 0000000..47a21ba --- /dev/null +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/RobotAutomationListener.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.chromium.latency.walt; + +public interface RobotAutomationListener { + public static final String START_EVENT = "START_EVENT"; + public static final String RESTART_EVENT = "RESTART_EVENT"; + public static final String FINISH_EVENT = "FINISH_EVENT"; + public static final String WRITE_LOG_EVENT = "WRITE_LOG_EVENT"; + public static final String CLEAR_LOG_EVENT = "CLEAR_LOG_EVENT"; + + public void onRobotAutomationEvent(String event); +} diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java index cfe6a53..629ed7d 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/ScreenResponseFragment.java @@ -53,7 +53,8 @@ import static org.chromium.latency.walt.Utils.getIntPreference; /** * Measurement of screen response time when switching between black and white. */ -public class ScreenResponseFragment extends Fragment implements View.OnClickListener { +public class ScreenResponseFragment extends Fragment + implements View.OnClickListener, RobotAutomationListener { private static final int CURVE_TIMEOUT = 1000; // milliseconds private static final int CURVE_BLINK_TIME = 250; // milliseconds @@ -248,14 +249,13 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { - // frameTimeNanos is he time in nanoseconds when the frame started being + // frameTimeNanos is the time in nanoseconds when the frame started being // rendered, in the nanoTime() timebase. lastFrameStartTime = frameTimeNanos / 1000 - waltDevice.clock.baseTime; lastFrameCallbackTime = System.nanoTime() / 1000 - waltDevice.clock.baseTime; } }); - // Repost doBlink to some far away time to blink again even if nothing arrives from // Teensy. This callback will almost always get cancelled by onIncomingTimestamp() handler.postDelayed(doBlinkRunnable, 550 + (long) (Math.random()*100)); @@ -290,9 +290,9 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList } } - final long startTimeMicros = lastFrameStartTime + waltDevice.clock.baseTime; - final long finishTimeMicros = tmsg.t + waltDevice.clock.baseTime; if (traceLogger != null) { + final long startTimeMicros = lastFrameStartTime + waltDevice.clock.baseTime; + final long finishTimeMicros = tmsg.t + waltDevice.clock.baseTime; traceLogger.log(startTimeMicros, finishTimeMicros, isBoxWhite ? "Black-to-white" : "White-to-black", "Bar starts at beginning of frame and ends when photosensor detects blink"); @@ -423,6 +423,18 @@ public class ScreenResponseFragment extends Fragment implements View.OnClickList } } + public void onRobotAutomationEvent(String event) { + // Never show the latency chart during automated runs. + shouldShowLatencyChart = false; + // Disable full-screen mode to prevent modal user dialog. + enableFullScreen = false; + if (event.equals(RobotAutomationListener.RESTART_EVENT)) { + onClick(stopButton); + } else if (event.equals(RobotAutomationListener.START_EVENT)) { + onClick(startButton); + } + } + private WaltDevice.TriggerHandler brightnessTriggerHandler = new WaltDevice.TriggerHandler() { @Override public void onReceive(WaltDevice.TriggerMessage tmsg) { diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java index 64e333d..e26a328 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/TapLatencyFragment.java @@ -36,7 +36,7 @@ import java.util.Locale; import static org.chromium.latency.walt.Utils.getBooleanPreference; public class TapLatencyFragment extends Fragment - implements View.OnClickListener { + implements View.OnClickListener, RobotAutomationListener { private static final int ACTION_DOWN_INDEX = 0; private static final int ACTION_UP_INDEX = 1; @@ -193,8 +193,8 @@ public class TapLatencyFragment extends Fragment } if (dt < 0 || dt > 200) { - logger.log(action + " bogus kernelTime, ignored, dt=" + dt); - return false; + logger.log(action + " bogus kernelTime=" + e.kernelTime + ", ignored, dt=" + dt); + return false; } return true; } @@ -303,4 +303,15 @@ public class TapLatencyFragment extends Fragment } } + + public void onRobotAutomationEvent(String event) { + // Never show the latency chart during automated runs. + shouldShowLatencyChart = false; + if (event.equals(RobotAutomationListener.RESTART_EVENT) || + event.equals(RobotAutomationListener.START_EVENT)) { + restartMeasurement(); + } else if (event.equals(RobotAutomationListener.FINISH_EVENT)) { + finishAndShowStats(); + } + } } diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java index 96ddcfd..8ec2cb4 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltDevice.java @@ -156,16 +156,20 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener { private String sendReceive(char c) throws IOException { - connection.sendByte(c); - return readOne(); + synchronized (connection) { + connection.sendByte(c); + return readOne(); + } } public void sendAndFlush(char c) { try { - connection.sendByte(c); - while(connection.blockingRead(buffer) > 0) { - // flushing all incoming data + synchronized (connection) { + connection.sendByte(c); + while (connection.blockingRead(buffer) > 0) { + // flushing all incoming data + } } } catch (Exception e) { logger.log("Exception in sendAndFlush: " + e.getMessage()); @@ -187,6 +191,7 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener { throw new IOException("Unexpected response from WALT. Expected \"" + ack + "\", got \"" + response + "\""); } + // Trim out the ack return response.substring(1).trim(); } @@ -237,6 +242,10 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener { return; } connection.updateLag(); + if (clock == null) { + // updateLag() will have logged a message if we get here + return; + } int drift = Math.abs(clock.getMeanLag()); String msg = String.format("Remote clock delayed between %d and %d us", clock.minLag, clock.maxLag); @@ -321,11 +330,13 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener { } void onReceiveRaw(String s) { - if (TriggerMessage.isTriggerString(s)) { - TriggerMessage tmsg = new TriggerMessage(s.substring(1).trim()); - onReceive(tmsg); - } else { - Log.i(TAG, "Malformed trigger data: " + s); + for (String trigger : s.split("\n")) { + if (TriggerMessage.isTriggerString(trigger)) { + TriggerMessage tmsg = new TriggerMessage(trigger.substring(1).trim()); + onReceive(tmsg); + } else { + Log.i(TAG, "Malformed trigger data: " + s); + } } } @@ -391,6 +402,12 @@ public class WaltDevice implements WaltConnection.ConnectionStateListener { } public void stopListener() { + // If the trigger listener is already stopped, then it is possible the listener thread is + // null. In that case, calling stop() followed by join() will result in a listener object + // that is stuck in the STOPPING state. + if (triggerListener.isStopped()) { + return; + } logger.log("Stopping Listener"); triggerListener.stop(); try { diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java index ee9c143..e63f4dc 100644 --- a/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/WaltTcpConnection.java @@ -32,8 +32,8 @@ import java.net.SocketTimeoutException; public class WaltTcpConnection implements WaltConnection { - // The local ip on ARC++ to connect to underlying ChromeOS - private static final String SERVER_IP = "192.168.254.1"; + // Use a "reverse" port over adb. The server is running on the host to which we're attached. + private static final String SERVER_IP = "127.0.0.1"; private static final int SERVER_PORT = 50007; private static final int TCP_READ_TIMEOUT_MS = 200; @@ -74,6 +74,10 @@ public class WaltTcpConnection implements WaltConnection { } public void connect() { + // If the singleton is already connected, do not kill the connection. + if (isConnected()) { + return; + } connectionState = Utils.ListenerState.STARTING; networkThread = new HandlerThread("NetworkThread"); networkThread.start(); @@ -85,6 +89,7 @@ public class WaltTcpConnection implements WaltConnection { try { InetAddress serverAddr = InetAddress.getByName(SERVER_IP); socket = new Socket(serverAddr, SERVER_PORT); + socket.setKeepAlive(true); socket.setSoTimeout(TCP_READ_TIMEOUT_MS); outputStream = socket.getOutputStream(); inputStream = socket.getInputStream(); @@ -119,18 +124,39 @@ public class WaltTcpConnection implements WaltConnection { return connectionState == Utils.ListenerState.RUNNING; } - public void sendByte(char c) throws IOException { - outputStream.write(Utils.char2byte(c)); + public void sendByte(final char c) throws IOException { + // All network accesses must occur on a separate thread. + networkHandler.post(new Runnable() { + @Override + public void run() { + try { + outputStream.write(Utils.char2byte(c)); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } - public void sendString(String s) throws IOException { - outputStream.write(s.getBytes("UTF-8")); + public void sendString(final String s) throws IOException { + // All network accesses must occur on a separate thread. + networkHandler.post(new Runnable() { + @Override + public void run() { + try { + outputStream.write(s.getBytes("UTF-8")); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } public synchronized int blockingRead(byte[] buff) { messageReceived = false; + // All network accesses must occur on a separate thread. networkHandler.post(new Runnable() { @Override public void run() { @@ -171,8 +197,7 @@ public class WaltTcpConnection implements WaltConnection { return lastRetVal; } - - private void updateClock(String cmd) throws IOException { + private synchronized void updateClock(String cmd) throws IOException { sendString(cmd); int retval = blockingRead(buffer); if (retval <= 0) { @@ -181,8 +206,9 @@ public class WaltTcpConnection implements WaltConnection { String s = new String(buffer, 0, retval); String[] parts = s.trim().split("\\s+"); // TODO: make sure reply starts with "clock" - long wallBaseTime = Long.parseLong(parts[1]); - remoteClock.baseTime = wallBaseTime - RemoteClockInfo.uptimeZero(); + // The bridge sends the time difference between when it sent the reply and when it zeroed + // the WALT's clock. We assume here that the reply transit time is negligible. + remoteClock.baseTime = RemoteClockInfo.microTime() - Long.parseLong(parts[1]); remoteClock.minLag = Integer.parseInt(parts[2]); remoteClock.maxLag = Integer.parseInt(parts[3]); } -- cgit v1.2.3