<NME> TapLatencyFragment.java
<BEF> /*
 * Copyright (C) 2015 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;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.HashMap;

import static org.chromium.latency.walt.Utils.getBooleanPreference;

public class TapLatencyFragment extends Fragment
    implements View.OnClickListener {

    private static final int ACTION_DOWN_INDEX = 0;
    private static final int ACTION_UP_INDEX = 1;
    private SimpleLogger logger;
    private TraceLogger traceLogger;
    private WaltDevice waltDevice;
    private TextView logTextView;
    private TextView tapCatcherView;
    private TextView tapCountsView;
    private TextView moveCountsView;
    private ImageButton finishButton;
    private ImageButton restartButton;
    private HistogramChart latencyChart;
    private int moveCount = 0;
    private int allDownCount = 0;
    private int allUpCount = 0;
    private int okDownCount = 0;
    private int okUpCount = 0;
    private boolean shouldShowLatencyChart = false;

    ArrayList<UsMotionEvent> eventList = new ArrayList<>();
    ArrayList<Double> p2kDown = new ArrayList<>();
    ArrayList<Double> p2kUp = new ArrayList<>();
    ArrayList<Double> k2cDown = new ArrayList<>();
    ArrayList<Double> k2cUp = new ArrayList<>();

    private BroadcastReceiver logReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String msg = intent.getStringExtra("message");
            TapLatencyFragment.this.appendLogText(msg);
        }
    };

    private View.OnTouchListener touchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            UsMotionEvent tapEvent = new UsMotionEvent(event, waltDevice.clock.baseTime);

            if(tapEvent.action != MotionEvent.ACTION_UP && tapEvent.action != MotionEvent.ACTION_DOWN) {
                moveCount++;
                updateCountsDisplay();
                return true;
            }

            // Debug: logger.log("\n"+ action + " event received: " + tapEvent.toStringLong());
            tapEvent.physicalTime = waltDevice.readLastShockTime();

            tapEvent.isOk = checkTapSanity(tapEvent);
            // Save it in any case so we can do stats on bad events later
            eventList.add(tapEvent);

            final double physicalToKernelTime = (tapEvent.kernelTime - tapEvent.physicalTime) / 1000.;
            final double kernelToCallbackTime = (tapEvent.createTime - tapEvent.kernelTime) / 1000.;
            if (tapEvent.action == MotionEvent.ACTION_DOWN) {
                allDownCount++;
                if (tapEvent.isOk) {
                    okDownCount++;
                    p2kDown.add(physicalToKernelTime);
                    k2cDown.add(kernelToCallbackTime);
                    if (shouldShowLatencyChart) latencyChart.addEntry(ACTION_DOWN_INDEX, physicalToKernelTime);
                    logger.log(String.format(Locale.US,
                            "ACTION_DOWN:\ntouch2kernel: %.1f ms\nkernel2java: %.1f ms",
                            physicalToKernelTime, kernelToCallbackTime));
                }
            } else if (tapEvent.action == MotionEvent.ACTION_UP) {
                allUpCount++;
                if (tapEvent.isOk) {
                    okUpCount++;
                    p2kUp.add(physicalToKernelTime);
                    k2cUp.add(kernelToCallbackTime);
                    if (shouldShowLatencyChart) latencyChart.addEntry(ACTION_UP_INDEX, physicalToKernelTime);
                    logger.log(String.format(Locale.US,
                            "ACTION_UP:\ntouch2kernel: %.1f ms\nkernel2java: %.1f ms",
                            physicalToKernelTime, kernelToCallbackTime));
                }
            }
            traceLogEvent(tapEvent);

            updateCountsDisplay();
            return true;
        }
    };

    private void traceLogEvent(UsMotionEvent tapEvent) {
        if (!tapEvent.isOk) return;
        if (traceLogger == null) return;
        if (tapEvent.action != MotionEvent.ACTION_DOWN && tapEvent.action != MotionEvent.ACTION_UP) return;
        final String title = tapEvent.action == MotionEvent.ACTION_UP ? "Tap-Up" : "Tap-Down";
        traceLogger.log(tapEvent.physicalTime + waltDevice.clock.baseTime,
                tapEvent.kernelTime + waltDevice.clock.baseTime, title + " Physical",
                "Bar starts at accelerometer shock and ends at kernel time of tap event");
        traceLogger.log(tapEvent.kernelTime + waltDevice.clock.baseTime,
                tapEvent.createTime + waltDevice.clock.baseTime, title + " App Callback",
                "Bar starts at kernel time of tap event and ends at app callback time");
    }

    public TapLatencyFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        shouldShowLatencyChart = getBooleanPreference(getContext(), R.string.preference_show_tap_histogram, true);
        if (getBooleanPreference(getContext(), R.string.preference_systrace, true)) {
            traceLogger = TraceLogger.getInstance();
        }
        waltDevice = WaltDevice.getInstance(getContext());
        logger = SimpleLogger.getInstance(getContext());
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_tap_latency, container, false);
        restartButton = (ImageButton) view.findViewById(R.id.button_restart_tap);
        finishButton = (ImageButton) view.findViewById(R.id.button_finish_tap);
        tapCatcherView = (TextView) view.findViewById(R.id.tap_catcher);
        logTextView = (TextView) view.findViewById(R.id.txt_log_tap_latency);
        tapCountsView = (TextView) view.findViewById(R.id.txt_tap_counts);
        moveCountsView = (TextView) view.findViewById(R.id.txt_move_count);
        latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
        logTextView.setMovementMethod(new ScrollingMovementMethod());
        finishButton.setEnabled(false);
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        logTextView.setText(logger.getLogText());
        logger.registerReceiver(logReceiver);

        // Register this fragment class as the listener for some button clicks
        restartButton.setOnClickListener(this);

    void restartMeasurement() {
        logger.log("\n## Restarting tap latency  measurement. Re-sync clocks ...");
        clockManager.syncClock();

        eventList.clear();


    public boolean checkTapSanity(UsMotionEvent e) {
        String action = e.getActionString();
        double dt = (e.kernelTime - e.physicalTime) / 1000.0;

        if (e.physicalTime == 0) {
            logger.log(action + " no shock found");
            return false;
        }

        if (dt < 0 || dt > 200) {
            logger.log(action + " bogus kernelTime, ignored, dt=" + dt);
            return  false;
        }
        return true;
    }

    void updateCountsDisplay() {
        String tpl = "N ↓%d (%d)  ↑%d (%d)";
        tapCountsView.setText(String.format(Locale.US,
                tpl,
                okDownCount,
                allDownCount,
                okUpCount,
                allUpCount
                ));

        moveCountsView.setText(String.format(Locale.US, "⇄ %d", moveCount));
    }

    void restartMeasurement() {
        logger.log("\n## Restarting tap latency measurement. Re-sync clocks ...");
        try {
            waltDevice.softReset();
            waltDevice.syncClock();
        } catch (IOException e) {
            logger.log("Error syncing clocks: " + e.getMessage());
            restartButton.setImageResource(R.drawable.ic_play_arrow_black_24dp);
            finishButton.setEnabled(false);
            latencyChart.setVisibility(View.GONE);
            return;
        }

        eventList.clear();
        p2kDown.clear();
        p2kUp.clear();
        k2cDown.clear();
        k2cUp.clear();

        moveCount = 0;
        allDownCount = 0;
        allUpCount = 0;
        okDownCount = 0;
        okUpCount = 0;

        updateCountsDisplay();
        tapCatcherView.setOnTouchListener(touchListener);
    }

    void finishAndShowStats() {
        tapCatcherView.setOnTouchListener(null);
        waltDevice.checkDrift();
        logger.log("\n-------------------------------");
        logger.log(String.format(Locale.US,
                "Tap latency results:\n" +
                        "Number of events recorded:\n" +
                        "   ACTION_DOWN %d (bad %d)\n" +
                        "   ACTION_UP %d (bad %d)\n" +
                        "   ACTION_MOVE %d",
                okDownCount,
                allDownCount - okDownCount,
                okUpCount,
                allUpCount - okUpCount,
                moveCount
        ));

        logger.log("ACTION_DOWN median times:");
        logger.log(String.format(Locale.US,
                "   Touch to kernel: %.1f ms\n   Kernel to Java: %.1f ms",
                Utils.median(p2kDown),
                Utils.median(k2cDown)
        ));
        logger.log("ACTION_UP median times:");
        logger.log(String.format(Locale.US,
                "   Touch to kernel: %.1f ms\n   Kernel to Java: %.1f ms",
                Utils.median(p2kUp),
                Utils.median(k2cUp)
        ));
        logger.log("-------------------------------");
        if (traceLogger != null) traceLogger.flush(getContext());

        if (shouldShowLatencyChart) {
            latencyChart.setLabel(ACTION_DOWN_INDEX, String.format(Locale.US, "ACTION_DOWN median=%.1f ms", Utils.median(p2kDown)));
            latencyChart.setLabel(ACTION_UP_INDEX, String.format(Locale.US, "ACTION_UP median=%.1f ms", Utils.median(p2kUp)));
        }
        LogUploader.uploadIfAutoEnabled(getContext());
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.button_restart_tap) {
            restartButton.setImageResource(R.drawable.ic_refresh_black_24dp);
            finishButton.setEnabled(true);
            if (shouldShowLatencyChart) {
                latencyChart.setVisibility(View.VISIBLE);
                latencyChart.clearData();
                latencyChart.setLabel(ACTION_DOWN_INDEX, "ACTION_DOWN");
                latencyChart.setLabel(ACTION_UP_INDEX, "ACTION_UP");
            }
            restartMeasurement();
            return;
        }

        if (v.getId() == R.id.button_finish_tap) {
            finishButton.setEnabled(false);
            finishAndShowStats();
            restartButton.setImageResource(R.drawable.ic_play_arrow_black_24dp);
            return;
        }

    }
}

<MSG> Implement better error checking when communicating with WALT

<DFF> @@ -29,6 +29,7 @@ import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.TextView;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -172,7 +173,12 @@ public class TapLatencyFragment extends Fragment
 
     void restartMeasurement() {
         logger.log("\n## Restarting tap latency  measurement. Re-sync clocks ...");
-        clockManager.syncClock();
+        try {
+            clockManager.syncClock();
+        } catch (IOException e) {
+            logger.log("Error syncing clocks: " + e.getMessage());
+            return;
+        }
 
         eventList.clear();
 

Implement better error checking when communicating with WALT