1/*
2 * Copyright (C) 2011 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 com.android.adb;
18
19import android.hardware.usb.UsbConstants;
20import android.hardware.usb.UsbDeviceConnection;
21import android.hardware.usb.UsbEndpoint;
22import android.hardware.usb.UsbInterface;
23import android.hardware.usb.UsbRequest;
24import android.util.SparseArray;
25
26import java.util.LinkedList;
27
28/* This class represents a USB device that supports the adb protocol. */
29public class AdbDevice {
30
31    private final AdbTestActivity mActivity;
32    private final UsbDeviceConnection mDeviceConnection;
33    private final UsbEndpoint mEndpointOut;
34    private final UsbEndpoint mEndpointIn;
35
36    private String mSerial;
37
38    // pool of requests for the OUT endpoint
39    private final LinkedList<UsbRequest> mOutRequestPool = new LinkedList<UsbRequest>();
40    // pool of requests for the IN endpoint
41    private final LinkedList<UsbRequest> mInRequestPool = new LinkedList<UsbRequest>();
42    // list of currently opened sockets
43    private final SparseArray<AdbSocket> mSockets = new SparseArray<AdbSocket>();
44    private int mNextSocketId = 1;
45
46    private final WaiterThread mWaiterThread = new WaiterThread();
47
48    public AdbDevice(AdbTestActivity activity, UsbDeviceConnection connection,
49            UsbInterface intf) {
50        mActivity = activity;
51        mDeviceConnection = connection;
52        mSerial = connection.getSerial();
53
54        UsbEndpoint epOut = null;
55        UsbEndpoint epIn = null;
56        // look for our bulk endpoints
57        for (int i = 0; i < intf.getEndpointCount(); i++) {
58            UsbEndpoint ep = intf.getEndpoint(i);
59            if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
60                if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
61                    epOut = ep;
62                } else {
63                    epIn = ep;
64                }
65            }
66        }
67        if (epOut == null || epIn == null) {
68            throw new IllegalArgumentException("not all endpoints found");
69        }
70        mEndpointOut = epOut;
71        mEndpointIn = epIn;
72    }
73
74    // return device serial number
75    public String getSerial() {
76        return mSerial;
77    }
78
79    // get an OUT request from our pool
80    public UsbRequest getOutRequest() {
81        synchronized(mOutRequestPool) {
82            if (mOutRequestPool.isEmpty()) {
83                UsbRequest request = new UsbRequest();
84                request.initialize(mDeviceConnection, mEndpointOut);
85                return request;
86            } else {
87                return mOutRequestPool.removeFirst();
88            }
89        }
90    }
91
92    // return an OUT request to the pool
93    public void releaseOutRequest(UsbRequest request) {
94        synchronized (mOutRequestPool) {
95            mOutRequestPool.add(request);
96        }
97    }
98
99    // get an IN request from the pool
100    public UsbRequest getInRequest() {
101        synchronized(mInRequestPool) {
102            if (mInRequestPool.isEmpty()) {
103                UsbRequest request = new UsbRequest();
104                request.initialize(mDeviceConnection, mEndpointIn);
105                return request;
106            } else {
107                return mInRequestPool.removeFirst();
108            }
109        }
110    }
111
112    public void start() {
113        mWaiterThread.start();
114        connect();
115    }
116
117    public AdbSocket openSocket(String destination) {
118        AdbSocket socket;
119        synchronized (mSockets) {
120            int id = mNextSocketId++;
121            socket = new AdbSocket(this, id);
122            mSockets.put(id, socket);
123        }
124        if (socket.open(destination)) {
125            return socket;
126        } else {
127            return null;
128        }
129    }
130
131    private AdbSocket getSocket(int id) {
132        synchronized (mSockets) {
133            return mSockets.get(id);
134        }
135    }
136
137    public void socketClosed(AdbSocket socket) {
138        synchronized (mSockets) {
139            mSockets.remove(socket.getId());
140        }
141    }
142
143    // send a connect command
144    private void connect() {
145        AdbMessage message = new AdbMessage();
146        message.set(AdbMessage.A_CNXN, AdbMessage.A_VERSION, AdbMessage.MAX_PAYLOAD, "host::\0");
147        message.write(this);
148    }
149
150    // handle connect response
151    private void handleConnect(AdbMessage message) {
152        if (message.getDataString().startsWith("device:")) {
153            log("connected");
154            mActivity.deviceOnline(this);
155        }
156    }
157
158    public void stop() {
159        synchronized (mWaiterThread) {
160            mWaiterThread.mStop = true;
161        }
162    }
163
164    // dispatch a message from the device
165    void dispatchMessage(AdbMessage message) {
166        int command = message.getCommand();
167        switch (command) {
168            case AdbMessage.A_SYNC:
169                log("got A_SYNC");
170                break;
171            case AdbMessage.A_CNXN:
172                handleConnect(message);
173                break;
174            case AdbMessage.A_OPEN:
175            case AdbMessage.A_OKAY:
176            case AdbMessage.A_CLSE:
177            case AdbMessage.A_WRTE:
178                AdbSocket socket = getSocket(message.getArg1());
179                if (socket == null) {
180                    log("ERROR socket not found");
181                } else {
182                    socket.handleMessage(message);
183                }
184                break;
185        }
186    }
187
188    void log(String s) {
189        mActivity.log(s);
190    }
191
192
193    private class WaiterThread extends Thread {
194        public boolean mStop;
195
196        public void run() {
197            // start out with a command read
198            AdbMessage currentCommand = new AdbMessage();
199            AdbMessage currentData = null;
200            // FIXME error checking
201            currentCommand.readCommand(getInRequest());
202
203            while (true) {
204                synchronized (this) {
205                    if (mStop) {
206                        return;
207                    }
208                }
209                UsbRequest request = mDeviceConnection.requestWait();
210                if (request == null) {
211                    break;
212                }
213
214                AdbMessage message = (AdbMessage)request.getClientData();
215                request.setClientData(null);
216                AdbMessage messageToDispatch = null;
217
218                if (message == currentCommand) {
219                    int dataLength = message.getDataLength();
220                    // read data if length > 0
221                    if (dataLength > 0) {
222                        message.readData(getInRequest(), dataLength);
223                        currentData = message;
224                    } else {
225                        messageToDispatch = message;
226                    }
227                    currentCommand = null;
228                } else if (message == currentData) {
229                    messageToDispatch = message;
230                    currentData = null;
231                }
232
233                if (messageToDispatch != null) {
234                    // queue another read first
235                    currentCommand = new AdbMessage();
236                    currentCommand.readCommand(getInRequest());
237
238                    // then dispatch the current message
239                    dispatchMessage(messageToDispatch);
240                }
241
242                // put request back into the appropriate pool
243                if (request.getEndpoint() == mEndpointOut) {
244                    releaseOutRequest(request);
245                } else {
246                    synchronized (mInRequestPool) {
247                        mInRequestPool.add(request);
248                    }
249                }
250            }
251        }
252    }
253}
254