Merge "init: separate out epoll into a class"
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 31cb853..44ed3a2 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -117,6 +117,7 @@
     atexit(adb_server_cleanup);
 
     init_transport_registration();
+    init_reconnect_handler();
     init_mdns_transport_discovery();
 
     usb_init();
diff --git a/adb/test_adb.py b/adb/test_adb.py
index 32bf029..ddd3ff0 100644
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -36,10 +36,11 @@
 
 
 @contextlib.contextmanager
-def fake_adb_server(protocol=socket.AF_INET, port=0):
-    """Creates a fake ADB server that just replies with a CNXN packet."""
+def fake_adbd(protocol=socket.AF_INET, port=0):
+    """Creates a fake ADB daemon that just replies with a CNXN packet."""
 
     serversock = socket.socket(protocol, socket.SOCK_STREAM)
+    serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
     if protocol == socket.AF_INET:
         serversock.bind(('127.0.0.1', port))
     else:
@@ -60,31 +61,33 @@
         rlist = [readpipe, serversock]
         cnxn_sent = {}
         while True:
-            ready, _, _ = select.select(rlist, [], [])
-            for r in ready:
-                if r == readpipe:
+            read_ready, _, _ = select.select(rlist, [], [])
+            for ready in read_ready:
+                if ready == readpipe:
                     # Closure pipe
-                    os.close(r)
+                    os.close(ready)
                     serversock.shutdown(socket.SHUT_RDWR)
                     serversock.close()
                     return
-                elif r == serversock:
+                elif ready == serversock:
                     # Server socket
-                    conn, _ = r.accept()
+                    conn, _ = ready.accept()
                     rlist.append(conn)
                 else:
                     # Client socket
-                    data = r.recv(1024)
-                    if not data:
-                        if r in cnxn_sent:
-                            del cnxn_sent[r]
-                        rlist.remove(r)
+                    data = ready.recv(1024)
+                    if not data or data.startswith('OPEN'):
+                        if ready in cnxn_sent:
+                            del cnxn_sent[ready]
+                        ready.shutdown(socket.SHUT_RDWR)
+                        ready.close()
+                        rlist.remove(ready)
                         continue
-                    if r in cnxn_sent:
+                    if ready in cnxn_sent:
                         continue
-                    cnxn_sent[r] = True
-                    r.sendall(_adb_packet('CNXN', 0x01000001, 1024 * 1024,
-                                          'device::ro.product.name=fakeadb'))
+                    cnxn_sent[ready] = True
+                    ready.sendall(_adb_packet('CNXN', 0x01000001, 1024 * 1024,
+                                              'device::ro.product.name=fakeadb'))
 
     port = serversock.getsockname()[1]
     server_thread = threading.Thread(target=_handle)
@@ -97,8 +100,52 @@
         server_thread.join()
 
 
-class NonApiTest(unittest.TestCase):
-    """Tests for ADB that aren't a part of the AndroidDevice API."""
+@contextlib.contextmanager
+def adb_connect(unittest, serial):
+    """Context manager for an ADB connection.
+
+    This automatically disconnects when done with the connection.
+    """
+
+    output = subprocess.check_output(['adb', 'connect', serial])
+    unittest.assertEqual(output.strip(), 'connected to {}'.format(serial))
+
+    try:
+        yield
+    finally:
+        # Perform best-effort disconnection. Discard the output.
+        subprocess.Popen(['adb', 'disconnect', serial],
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE).communicate()
+
+
+@contextlib.contextmanager
+def adb_server():
+    """Context manager for an ADB server.
+
+    This creates an ADB server and returns the port it's listening on.
+    """
+
+    port = 5038
+    # Kill any existing server on this non-default port.
+    subprocess.check_output(['adb', '-P', str(port), 'kill-server'],
+                            stderr=subprocess.STDOUT)
+    read_pipe, write_pipe = os.pipe()
+    proc = subprocess.Popen(['adb', '-L', 'tcp:localhost:{}'.format(port),
+                             'fork-server', 'server',
+                             '--reply-fd', str(write_pipe)])
+    try:
+        os.close(write_pipe)
+        greeting = os.read(read_pipe, 1024)
+        assert greeting == 'OK\n', repr(greeting)
+        yield port
+    finally:
+        proc.terminate()
+        proc.wait()
+
+
+class CommandlineTest(unittest.TestCase):
+    """Tests for the ADB commandline."""
 
     def test_help(self):
         """Make sure we get _something_ out of help."""
@@ -120,28 +167,37 @@
                 revision_line, r'^Revision [0-9a-f]{12}-android$')
 
     def test_tcpip_error_messages(self):
-        p = subprocess.Popen(['adb', 'tcpip'], stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT)
-        out, _ = p.communicate()
-        self.assertEqual(1, p.returncode)
+        """Make sure 'adb tcpip' parsing is sane."""
+        proc = subprocess.Popen(['adb', 'tcpip'], stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        out, _ = proc.communicate()
+        self.assertEqual(1, proc.returncode)
         self.assertIn('requires an argument', out)
 
-        p = subprocess.Popen(['adb', 'tcpip', 'foo'], stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT)
-        out, _ = p.communicate()
-        self.assertEqual(1, p.returncode)
+        proc = subprocess.Popen(['adb', 'tcpip', 'foo'], stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        out, _ = proc.communicate()
+        self.assertEqual(1, proc.returncode)
         self.assertIn('invalid port', out)
 
-    # Helper method that reads a pipe until it is closed, then sets the event.
-    def _read_pipe_and_set_event(self, pipe, event):
-        x = pipe.read()
+
+class ServerTest(unittest.TestCase):
+    """Tests for the ADB server."""
+
+    @staticmethod
+    def _read_pipe_and_set_event(pipe, event):
+        """Reads a pipe until it is closed, then sets the event."""
+        pipe.read()
         event.set()
 
-    # Test that launch_server() does not let the adb server inherit
-    # stdin/stdout/stderr handles which can cause callers of adb.exe to hang.
-    # This test also runs fine on unix even though the impetus is an issue
-    # unique to Windows.
     def test_handle_inheritance(self):
+        """Test that launch_server() does not inherit handles.
+
+        launch_server() should not let the adb server inherit
+        stdin/stdout/stderr handles, which can cause callers of adb.exe to hang.
+        This test also runs fine on unix even though the impetus is an issue
+        unique to Windows.
+        """
         # This test takes 5 seconds to run on Windows: if there is no adb server
         # running on the the port used below, adb kill-server tries to make a
         # TCP connection to a closed port and that takes 1 second on Windows;
@@ -163,29 +219,30 @@
 
         try:
             # Run the adb client and have it start the adb server.
-            p = subprocess.Popen(['adb', '-P', str(port), 'start-server'],
-                                 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
+            proc = subprocess.Popen(['adb', '-P', str(port), 'start-server'],
+                                    stdin=subprocess.PIPE,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
 
             # Start threads that set events when stdout/stderr are closed.
             stdout_event = threading.Event()
             stdout_thread = threading.Thread(
-                    target=self._read_pipe_and_set_event,
-                    args=(p.stdout, stdout_event))
+                target=ServerTest._read_pipe_and_set_event,
+                args=(proc.stdout, stdout_event))
             stdout_thread.daemon = True
             stdout_thread.start()
 
             stderr_event = threading.Event()
             stderr_thread = threading.Thread(
-                    target=self._read_pipe_and_set_event,
-                    args=(p.stderr, stderr_event))
+                target=ServerTest._read_pipe_and_set_event,
+                args=(proc.stderr, stderr_event))
             stderr_thread.daemon = True
             stderr_thread.start()
 
             # Wait for the adb client to finish. Once that has occurred, if
             # stdin/stderr/stdout are still open, it must be open in the adb
             # server.
-            p.wait()
+            proc.wait()
 
             # Try to write to stdin which we expect is closed. If it isn't
             # closed, we should get an IOError. If we don't get an IOError,
@@ -193,7 +250,7 @@
             # probably letting the adb server inherit stdin which would be
             # wrong.
             with self.assertRaises(IOError):
-                p.stdin.write('x')
+                proc.stdin.write('x')
 
             # Wait a few seconds for stdout/stderr to be closed (in the success
             # case, this won't wait at all). If there is a timeout, that means
@@ -207,8 +264,12 @@
             subprocess.check_output(['adb', '-P', str(port), 'kill-server'],
                                     stderr=subprocess.STDOUT)
 
-    # Use SO_LINGER to cause TCP RST segment to be sent on socket close.
+
+class EmulatorTest(unittest.TestCase):
+    """Tests for the emulator connection."""
+
     def _reset_socket_on_close(self, sock):
+        """Use SO_LINGER to cause TCP RST segment to be sent on socket close."""
         # The linger structure is two shorts on Windows, but two ints on Unix.
         linger_format = 'hh' if os.name == 'nt' else 'ii'
         l_onoff = 1
@@ -227,7 +288,7 @@
         Bug: https://code.google.com/p/android/issues/detail?id=21021
         """
         with contextlib.closing(
-                socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as listener:
+            socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as listener:
             # Use SO_REUSEADDR so subsequent runs of the test can grab the port
             # even if it is in TIME_WAIT.
             listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -237,7 +298,7 @@
 
             # Now that listening has started, start adb emu kill, telling it to
             # connect to our mock emulator.
-            p = subprocess.Popen(
+            proc = subprocess.Popen(
                 ['adb', '-s', 'emulator-' + str(port), 'emu', 'kill'],
                 stderr=subprocess.STDOUT)
 
@@ -246,12 +307,16 @@
                 # If WSAECONNABORTED (10053) is raised by any socket calls,
                 # then adb probably isn't reading the data that we sent it.
                 conn.sendall('Android Console: type \'help\' for a list ' +
-                                'of commands\r\n')
+                             'of commands\r\n')
                 conn.sendall('OK\r\n')
 
-                with contextlib.closing(conn.makefile()) as f:
-                    self.assertEqual('kill\n', f.readline())
-                    self.assertEqual('quit\n', f.readline())
+                with contextlib.closing(conn.makefile()) as connf:
+                    line = connf.readline()
+                    if line.startswith('auth'):
+                        # Ignore the first auth line.
+                        line = connf.readline()
+                    self.assertEqual('kill\n', line)
+                    self.assertEqual('quit\n', connf.readline())
 
                 conn.sendall('OK: killing emulator, bye bye\r\n')
 
@@ -264,11 +329,48 @@
                 self._reset_socket_on_close(conn)
 
             # Wait for adb to finish, so we can check return code.
-            p.communicate()
+            proc.communicate()
 
             # If this fails, adb probably isn't ignoring WSAECONNRESET when
             # reading the response from the adb emu kill command (on Windows).
-            self.assertEqual(0, p.returncode)
+            self.assertEqual(0, proc.returncode)
+
+    def test_emulator_connect(self):
+        """Ensure that the emulator can connect.
+
+        Bug: http://b/78991667
+        """
+        with adb_server() as server_port:
+            with fake_adbd() as port:
+                serial = 'emulator-{}'.format(port - 1)
+                # Ensure that the emulator is not there.
+                try:
+                    subprocess.check_output(['adb', '-P', str(server_port),
+                                             '-s', serial, 'get-state'],
+                                            stderr=subprocess.STDOUT)
+                    self.fail('Device should not be available')
+                except subprocess.CalledProcessError as err:
+                    self.assertEqual(
+                        err.output.strip(),
+                        'error: device \'{}\' not found'.format(serial))
+
+                # Let the ADB server know that the emulator has started.
+                with contextlib.closing(
+                    socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
+                    sock.connect(('localhost', server_port))
+                    command = 'host:emulator:{}'.format(port)
+                    sock.sendall('%04x%s' % (len(command), command))
+
+                # Ensure the emulator is there.
+                subprocess.check_call(['adb', '-P', str(server_port),
+                                       '-s', serial, 'wait-for-device'])
+                output = subprocess.check_output(['adb', '-P', str(server_port),
+                                                  '-s', serial, 'get-state'])
+                self.assertEqual(output.strip(), 'device')
+
+
+class ConnectionTest(unittest.TestCase):
+    """Tests for adb connect."""
 
     def test_connect_ipv4_ipv6(self):
         """Ensure that `adb connect localhost:1234` will try both IPv4 and IPv6.
@@ -277,38 +379,67 @@
         """
         for protocol in (socket.AF_INET, socket.AF_INET6):
             try:
-                with fake_adb_server(protocol=protocol) as port:
-                    output = subprocess.check_output(
-                        ['adb', 'connect', 'localhost:{}'.format(port)])
-
-                    self.assertEqual(
-                        output.strip(), 'connected to localhost:{}'.format(port))
+                with fake_adbd(protocol=protocol) as port:
+                    serial = 'localhost:{}'.format(port)
+                    with adb_connect(self, serial):
+                        pass
             except socket.error:
                 print("IPv6 not available, skipping")
                 continue
 
     def test_already_connected(self):
-        with fake_adb_server() as port:
-            output = subprocess.check_output(
-                ['adb', 'connect', 'localhost:{}'.format(port)])
+        """Ensure that an already-connected device stays connected."""
 
-            self.assertEqual(
-                output.strip(), 'connected to localhost:{}'.format(port))
+        with fake_adbd() as port:
+            serial = 'localhost:{}'.format(port)
+            with adb_connect(self, serial):
+                # b/31250450: this always returns 0 but probably shouldn't.
+                output = subprocess.check_output(['adb', 'connect', serial])
+                self.assertEqual(
+                    output.strip(), 'already connected to {}'.format(serial))
 
-            # b/31250450: this always returns 0 but probably shouldn't.
-            output = subprocess.check_output(
-                ['adb', 'connect', 'localhost:{}'.format(port)])
+    def test_reconnect(self):
+        """Ensure that a disconnected device reconnects."""
 
-            self.assertEqual(
-                output.strip(), 'already connected to localhost:{}'.format(port))
+        with fake_adbd() as port:
+            serial = 'localhost:{}'.format(port)
+            with adb_connect(self, serial):
+                output = subprocess.check_output(['adb', '-s', serial,
+                                                  'get-state'])
+                self.assertEqual(output.strip(), 'device')
+
+                # This will fail.
+                proc = subprocess.Popen(['adb', '-s', serial, 'shell', 'true'],
+                                        stdout=subprocess.PIPE,
+                                        stderr=subprocess.STDOUT)
+                output, _ = proc.communicate()
+                self.assertEqual(output.strip(), 'error: closed')
+
+                subprocess.check_call(['adb', '-s', serial, 'wait-for-device'])
+
+                output = subprocess.check_output(['adb', '-s', serial,
+                                                  'get-state'])
+                self.assertEqual(output.strip(), 'device')
+
+                # Once we explicitly kick a device, it won't attempt to
+                # reconnect.
+                output = subprocess.check_output(['adb', 'disconnect', serial])
+                self.assertEqual(
+                    output.strip(), 'disconnected {}'.format(serial))
+                try:
+                    subprocess.check_output(['adb', '-s', serial, 'get-state'],
+                                            stderr=subprocess.STDOUT)
+                    self.fail('Device should not be available')
+                except subprocess.CalledProcessError as err:
+                    self.assertEqual(
+                        err.output.strip(),
+                        'error: device \'{}\' not found'.format(serial))
+
 
 def main():
+    """Main entrypoint."""
     random.seed(0)
-    if len(adb.get_devices()) > 0:
-        suite = unittest.TestLoader().loadTestsFromName(__name__)
-        unittest.TextTestRunner(verbosity=3).run(suite)
-    else:
-        print('Test suite must be run with attached devices')
+    unittest.main(verbosity=3)
 
 
 if __name__ == '__main__':
diff --git a/adb/transport.cpp b/adb/transport.cpp
index be7f8fe..beec13a 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -33,6 +33,7 @@
 #include <deque>
 #include <list>
 #include <mutex>
+#include <queue>
 #include <thread>
 
 #include <android-base/logging.h>
@@ -50,7 +51,9 @@
 #include "adb_utils.h"
 #include "fdevent.h"
 
-static void transport_unref(atransport *t);
+static void register_transport(atransport* transport);
+static void remove_transport(atransport* transport);
+static void transport_unref(atransport* transport);
 
 // TODO: unordered_map<TransportId, atransport*>
 static auto& transport_list = *new std::list<atransport*>();
@@ -77,6 +80,130 @@
     ~ScopedAssumeLocked() RELEASE() {}
 };
 
+// Tracks and handles atransport*s that are attempting reconnection.
+class ReconnectHandler {
+  public:
+    ReconnectHandler() = default;
+    ~ReconnectHandler() = default;
+
+    // Starts the ReconnectHandler thread.
+    void Start();
+
+    // Requests the ReconnectHandler thread to stop.
+    void Stop();
+
+    // Adds the atransport* to the queue of reconnect attempts.
+    void TrackTransport(atransport* transport);
+
+  private:
+    // The main thread loop.
+    void Run();
+
+    // Tracks a reconnection attempt.
+    struct ReconnectAttempt {
+        atransport* transport;
+        std::chrono::system_clock::time_point deadline;
+        size_t attempts_left;
+    };
+
+    // Only retry for up to one minute.
+    static constexpr const std::chrono::seconds kDefaultTimeout = std::chrono::seconds(10);
+    static constexpr const size_t kMaxAttempts = 6;
+
+    // Protects all members.
+    std::mutex reconnect_mutex_;
+    bool running_ GUARDED_BY(reconnect_mutex_) = true;
+    std::thread handler_thread_;
+    std::condition_variable reconnect_cv_;
+    std::queue<ReconnectAttempt> reconnect_queue_ GUARDED_BY(reconnect_mutex_);
+
+    DISALLOW_COPY_AND_ASSIGN(ReconnectHandler);
+};
+
+void ReconnectHandler::Start() {
+    check_main_thread();
+    handler_thread_ = std::thread(&ReconnectHandler::Run, this);
+}
+
+void ReconnectHandler::Stop() {
+    check_main_thread();
+    {
+        std::lock_guard<std::mutex> lock(reconnect_mutex_);
+        running_ = false;
+    }
+    reconnect_cv_.notify_one();
+    handler_thread_.join();
+
+    // Drain the queue to free all resources.
+    std::lock_guard<std::mutex> lock(reconnect_mutex_);
+    while (!reconnect_queue_.empty()) {
+        ReconnectAttempt attempt = reconnect_queue_.front();
+        reconnect_queue_.pop();
+        remove_transport(attempt.transport);
+    }
+}
+
+void ReconnectHandler::TrackTransport(atransport* transport) {
+    check_main_thread();
+    {
+        std::lock_guard<std::mutex> lock(reconnect_mutex_);
+        if (!running_) return;
+        reconnect_queue_.emplace(ReconnectAttempt{
+            transport, std::chrono::system_clock::now() + ReconnectHandler::kDefaultTimeout,
+            ReconnectHandler::kMaxAttempts});
+    }
+    reconnect_cv_.notify_one();
+}
+
+void ReconnectHandler::Run() {
+    while (true) {
+        ReconnectAttempt attempt;
+        {
+            std::unique_lock<std::mutex> lock(reconnect_mutex_);
+            ScopedAssumeLocked assume_lock(reconnect_mutex_);
+
+            auto deadline = std::chrono::time_point<std::chrono::system_clock>::max();
+            if (!reconnect_queue_.empty()) deadline = reconnect_queue_.front().deadline;
+            reconnect_cv_.wait_until(lock, deadline, [&]() REQUIRES(reconnect_mutex_) {
+                return !running_ ||
+                       (!reconnect_queue_.empty() && reconnect_queue_.front().deadline < deadline);
+            });
+
+            if (!running_) return;
+            attempt = reconnect_queue_.front();
+            reconnect_queue_.pop();
+            if (attempt.transport->kicked()) {
+                D("transport %s was kicked. giving up on it.", attempt.transport->serial);
+                remove_transport(attempt.transport);
+                continue;
+            }
+        }
+        D("attempting to reconnect %s", attempt.transport->serial);
+
+        if (!attempt.transport->Reconnect()) {
+            D("attempting to reconnect %s failed.", attempt.transport->serial);
+            if (attempt.attempts_left == 0) {
+                D("transport %s exceeded the number of retry attempts. giving up on it.",
+                  attempt.transport->serial);
+                remove_transport(attempt.transport);
+                continue;
+            }
+
+            std::lock_guard<std::mutex> lock(reconnect_mutex_);
+            reconnect_queue_.emplace(ReconnectAttempt{
+                attempt.transport,
+                std::chrono::system_clock::now() + ReconnectHandler::kDefaultTimeout,
+                attempt.attempts_left - 1});
+            continue;
+        }
+
+        D("reconnection to %s succeeded.", attempt.transport->serial);
+        register_transport(attempt.transport);
+    }
+}
+
+static auto& reconnect_handler = *new ReconnectHandler();
+
 }  // namespace
 
 TransportId NextTransportId() {
@@ -477,8 +604,6 @@
     return 0;
 }
 
-static void remove_transport(atransport*);
-
 static void transport_registration_func(int _fd, unsigned ev, void*) {
     tmsg m;
     atransport* t;
@@ -515,8 +640,9 @@
 
     /* don't create transport threads for inaccessible devices */
     if (t->GetConnectionState() != kCsNoPerm) {
-        /* initial references are the two threads */
-        t->ref_count = 1;
+        // The connection gets a reference to the atransport. It will release it
+        // upon a read/write error.
+        t->ref_count++;
         t->connection()->SetTransportName(t->serial_name());
         t->connection()->SetReadCallback([t](Connection*, std::unique_ptr<apacket> p) {
             if (!check_header(p.get(), t)) {
@@ -547,13 +673,20 @@
 
     {
         std::lock_guard<std::recursive_mutex> lock(transport_lock);
-        pending_list.remove(t);
-        transport_list.push_front(t);
+        auto it = std::find(pending_list.begin(), pending_list.end(), t);
+        if (it != pending_list.end()) {
+            pending_list.remove(t);
+            transport_list.push_front(t);
+        }
     }
 
     update_transports();
 }
 
+void init_reconnect_handler(void) {
+    reconnect_handler.Start();
+}
+
 void init_transport_registration(void) {
     int s[2];
 
@@ -572,6 +705,7 @@
 }
 
 void kick_all_transports() {
+    reconnect_handler.Stop();
     // To avoid only writing part of a packet to a transport after exit, kick all transports.
     std::lock_guard<std::recursive_mutex> lock(transport_lock);
     for (auto t : transport_list) {
@@ -601,15 +735,21 @@
 }
 
 static void transport_unref(atransport* t) {
+    check_main_thread();
     CHECK(t != nullptr);
 
     std::lock_guard<std::recursive_mutex> lock(transport_lock);
     CHECK_GT(t->ref_count, 0u);
     t->ref_count--;
     if (t->ref_count == 0) {
-        D("transport: %s unref (kicking and closing)", t->serial);
         t->connection()->Stop();
-        remove_transport(t);
+        if (t->IsTcpDevice() && !t->kicked()) {
+            D("transport: %s unref (attempting reconnection) %d", t->serial, t->kicked());
+            reconnect_handler.TrackTransport(t);
+        } else {
+            D("transport: %s unref (kicking and closing)", t->serial);
+            remove_transport(t);
+        }
     } else {
         D("transport: %s unref (count=%zu)", t->serial, t->ref_count);
     }
@@ -781,9 +921,8 @@
 }
 
 void atransport::Kick() {
-    if (!kicked_) {
-        D("kicking transport %s", this->serial);
-        kicked_ = true;
+    if (!kicked_.exchange(true)) {
+        D("kicking transport %p %s", this, this->serial);
         this->connection()->Stop();
     }
 }
@@ -941,6 +1080,10 @@
     connection_waitable_->SetConnectionEstablished(success);
 }
 
+bool atransport::Reconnect() {
+    return reconnect_(this);
+}
+
 #if ADB_HOST
 
 // We use newline as our delimiter, make sure to never output it.
@@ -1021,8 +1164,9 @@
 }
 #endif  // ADB_HOST
 
-int register_socket_transport(int s, const char* serial, int port, int local) {
-    atransport* t = new atransport();
+int register_socket_transport(int s, const char* serial, int port, int local,
+                              atransport::ReconnectCallback reconnect) {
+    atransport* t = new atransport(std::move(reconnect), kCsOffline);
 
     if (!serial) {
         char buf[32];
@@ -1103,7 +1247,7 @@
 
 void register_usb_transport(usb_handle* usb, const char* serial, const char* devpath,
                             unsigned writeable) {
-    atransport* t = new atransport((writeable ? kCsConnecting : kCsNoPerm));
+    atransport* t = new atransport(writeable ? kCsOffline : kCsNoPerm);
 
     D("transport: %p init'ing for usb_handle %p (sn='%s')", t, usb, serial ? serial : "");
     init_usb_transport(t, usb);
diff --git a/adb/transport.h b/adb/transport.h
index e1cbc09..ae9cc02 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -198,20 +198,27 @@
     // class in one go is a very large change. Given how bad our testing is,
     // it's better to do this piece by piece.
 
-    atransport(ConnectionState state = kCsConnecting)
+    using ReconnectCallback = std::function<bool(atransport*)>;
+
+    atransport(ReconnectCallback reconnect, ConnectionState state)
         : id(NextTransportId()),
+          kicked_(false),
           connection_state_(state),
           connection_waitable_(std::make_shared<ConnectionWaitable>()),
-          connection_(nullptr) {
+          connection_(nullptr),
+          reconnect_(std::move(reconnect)) {
         // Initialize protocol to min version for compatibility with older versions.
         // Version will be updated post-connect.
         protocol_version = A_VERSION_MIN;
         max_payload = MAX_PAYLOAD;
     }
+    atransport(ConnectionState state = kCsOffline)
+        : atransport([](atransport*) { return false; }, state) {}
     virtual ~atransport();
 
     int Write(apacket* p);
     void Kick();
+    bool kicked() const { return kicked_; }
 
     // ConnectionState can be read by all threads, but can only be written in the main thread.
     ConnectionState GetConnectionState() const;
@@ -286,8 +293,12 @@
     // Gets a shared reference to the ConnectionWaitable.
     std::shared_ptr<ConnectionWaitable> connection_waitable() { return connection_waitable_; }
 
+    // Attempts to reconnect with the underlying Connection. Returns true if the
+    // reconnection attempt succeeded.
+    bool Reconnect();
+
   private:
-    bool kicked_ = false;
+    std::atomic<bool> kicked_;
 
     // A set of features transmitted in the banner with the initial connection.
     // This is stored in the banner as 'features=feature0,feature1,etc'.
@@ -310,6 +321,9 @@
     // The underlying connection object.
     std::shared_ptr<Connection> connection_ GUARDED_BY(mutex_);
 
+    // A callback that will be invoked when the atransport needs to reconnect.
+    ReconnectCallback reconnect_;
+
     std::mutex mutex_;
 
     DISALLOW_COPY_AND_ASSIGN(atransport);
@@ -333,6 +347,7 @@
 // Stops iteration and returns false if fn returns false, otherwise returns true.
 bool iterate_transports(std::function<bool(const atransport*)> fn);
 
+void init_reconnect_handler(void);
 void init_transport_registration(void);
 void init_mdns_transport_discovery(void);
 std::string list_transports(bool long_listing);
@@ -347,7 +362,8 @@
 void connect_device(const std::string& address, std::string* response);
 
 /* cause new transports to be init'd and added to the list */
-int register_socket_transport(int s, const char* serial, int port, int local);
+int register_socket_transport(int s, const char* serial, int port, int local,
+                              atransport::ReconnectCallback reconnect);
 
 // This should only be used for transports with connection_state == kCsNoPerm.
 void unregister_usb_transport(usb_handle* usb);
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index e81f27c..181d666 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -68,28 +68,24 @@
     return local_connect_arbitrary_ports(port - 1, port, &dummy) == 0;
 }
 
-void connect_device(const std::string& address, std::string* response) {
-    if (address.empty()) {
-        *response = "empty address";
-        return;
-    }
-
+std::tuple<unique_fd, int, std::string> tcp_connect(const std::string& address,
+                                                    std::string* response) {
     std::string serial;
     std::string host;
     int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT;
     if (!android::base::ParseNetAddress(address, &host, &port, &serial, response)) {
-        return;
+        return std::make_tuple(unique_fd(), port, serial);
     }
 
     std::string error;
-    int fd = network_connect(host.c_str(), port, SOCK_STREAM, 10, &error);
+    unique_fd fd(network_connect(host.c_str(), port, SOCK_STREAM, 10, &error));
     if (fd == -1) {
         *response = android::base::StringPrintf("unable to connect to %s: %s",
                                                 serial.c_str(), error.c_str());
-        return;
+        return std::make_tuple(std::move(fd), port, serial);
     }
 
-    D("client: connected %s remote on fd %d", serial.c_str(), fd);
+    D("client: connected %s remote on fd %d", serial.c_str(), fd.get());
     close_on_exec(fd);
     disable_tcp_nagle(fd);
 
@@ -98,7 +94,38 @@
         D("warning: failed to configure TCP keepalives (%s)", strerror(errno));
     }
 
-    int ret = register_socket_transport(fd, serial.c_str(), port, 0);
+    return std::make_tuple(std::move(fd), port, serial);
+}
+
+void connect_device(const std::string& address, std::string* response) {
+    if (address.empty()) {
+        *response = "empty address";
+        return;
+    }
+
+    unique_fd fd;
+    int port;
+    std::string serial;
+    std::tie(fd, port, serial) = tcp_connect(address, response);
+    auto reconnect = [address](atransport* t) {
+        std::string response;
+        unique_fd fd;
+        int port;
+        std::string serial;
+        std::tie(fd, port, serial) = tcp_connect(address, &response);
+        if (fd == -1) {
+            D("reconnect failed: %s", response.c_str());
+            return false;
+        }
+
+        // This invokes the part of register_socket_transport() that needs to be
+        // invoked if the atransport* has already been setup. This eventually
+        // calls atransport->SetConnection() with a newly created Connection*
+        // that will in turn send the CNXN packet.
+        return init_socket_transport(t, fd.release(), port, 0) >= 0;
+    };
+
+    int ret = register_socket_transport(fd.release(), serial.c_str(), port, 0, std::move(reconnect));
     if (ret < 0) {
         adb_close(fd);
         if (ret == -EALREADY) {
@@ -135,7 +162,8 @@
         close_on_exec(fd);
         disable_tcp_nagle(fd);
         std::string serial = getEmulatorSerialString(console_port);
-        if (register_socket_transport(fd, serial.c_str(), adb_port, 1) == 0) {
+        if (register_socket_transport(fd, serial.c_str(), adb_port, 1,
+                                      [](atransport*) { return false; }) == 0) {
             return 0;
         }
         adb_close(fd);
@@ -239,7 +267,8 @@
             close_on_exec(fd);
             disable_tcp_nagle(fd);
             std::string serial = android::base::StringPrintf("host-%d", fd);
-            if (register_socket_transport(fd, serial.c_str(), port, 1) != 0) {
+            if (register_socket_transport(fd, serial.c_str(), port, 1,
+                                          [](atransport*) { return false; }) != 0) {
                 adb_close(fd);
             }
         }
@@ -338,7 +367,8 @@
                 /* Host is connected. Register the transport, and start the
                  * exchange. */
                 std::string serial = android::base::StringPrintf("host-%d", fd);
-                if (register_socket_transport(fd, serial.c_str(), port, 1) != 0 ||
+                if (register_socket_transport(fd, serial.c_str(), port, 1,
+                                              [](atransport*) { return false; }) != 0 ||
                     !WriteFdExactly(fd, _start_req, strlen(_start_req))) {
                     adb_close(fd);
                 }
diff --git a/base/Android.bp b/base/Android.bp
index ec81f61..47b29c6 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -26,6 +26,7 @@
 cc_library_headers {
     name: "libbase_headers",
     vendor_available: true,
+    recovery_available: true,
     host_supported: true,
     export_include_dirs: ["include"],
 
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index 4987ba1..0f93dd0 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -51,6 +51,7 @@
 cc_library_headers {
     name: "libbacktrace_headers",
     vendor_available: true,
+    recovery_available: true,
     export_include_dirs: ["include"],
 }
 
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index e823257..cdbb65f 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -32,6 +32,7 @@
 cc_library_headers {
     name: "libcutils_headers",
     vendor_available: true,
+    recovery_available: true,
     host_supported: true,
     export_include_dirs: ["include"],
     target: {
@@ -54,6 +55,7 @@
         enabled: true,
         support_system_process: true,
     },
+    recovery_available: true,
     host_supported: true,
     srcs: [
         "config_utils.cpp",
diff --git a/liblog/Android.bp b/liblog/Android.bp
index 7d9e306..2d5a5db 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -46,6 +46,7 @@
     name: "liblog_headers",
     host_supported: true,
     vendor_available: true,
+    recovery_available: true,
     export_include_dirs: ["include"],
     target: {
         windows: {
@@ -65,7 +66,7 @@
 cc_library {
     name: "liblog",
     host_supported: true,
-
+    recovery_available: true,
     srcs: liblog_sources,
 
     target: {
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index 27693b3..c38594a 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,6 +1,7 @@
 cc_library {
 
     name: "libpackagelistparser",
+    recovery_available: true,
     srcs: ["packagelistparser.c"],
     cflags: [
         "-Wall",
diff --git a/libsystem/Android.bp b/libsystem/Android.bp
index 82bf1bc..2e22b43 100644
--- a/libsystem/Android.bp
+++ b/libsystem/Android.bp
@@ -1,6 +1,7 @@
 cc_library_headers {
     name: "libsystem_headers",
     vendor_available: true,
+    recovery_available: true,
     host_supported: true,
     export_include_dirs: ["include"],
 
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 0d7925a..9395ef8 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -15,6 +15,7 @@
 cc_library_headers {
     name: "libutils_headers",
     vendor_available: true,
+    recovery_available: true,
     host_supported: true,
 
     header_libs: [
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index ea9b968..70f6faa 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -2,6 +2,7 @@
     name: "libpropertyinfoparser",
     host_supported: true,
     vendor_available: true,
+    recovery_available: true,
     srcs: ["property_info_parser.cpp"],
 
     cpp_std: "experimental",
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index f488ed5..3c9e5f3 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -147,13 +147,10 @@
 bcp_md5 :=
 bcp_dep :=
 
-# If BOARD_VNDK_VERSION is defined, append PLATFORM_VNDK_VERSION to base name.
+# Append PLATFORM_VNDK_VERSION to base name.
 define append_vndk_version
 $(strip \
-  $(if $(BOARD_VNDK_VERSION), \
-    $(basename $(1)).$(PLATFORM_VNDK_VERSION)$(suffix $(1)), \
-    $(1) \
-  ) \
+  $(basename $(1)).$(PLATFORM_VNDK_VERSION)$(suffix $(1)) \
 )
 endef
 
@@ -215,31 +212,46 @@
 vndk_version_suffix :=
 endef # update_and_install_ld_config
 
+
+#######################################
+# ld.config.txt selection variables
+#
+_enforce_vndk_at_runtime := false
+ifdef BOARD_VNDK_VERSION
+  ifneq ($(BOARD_VNDK_RUNTIME_DISABLE),true)
+    _enforce_vndk_at_runtime := true
+  endif
+endif
+
+_enforce_vndk_lite_at_runtime := false
+ifeq ($(_enforce_vndk_at_runtime),false)
+  ifeq ($(PRODUCT_TREBLE_LINKER_NAMESPACES)|$(SANITIZE_TARGET),true|)
+    _enforce_vndk_lite_at_runtime := true
+  endif
+endif
+
 #######################################
 # ld.config.txt
 #
 # For VNDK enforced devices that have defined BOARD_VNDK_VERSION, use
 # "ld.config.txt" as a source file. This configuration includes strict VNDK
 # run-time restrictions for vendor process.
+#
 # Other treblized devices, that have not defined BOARD_VNDK_VERSION or that
 # have set BOARD_VNDK_RUNTIME_DISABLE to true, use "ld.config.vndk_lite.txt"
 # as a source file. This configuration does not have strict VNDK run-time
 # restrictions.
+#
 # If the device is not treblized, use "ld.config.legacy.txt" for legacy
 # namespace configuration.
+#
 include $(CLEAR_VARS)
 LOCAL_MODULE := ld.config.txt
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
 
-_enforce_vndk_at_runtime := false
-ifdef BOARD_VNDK_VERSION
-ifneq ($(BOARD_VNDK_RUNTIME_DISABLE),true)
-  _enforce_vndk_at_runtime := true
-endif
-endif
-
 ifeq ($(_enforce_vndk_at_runtime),true)
+
 # for VNDK enforced devices
 LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
 include $(BUILD_SYSTEM)/base_rules.mk
@@ -248,37 +260,36 @@
   $(LOCAL_BUILT_MODULE),\
   $(PLATFORM_VNDK_VERSION)))
 
-else ifeq ($(PRODUCT_TREBLE_LINKER_NAMESPACES)|$(SANITIZE_TARGET),true|)
-# for treblized but VNDK non-enforced devices
-LOCAL_MODULE_STEM := $(call append_vndk_version,$(LOCAL_MODULE))
+else ifeq ($(_enforce_vndk_lite_at_runtime),true)
+
+# for treblized but VNDK lightly enforced devices
+LOCAL_MODULE_STEM := ld.config.vndk_lite.txt
 include $(BUILD_SYSTEM)/base_rules.mk
 $(eval $(call update_and_install_ld_config,\
   $(LOCAL_PATH)/etc/ld.config.vndk_lite.txt,\
   $(LOCAL_BUILT_MODULE),\
-  $(if $(BOARD_VNDK_VERSION),$(PLATFORM_VNDK_VERSION)),\
+  $(PLATFORM_VNDK_VERSION),\
   true))
 
 else
+
 # for legacy non-treblized devices
-LOCAL_SRC_FILES := etc/ld.config.legacy.txt
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
+LOCAL_SRC_FILES := etc/ld.config.legacy.txt
 include $(BUILD_PREBUILT)
 
-endif # if _enforce_vndk_at_runtime is true
+endif  # ifeq ($(_enforce_vndk_at_runtime),true)
 
-_enforce_vndk_at_runtime :=
 
 #######################################
-# ld.config.noenforce.txt
+# ld.config.vndk_lite.txt
 #
-# This file is a temporary configuration file only for GSI. Originally GSI has
-# BOARD_VNDK_VERSION defined and has strict VNDK enforcing rule based on
-# "ld.config.txt". However for the devices, that have not defined
-# BOARD_VNDK_VERSION, GSI provides this configuration file which is based on
-# "ld.config.vndk_lite.txt".
-# Do not install this file for the devices other than GSI.
+# This module is only for GSI.
+#
+ifeq ($(_enforce_vndk_lite_at_runtime),false)
+
 include $(CLEAR_VARS)
-LOCAL_MODULE := ld.config.noenforce.txt
+LOCAL_MODULE := ld.config.vndk_lite.txt
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
@@ -289,6 +300,11 @@
   $(PLATFORM_VNDK_VERSION),\
   true))
 
+endif  # ifeq ($(_enforce_vndk_lite_at_runtime),false)
+
+_enforce_vndk_at_runtime :=
+_enforce_vndk_lite_at_runtime :=
+
 #######################################
 # llndk.libraries.txt
 include $(CLEAR_VARS)