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.app.PendingIntent;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.hardware.usb.UsbDevice;
25import android.hardware.usb.UsbDeviceConnection;
26import android.hardware.usb.UsbManager;
27import android.support.v4.content.LocalBroadcastManager;
28
29import java.util.HashMap;
30import java.util.Locale;
31
32public abstract class BaseUsbConnection {
33    private static final String USB_PERMISSION_RESPONSE_INTENT = "usb-permission-response";
34    private static final String CONNECT_INTENT = "org.chromium.latency.walt.CONNECT";
35
36    protected SimpleLogger logger;
37    protected Context context;
38    private LocalBroadcastManager broadcastManager;
39    private BroadcastReceiver currentConnectReceiver;
40    private WaltConnection.ConnectionStateListener connectionStateListener;
41
42    private UsbManager usbManager;
43    protected UsbDevice usbDevice = null;
44    protected UsbDeviceConnection usbConnection;
45
46    public BaseUsbConnection(Context context) {
47        this.context = context;
48        usbManager = (UsbManager) this.context.getSystemService(Context.USB_SERVICE);
49        logger = SimpleLogger.getInstance(context);
50        broadcastManager = LocalBroadcastManager.getInstance(context);
51    }
52
53    public abstract int getVid();
54    public abstract int getPid();
55
56    // Used to distinguish between bootloader and normal mode that differ by PID
57    // TODO: change intent strings to reduce dependence on PID
58    protected abstract boolean isCompatibleUsbDevice(UsbDevice usbDevice);
59
60    public void onDisconnect() {
61        if (connectionStateListener != null) {
62            connectionStateListener.onDisconnect();
63        }
64    }
65
66    public void onConnect() {
67        if (connectionStateListener != null) {
68            connectionStateListener.onConnect();
69        }
70    }
71
72
73    private String getConnectIntent() {
74        return CONNECT_INTENT + getVid() + ":" + getPid();
75    }
76
77    private String getUsbPermissionResponseIntent() {
78        return USB_PERMISSION_RESPONSE_INTENT + getVid() + ":" + getPid();
79    }
80
81    public boolean isConnected() {
82        return usbConnection != null;
83    }
84
85    public void registerConnectCallback(final Runnable r) {
86        if (currentConnectReceiver != null) {
87            broadcastManager.unregisterReceiver(currentConnectReceiver);
88            currentConnectReceiver = null;
89        }
90
91        if (isConnected()) {
92            r.run();
93            return;
94        }
95
96        currentConnectReceiver = new BroadcastReceiver() {
97            @Override
98            public void onReceive(Context context, Intent intent) {
99                broadcastManager.unregisterReceiver(this);
100                r.run();
101            }
102        };
103        broadcastManager.registerReceiver(currentConnectReceiver,
104                new IntentFilter(getConnectIntent()));
105    }
106
107    public void connect() {
108        UsbDevice usbDevice = findUsbDevice();
109        connect(usbDevice);
110    }
111
112    public void connect(UsbDevice usbDevice) {
113        if (usbDevice == null) {
114            logger.log("Device not found.");
115            return;
116        }
117
118        if (!isCompatibleUsbDevice(usbDevice)) {
119            logger.log("Not a valid device");
120            return;
121        }
122
123        this.usbDevice = usbDevice;
124
125        // Request permission
126        // This displays a dialog asking user for permission to use the device.
127        // No dialog is displayed if the permission was already given before or the app started as a
128        // result of intent filter when the device was plugged in.
129
130        PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0,
131                new Intent(getUsbPermissionResponseIntent()), 0);
132        context.registerReceiver(respondToUsbPermission,
133                new IntentFilter(getUsbPermissionResponseIntent()));
134        logger.log("Requesting permission for USB device.");
135        usbManager.requestPermission(this.usbDevice, permissionIntent);
136    }
137
138    public void disconnect() {
139        onDisconnect();
140
141        usbConnection.close();
142        usbConnection = null;
143        usbDevice = null;
144
145        context.unregisterReceiver(disconnectReceiver);
146    }
147
148    private BroadcastReceiver disconnectReceiver = new BroadcastReceiver() {
149        @Override
150        public void onReceive(Context context, Intent intent) {
151            UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
152            if (isConnected() && BaseUsbConnection.this.usbDevice.equals(usbDevice)) {
153                logger.log("WALT was detached");
154                disconnect();
155            }
156        }
157    };
158
159    private BroadcastReceiver respondToUsbPermission = new BroadcastReceiver() {
160        @Override
161        public void onReceive(Context context, Intent intent) {
162
163            if (usbDevice == null) {
164                logger.log("USB device was not properly opened");
165                return;
166            }
167
168            if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) &&
169                    usbDevice.equals(intent.getParcelableExtra(UsbManager.EXTRA_DEVICE))){
170                usbConnection = usbManager.openDevice(usbDevice);
171
172                BaseUsbConnection.this.context.registerReceiver(disconnectReceiver,
173                        new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
174
175                onConnect();
176
177                broadcastManager.sendBroadcast(new Intent(getConnectIntent()));
178            } else {
179                logger.log("Could not get permission to open the USB device");
180            }
181            BaseUsbConnection.this.context.unregisterReceiver(respondToUsbPermission);
182        }
183    };
184
185    public UsbDevice findUsbDevice() {
186
187        logger.log(String.format("Looking for TeensyUSB VID=0x%x PID=0x%x", getVid(), getPid()));
188
189        HashMap<String, UsbDevice> deviceHash = usbManager.getDeviceList();
190        if (deviceHash.size() == 0) {
191            logger.log("No connected USB devices found");
192            return null;
193        }
194
195        logger.log("Found " + deviceHash.size() + " connected USB devices:");
196
197        UsbDevice usbDevice = null;
198
199        for (String key : deviceHash.keySet()) {
200
201            UsbDevice dev = deviceHash.get(key);
202
203            String msg = String.format(Locale.US,
204                    "USB Device: %s, VID:PID - %x:%x, %d interfaces",
205                    key, dev.getVendorId(), dev.getProductId(), dev.getInterfaceCount()
206            );
207
208            if (isCompatibleUsbDevice(dev)) {
209                usbDevice = dev;
210                msg = "Using " + msg;
211            } else {
212                msg = "Skipping " + msg;
213            }
214
215            logger.log(msg);
216        }
217        return usbDevice;
218    }
219
220    public void setConnectionStateListener(WaltConnection.ConnectionStateListener connectionStateListener) {
221        this.connectionStateListener = connectionStateListener;
222    }
223}
224