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.hardware.usb.UsbDevice; 21 import android.hardware.usb.UsbEndpoint; 22 import android.hardware.usb.UsbInterface; 23 import android.util.Log; 24 25 import java.io.IOException; 26 27 /** 28 * A singleton used as an interface for the physical WALT device. 29 */ 30 public class WaltUsbConnection extends BaseUsbConnection implements WaltConnection { 31 32 private static final int TEENSY_VID = 0x16c0; 33 // TODO: refactor to demystify PID. See BaseUsbConnection.isCompatibleUsbDevice() 34 private static final int TEENSY_PID = 0; 35 private static final int HALFKAY_PID = 0x0478; 36 private static final int USB_READ_TIMEOUT_MS = 200; 37 private static final String TAG = "WaltUsbConnection"; 38 39 private UsbEndpoint endpointIn = null; 40 private UsbEndpoint endpointOut = null; 41 42 private RemoteClockInfo remoteClock = new RemoteClockInfo(); 43 44 private static final Object LOCK = new Object(); 45 46 private static WaltUsbConnection instance; 47 48 private WaltUsbConnection(Context context) { 49 super(context); 50 } 51 52 public static WaltUsbConnection getInstance(Context context) { 53 synchronized (LOCK) { 54 if (instance == null) { 55 instance = new WaltUsbConnection(context.getApplicationContext()); 56 } 57 return instance; 58 } 59 } 60 61 @Override 62 public int getPid() { 63 return TEENSY_PID; 64 } 65 66 @Override 67 public int getVid() { 68 return TEENSY_VID; 69 } 70 71 @Override 72 protected boolean isCompatibleUsbDevice(UsbDevice usbDevice) { 73 // Allow any Teensy, but not in HalfKay bootloader mode 74 // Teensy PID depends on mode (e.g: Serail + MIDI) and also changed in TeensyDuino 1.31 75 return ((usbDevice.getProductId() != HALFKAY_PID) && 76 (usbDevice.getVendorId() == TEENSY_VID)); 77 } 78 79 80 // Called when WALT is physically unplugged from USB 81 @Override 82 public void onDisconnect() { 83 endpointIn = null; 84 endpointOut = null; 85 super.onDisconnect(); 86 } 87 88 89 // Called when WALT is physically plugged into USB 90 @Override 91 public void onConnect() { 92 // Serial mode only 93 // TODO: find the interface and endpoint indexes no matter what mode it is 94 int ifIdx = 1; 95 int epInIdx = 1; 96 int epOutIdx = 0; 97 98 UsbInterface iface = usbDevice.getInterface(ifIdx); 99 100 if (usbConnection.claimInterface(iface, true)) { 101 logger.log("Interface claimed successfully\n"); 102 } else { 103 logger.log("ERROR - can't claim interface\n"); 104 return; 105 } 106 107 endpointIn = iface.getEndpoint(epInIdx); 108 endpointOut = iface.getEndpoint(epOutIdx); 109 110 super.onConnect(); 111 } 112 113 @Override 114 public boolean isConnected() { 115 return super.isConnected() && (endpointIn != null) && (endpointOut != null); 116 } 117 118 119 @Override 120 public void sendByte(char c) throws IOException { 121 if (!isConnected()) { 122 throw new IOException("Not connected to WALT"); 123 } 124 // logger.log("Sending char " + c); 125 usbConnection.bulkTransfer(endpointOut, Utils.char2byte(c), 1, 100); 126 } 127 128 @Override 129 public int blockingRead(byte[] buffer) { 130 return usbConnection.bulkTransfer(endpointIn, buffer, buffer.length, USB_READ_TIMEOUT_MS); 131 } 132 133 134 @Override 135 public RemoteClockInfo syncClock() throws IOException { 136 if (!isConnected()) { 137 throw new IOException("Not connected to WALT"); 138 } 139 140 try { 141 int fd = usbConnection.getFileDescriptor(); 142 int ep_out = endpointOut.getAddress(); 143 int ep_in = endpointIn.getAddress(); 144 145 remoteClock.baseTime = syncClock(fd, ep_out, ep_in); 146 remoteClock.minLag = 0; 147 remoteClock.maxLag = getMaxE(); 148 } catch (Exception e) { 149 logger.log("Exception while syncing clocks: " + e.getStackTrace()); 150 } 151 logger.log("Synced clocks, maxE=" + remoteClock.maxLag + "us"); 152 Log.i(TAG, remoteClock.toString()); 153 return remoteClock; 154 } 155 156 @Override 157 public void updateLag() { 158 if (! isConnected()) { 159 logger.log("ERROR: Not connected, aborting checkDrift()"); 160 return; 161 } 162 updateBounds(); 163 remoteClock.minLag = getMinE(); 164 remoteClock.maxLag = getMaxE(); 165 } 166 167 168 169 // NDK / JNI stuff 170 // TODO: add guards to avoid calls to updateBounds and getter when listener is running. 171 private native long syncClock(int fd, int endpoint_out, int endpoint_in); 172 173 private native void updateBounds(); 174 175 private native int getMinE(); 176 177 private native int getMaxE(); 178 179 static { 180 System.loadLibrary("sync_clock_jni"); 181 } 182 } 183