/* * Copyright (C) 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; import android.content.Context; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.util.Log; import java.io.IOException; /** * A singleton used as an interface for the physical WALT device. */ public class WaltUsbConnection extends BaseUsbConnection implements WaltConnection { private static final int TEENSY_VID = 0x16c0; // TODO: refactor to demystify PID. See BaseUsbConnection.isCompatibleUsbDevice() private static final int TEENSY_PID = 0; private static final int HALFKAY_PID = 0x0478; private static final int USB_READ_TIMEOUT_MS = 200; private static final String TAG = "WaltUsbConnection"; private UsbEndpoint endpointIn = null; private UsbEndpoint endpointOut = null; private RemoteClockInfo remoteClock = new RemoteClockInfo(); private static final Object LOCK = new Object(); private static WaltUsbConnection instance; private WaltUsbConnection(Context context) { super(context); } public static WaltUsbConnection getInstance(Context context) { synchronized (LOCK) { if (instance == null) { instance = new WaltUsbConnection(context.getApplicationContext()); } return instance; } } @Override public int getPid() { return TEENSY_PID; } @Override public int getVid() { return TEENSY_VID; } @Override protected boolean isCompatibleUsbDevice(UsbDevice usbDevice) { // Allow any Teensy, but not in HalfKay bootloader mode // Teensy PID depends on mode (e.g: Serail + MIDI) and also changed in TeensyDuino 1.31 return ((usbDevice.getProductId() != HALFKAY_PID) && (usbDevice.getVendorId() == TEENSY_VID)); } // Called when WALT is physically unplugged from USB @Override public void onDisconnect() { endpointIn = null; endpointOut = null; super.onDisconnect(); } // Called when WALT is physically plugged into USB @Override public void onConnect() { // Serial mode only // TODO: find the interface and endpoint indexes no matter what mode it is int ifIdx = 1; int epInIdx = 1; int epOutIdx = 0; UsbInterface iface = usbDevice.getInterface(ifIdx); if (usbConnection.claimInterface(iface, true)) { logger.log("Interface claimed successfully\n"); } else { logger.log("ERROR - can't claim interface\n"); return; } endpointIn = iface.getEndpoint(epInIdx); endpointOut = iface.getEndpoint(epOutIdx); super.onConnect(); } @Override public boolean isConnected() { return super.isConnected() && (endpointIn != null) && (endpointOut != null); } @Override public void sendByte(char c) throws IOException { if (!isConnected()) { throw new IOException("Not connected to WALT"); } // logger.log("Sending char " + c); usbConnection.bulkTransfer(endpointOut, Utils.char2byte(c), 1, 100); } @Override public int blockingRead(byte[] buffer) { return usbConnection.bulkTransfer(endpointIn, buffer, buffer.length, USB_READ_TIMEOUT_MS); } @Override public RemoteClockInfo syncClock() throws IOException { if (!isConnected()) { throw new IOException("Not connected to WALT"); } try { int fd = usbConnection.getFileDescriptor(); int ep_out = endpointOut.getAddress(); int ep_in = endpointIn.getAddress(); remoteClock.baseTime = syncClock(fd, ep_out, ep_in); remoteClock.minLag = 0; remoteClock.maxLag = getMaxE(); } catch (Exception e) { logger.log("Exception while syncing clocks: " + e.getStackTrace()); } logger.log("Synced clocks, maxE=" + remoteClock.maxLag + "us"); Log.i(TAG, remoteClock.toString()); return remoteClock; } @Override public void updateLag() { if (! isConnected()) { logger.log("ERROR: Not connected, aborting checkDrift()"); return; } updateBounds(); remoteClock.minLag = getMinE(); remoteClock.maxLag = getMaxE(); } // NDK / JNI stuff // TODO: add guards to avoid calls to updateBounds and getter when listener is running. private native long syncClock(int fd, int endpoint_out, int endpoint_in); private native void updateBounds(); private native int getMinE(); private native int getMaxE(); static { System.loadLibrary("sync_clock_jni"); } }