1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.tools.sdkcontroller.lib;
18 
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22 import java.util.ArrayList;
23 import java.util.List;
24 
25 import android.util.Log;
26 import android.net.LocalServerSocket;
27 import android.net.LocalSocket;
28 
29 import com.android.tools.sdkcontroller.lib.Channel;
30 import com.android.tools.sdkcontroller.service.ControllerService;
31 
32 /**
33  * Encapsulates a connection between SdkController service and the emulator. On
34  * the device side, the connection is bound to the UNIX-domain socket named
35  * 'android.sdk.controller'. On the emulator side the connection is established
36  * via TCP port that is used to forward I/O traffic on the host machine to
37  * 'android.sdk.controller' socket on the device. Typically, the port forwarding
38  * can be enabled using adb command:
39  * <p/>
40  * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
41  * <p/>
42  * The way communication between the emulator and SDK controller service works
43  * is as follows:
44  * <p/>
45  * 1. Both sides, emulator and the service have components that implement a particular
46  * type of emulation. For instance, AndroidSensorsPort in the emulator, and
47  * SensorChannel in the application implement sensors emulation.
48  * Emulation channels are identified by unique names. For instance, sensor emulation
49  * is done via "sensors" channel, multi-touch emulation is done via "multi-touch"
50  * channel, etc.
51  * <p/>
52  * 2. Channels are connected to emulator via separate socket instance (though all
53  * of the connections share the same socket address).
54  * <p/>
55  * 3. Connection is initiated by the emulator side, while the service provides
56  * its side (a channel) that implement functionality and exchange protocol required
57  * by the requested type of emulation.
58  * <p/>
59  * Given that, the main responsibilities of this class are:
60  * <p/>
61  * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
62  * <p/>
63  * 2. Maintain a list of service-side channels registered by the application.
64  * <p/>
65  * 3. Bind emulator connection with service-side channel via port name, provided by
66  * the emulator.
67  * <p/>
68  * 4. Monitor connection state with the emulator, and automatically restore the
69  * connection once it is lost.
70  */
71 public class Connection {
72     /** UNIX-domain name reserved for SDK controller. */
73     public static final String SDK_CONTROLLER_PORT = "android.sdk.controller";
74     /** Tag for logging messages. */
75     private static final String TAG = "SdkControllerConnection";
76     /** Controls debug logging */
77     private static final boolean DEBUG = false;
78 
79     /** Server socket used to listen to emulator connections. */
80     private LocalServerSocket mServerSocket = null;
81     /** Service that has created this object. */
82     private ControllerService mService;
83     /**
84      * List of connected emulator sockets, pending for a channel to be registered.
85      * <p/>
86      * Emulator may connect to SDK controller before the app registers a channel
87      * for that connection. In this case (when app-side channel is not registered
88      * with this class) we will keep emulator connection in this list, pending
89      * for the app-side channel to register.
90      */
91     private List<Socket> mPendingSockets = new ArrayList<Socket>();
92     /**
93      * List of registered app-side channels.
94      * <p/>
95      * Channels that are kept in this list may be disconnected from (or pending
96      * connection with) the emulator, or they may be connected with the
97      * emulator.
98      */
99     private List<Channel> mChannels = new ArrayList<Channel>();
100 
101     /**
102      * Constructs Connection instance.
103      */
Connection(ControllerService service)104     public Connection(ControllerService service) {
105         mService = service;
106         if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed.");
107     }
108 
109     /**
110      * Binds to the socket, and starts the listening thread.
111      */
connect()112     public void connect() {
113         if (DEBUG) Log.d(TAG, "SdkControllerConnection is connecting...");
114         // Start connection listener.
115         new Thread(new Runnable() {
116                 @Override
117             public void run() {
118                 runIOLooper();
119             }
120         }, "SdkControllerConnectionIoLoop").start();
121     }
122 
123     /**
124      * Stops the listener, and closes the socket.
125      *
126      * @return true if connection has been stopped in this call, or false if it
127      *         has been already stopped when this method has been called.
128      */
disconnect()129     public boolean disconnect() {
130         // This is the only place in this class where we will null the
131         // socket object. Since this method can be called concurrently from
132         // different threads, lets do this under the lock.
133         LocalServerSocket socket;
134         synchronized (this) {
135             socket = mServerSocket;
136             mServerSocket = null;
137         }
138         if (socket != null) {
139             if (DEBUG) Log.d(TAG, "SdkControllerConnection is stopping I/O looper...");
140             // Stop accepting new connections.
141             wakeIOLooper(socket);
142             try {
143                 socket.close();
144             } catch (Exception e) {
145             }
146 
147             // Close all the pending sockets, and clear pending socket list.
148             if (DEBUG) Log.d(TAG, "SdkControllerConnection is closing pending sockets...");
149             for (Socket pending_socket : mPendingSockets) {
150                 pending_socket.close();
151             }
152             mPendingSockets.clear();
153 
154             // Disconnect all the emualtors.
155             if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnecting channels...");
156             for (Channel channel : mChannels) {
157                 if (channel.disconnect()) {
158                     channel.onEmulatorDisconnected();
159                 }
160             }
161             if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected.");
162         }
163         return socket != null;
164     }
165 
166     /**
167      * Registers SDK controller channel.
168      *
169      * @param channel SDK controller emulator to register.
170      * @return true if channel has been registered successfully, or false if channel
171      *         with the same name is already registered.
172      */
registerChannel(Channel channel)173     public boolean registerChannel(Channel channel) {
174         for (Channel check_channel : mChannels) {
175             if (check_channel.getChannelName().equals(channel.getChannelName())) {
176                 Loge("Registering a duplicate Channel " + channel.getChannelName());
177                 return false;
178             }
179         }
180         if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName());
181         mChannels.add(channel);
182 
183         // Lets see if there is a pending socket for this channel.
184         for (Socket pending_socket : mPendingSockets) {
185             if (pending_socket.getChannelName().equals(channel.getChannelName())) {
186                 // Remove the socket from the pending list, and connect the registered channel with it.
187                 if (DEBUG) Log.d(TAG, "Found pending Socket for registering Channel "
188                         + channel.getChannelName());
189                 mPendingSockets.remove(pending_socket);
190                 channel.connect(pending_socket);
191             }
192         }
193         return true;
194     }
195 
196     /**
197      * Checks if at least one socket connection exists with channel.
198      *
199      * @return true if at least one socket connection exists with channel.
200      */
isEmulatorConnected()201     public boolean isEmulatorConnected() {
202         for (Channel channel : mChannels) {
203             if (channel.isConnected()) {
204                 return true;
205             }
206         }
207         return !mPendingSockets.isEmpty();
208     }
209 
210     /**
211      * Gets Channel instance for the given channel name.
212      *
213      * @param name Channel name to get Channel instance for.
214      * @return Channel instance for the given channel name, or NULL if no
215      *         channel has been registered for that name.
216      */
getChannel(String name)217     public Channel getChannel(String name) {
218         for (Channel channel : mChannels) {
219             if (channel.getChannelName().equals(name)) {
220                 return channel;
221             }
222         }
223         return null;
224     }
225 
226     /**
227      * Gets connected emulator socket that is pending for service-side channel
228      * registration.
229      *
230      * @param name Channel name to lookup Socket for.
231      * @return Connected emulator socket that is pending for service-side channel
232      *         registration, or null if no socket is pending for service-size
233      *         channel registration.
234      */
getPendingSocket(String name)235     private Socket getPendingSocket(String name) {
236         for (Socket socket : mPendingSockets) {
237             if (socket.getChannelName().equals(name)) {
238                 return socket;
239             }
240         }
241         return null;
242     }
243 
244     /**
245      * Wakes I/O looper waiting on connection with the emulator.
246      *
247      * @param socket Server socket waiting on connection.
248      */
wakeIOLooper(LocalServerSocket socket)249     private void wakeIOLooper(LocalServerSocket socket) {
250         // We wake the looper by connecting to the socket.
251         LocalSocket waker = new LocalSocket();
252         try {
253             waker.connect(socket.getLocalSocketAddress());
254         } catch (IOException e) {
255             Loge("Exception " + e + " in SdkControllerConnection while waking up the I/O looper.");
256         }
257     }
258 
259     /**
260      * Loops on the local socket, handling emulator connection attempts.
261      */
runIOLooper()262     private void runIOLooper() {
263         if (DEBUG) Log.d(TAG, "In SdkControllerConnection I/O looper.");
264         do {
265             try {
266                 // Create non-blocking server socket that would listen for connections,
267                 // and bind it to the given port on the local host.
268                 mServerSocket = new LocalServerSocket(SDK_CONTROLLER_PORT);
269                 LocalServerSocket socket = mServerSocket;
270                 while (socket != null) {
271                     final LocalSocket sk = socket.accept();
272                     if (mServerSocket != null) {
273                         onAccept(sk);
274                     } else {
275                         break;
276                     }
277                     socket = mServerSocket;
278                 }
279             } catch (IOException e) {
280                 Loge("Exception " + e + "SdkControllerConnection I/O looper.");
281             }
282             if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper.");
283 
284           // If we're exiting the internal loop for reasons other than an explicit
285           // disconnect request, we should reconnect again.
286         } while (disconnect());
287     }
288 
289     /**
290      * Accepts new connection from the emulator.
291      *
292      * @param sock Connecting socket.
293      * @throws IOException
294      */
onAccept(LocalSocket sock)295     private void onAccept(LocalSocket sock) throws IOException {
296         final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE);
297 
298         // By protocol, first byte received from newly connected emulator socket
299         // indicates host endianness.
300         Socket.receive(sock, handshake.array(), 1);
301         final ByteOrder endian = (handshake.getChar() == 0) ? ByteOrder.LITTLE_ENDIAN :
302                 ByteOrder.BIG_ENDIAN;
303         handshake.order(endian);
304 
305         // Right after that follows the handshake query header.
306         handshake.position(0);
307         Socket.receive(sock, handshake.array(), handshake.array().length);
308 
309         // First int - signature
310         final int signature = handshake.getInt();
311         assert signature == ProtocolConstants.PACKET_SIGNATURE;
312         // Second int - total query size (including fixed query header)
313         final int remains = handshake.getInt() - ProtocolConstants.QUERY_HEADER_SIZE;
314         // After that - header type (which must be SDKCTL_PACKET_TYPE_QUERY)
315         final int msg_type = handshake.getInt();
316         assert msg_type == ProtocolConstants.PACKET_TYPE_QUERY;
317         // After that - query ID.
318         final int query_id = handshake.getInt();
319         // And finally, query type (which must be ProtocolConstants.QUERY_HANDSHAKE for
320         // handshake query)
321         final int query_type = handshake.getInt();
322         assert query_type == ProtocolConstants.QUERY_HANDSHAKE;
323         // Verify that received is a query.
324         if (msg_type != ProtocolConstants.PACKET_TYPE_QUERY) {
325             // Message type is not a query. Lets read and discard the remainder
326             // of the message.
327             if (remains > 0) {
328                 Loge("Unexpected handshake message type: " + msg_type);
329                 byte[] discard = new byte[remains];
330                 Socket.receive(sock, discard, discard.length);
331             }
332             return;
333         }
334 
335         // Receive query data.
336         final byte[] name_array = new byte[remains];
337         Socket.receive(sock, name_array, name_array.length);
338 
339         // Prepare response header.
340         handshake.position(0);
341         handshake.putInt(ProtocolConstants.PACKET_SIGNATURE);
342         // Handshake reply is just one int.
343         handshake.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + 4);
344         handshake.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
345         handshake.putInt(query_id);
346 
347         // Verify that received query is in deed a handshake query.
348         if (query_type != ProtocolConstants.QUERY_HANDSHAKE) {
349             // Query is not a handshake. Reply with failure.
350             Loge("Unexpected handshake query type: " + query_type);
351             handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_QUERY_UNKNOWN);
352             sock.getOutputStream().write(handshake.array());
353             return;
354         }
355 
356         // Handshake query data consist of SDK controller channel name.
357         final String channel_name = new String(name_array);
358         if (DEBUG) Log.d(TAG, "Handshake received for channel " + channel_name);
359 
360         // Respond to query depending on service-side channel availability
361         final Channel channel = getChannel(channel_name);
362         Socket sk = null;
363 
364         if (channel != null) {
365             if (channel.isConnected()) {
366                 // This is a duplicate connection.
367                 Loge("Duplicate connection to a connected Channel " + channel_name);
368                 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
369             } else {
370                 // Connecting to a registered channel.
371                 if (DEBUG) Log.d(TAG, "Emulator is connected to a registered Channel " + channel_name);
372                 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_CONNECTED);
373             }
374         } else {
375             // Make sure that there are no other channel connections for this
376             // channel name.
377             if (getPendingSocket(channel_name) != null) {
378                 // This is a duplicate.
379                 Loge("Duplicate connection to a pending Socket " + channel_name);
380                 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
381             } else {
382                 // Connecting to a channel that has not been registered yet.
383                 if (DEBUG) Log.d(TAG, "Emulator is connected to a pending Socket " + channel_name);
384                 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_NOPORT);
385                 sk = new Socket(sock, channel_name, endian);
386                 mPendingSockets.add(sk);
387             }
388         }
389 
390         // Send handshake reply.
391         sock.getOutputStream().write(handshake.array());
392 
393         // If a disconnected channel for emulator connection has been found,
394         // connect it.
395         if (channel != null && !channel.isConnected()) {
396             if (DEBUG) Log.d(TAG, "Connecting Channel " + channel_name + " with emulator.");
397             sk = new Socket(sock, channel_name, endian);
398             channel.connect(sk);
399         }
400 
401         mService.notifyStatusChanged();
402     }
403 
404     /***************************************************************************
405      * Logging wrappers
406      **************************************************************************/
407 
Loge(String log)408     private void Loge(String log) {
409         mService.addError(log);
410         Log.e(TAG, log);
411     }
412 }
413