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
17package org.chromium.latency.walt;
18
19import android.content.Context;
20import android.hardware.usb.UsbDevice;
21import android.hardware.usb.UsbEndpoint;
22import android.hardware.usb.UsbInterface;
23import android.util.Log;
24
25import java.io.IOException;
26
27/**
28 * A singleton used as an interface for the physical WALT device.
29 */
30public 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