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
17package com.android.tools.sdkcontroller.lib;
18
19import java.io.IOException;
20import java.nio.ByteBuffer;
21import java.nio.ByteOrder;
22import java.util.ArrayList;
23import java.util.List;
24
25import android.util.Log;
26import android.net.LocalServerSocket;
27import android.net.LocalSocket;
28
29import com.android.tools.sdkcontroller.lib.Channel;
30import 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 */
71public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
408    private void Loge(String log) {
409        mService.addError(log);
410        Log.e(TAG, log);
411    }
412}
413