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 
17 package com.android.adb;
18 
19 import android.hardware.usb.UsbConstants;
20 import android.hardware.usb.UsbDeviceConnection;
21 import android.hardware.usb.UsbEndpoint;
22 import android.hardware.usb.UsbInterface;
23 import android.hardware.usb.UsbRequest;
24 import android.util.SparseArray;
25 
26 import java.util.LinkedList;
27 
28 /* This class represents a USB device that supports the adb protocol. */
29 public 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 
AdbDevice(AdbTestActivity activity, UsbDeviceConnection connection, UsbInterface intf)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
getSerial()75     public String getSerial() {
76         return mSerial;
77     }
78 
79     // get an OUT request from our pool
getOutRequest()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
releaseOutRequest(UsbRequest request)93     public void releaseOutRequest(UsbRequest request) {
94         synchronized (mOutRequestPool) {
95             mOutRequestPool.add(request);
96         }
97     }
98 
99     // get an IN request from the pool
getInRequest()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 
start()112     public void start() {
113         mWaiterThread.start();
114         connect();
115     }
116 
openSocket(String destination)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 
getSocket(int id)131     private AdbSocket getSocket(int id) {
132         synchronized (mSockets) {
133             return mSockets.get(id);
134         }
135     }
136 
socketClosed(AdbSocket socket)137     public void socketClosed(AdbSocket socket) {
138         synchronized (mSockets) {
139             mSockets.remove(socket.getId());
140         }
141     }
142 
143     // send a connect command
connect()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
handleConnect(AdbMessage message)151     private void handleConnect(AdbMessage message) {
152         if (message.getDataString().startsWith("device:")) {
153             log("connected");
154             mActivity.deviceOnline(this);
155         }
156     }
157 
stop()158     public void stop() {
159         synchronized (mWaiterThread) {
160             mWaiterThread.mStop = true;
161         }
162     }
163 
164     // dispatch a message from the device
dispatchMessage(AdbMessage message)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 
log(String s)188     void log(String s) {
189         mActivity.log(s);
190     }
191 
192 
193     private class WaiterThread extends Thread {
194         public boolean mStop;
195 
run()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