Merge "logd: liblog: logcat: Add LOG_ID_SECURITY"
diff --git a/adb/adb_utils.cpp b/adb/adb_utils.cpp
index 3ed2a7d..b132118 100644
--- a/adb/adb_utils.cpp
+++ b/adb/adb_utils.cpp
@@ -30,12 +30,31 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "adb.h"
 #include "adb_trace.h"
 #include "sysdeps.h"
 
 ADB_MUTEX_DEFINE(basename_lock);
 ADB_MUTEX_DEFINE(dirname_lock);
 
+#if defined(_WIN32)
+constexpr char kNullFileName[] = "NUL";
+#else
+constexpr char kNullFileName[] = "/dev/null";
+#endif
+
+void close_stdin() {
+    int fd = unix_open(kNullFileName, O_RDONLY);
+    if (fd == -1) {
+        fatal_errno("failed to open %s", kNullFileName);
+    }
+
+    if (TEMP_FAILURE_RETRY(dup2(fd, STDIN_FILENO)) == -1) {
+        fatal_errno("failed to redirect stdin to %s", kNullFileName);
+    }
+    unix_close(fd);
+}
+
 bool getcwd(std::string* s) {
   char* cwd = getcwd(nullptr, 0);
   if (cwd != nullptr) *s = cwd;
diff --git a/adb/adb_utils.h b/adb/adb_utils.h
index 537d0e4..388d7dd 100644
--- a/adb/adb_utils.h
+++ b/adb/adb_utils.h
@@ -19,6 +19,8 @@
 
 #include <string>
 
+void close_stdin();
+
 bool getcwd(std::string* cwd);
 bool directory_exists(const std::string& path);
 
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 3ce5242..b37d04d 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -34,11 +34,10 @@
 #include "adb.h"
 #include "adb_auth.h"
 #include "adb_listeners.h"
+#include "adb_utils.h"
 #include "transport.h"
 
 #if defined(_WIN32)
-static const char kNullFileName[] = "NUL";
-
 static BOOL WINAPI ctrlc_handler(DWORD type) {
     // TODO: Consider trying to kill a starting up adb server (if we're in
     // launch_server) by calling GenerateConsoleCtrlEvent().
@@ -66,24 +65,11 @@
     return temp_path_utf8 + log_name;
 }
 #else
-static const char kNullFileName[] = "/dev/null";
-
 static std::string GetLogFilePath() {
     return std::string("/tmp/adb.log");
 }
 #endif
 
-static void close_stdin() {
-    int fd = unix_open(kNullFileName, O_RDONLY);
-    if (fd == -1) {
-        fatal("cannot open '%s': %s", kNullFileName, strerror(errno));
-    }
-    if (dup2(fd, STDIN_FILENO) == -1) {
-        fatal("cannot redirect stdin: %s", strerror(errno));
-    }
-    unix_close(fd);
-}
-
 static void setup_daemon_logging(void) {
     const std::string log_file_path(GetLogFilePath());
     int fd = unix_open(log_file_path.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0640);
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index d244f7d..8575c86 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -112,9 +112,10 @@
         "                                 (-a preserves file timestamp and mode)\n"
         "  adb sync [ <directory> ]     - copy host->device only if changed\n"
         "                                 (-l means list but don't copy)\n"
-        "  adb shell [-e escape] [-Tt] [-x] [command]\n"
+        "  adb shell [-e escape] [-n] [-Tt] [-x] [command]\n"
         "                               - run remote shell command (interactive shell if no command given)\n"
         "                                 (-e: choose escape character, or \"none\"; default '~')\n"
+        "                                 (-n: don't read from stdin)\n"
         "                                 (-T: disable PTY allocation)\n"
         "                                 (-t: force PTY allocation)\n"
         "                                 (-x: disable remote exit codes and stdout/stderr separation)\n"
@@ -733,6 +734,11 @@
             use_shell_protocol = false;
             --argc;
             ++argv;
+        } else if (!strcmp(argv[0], "-n")) {
+            close_stdin();
+
+            --argc;
+            ++argv;
         } else {
             break;
         }
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 10c5296..feea7a3 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -34,6 +34,7 @@
 #include "adb.h"
 #include "adb_auth.h"
 #include "adb_listeners.h"
+#include "adb_utils.h"
 #include "transport.h"
 
 static const char* root_seclabel = nullptr;
@@ -106,33 +107,7 @@
 #endif // ALLOW_ADBD_ROOT
 }
 
-int adbd_main(int server_port) {
-    umask(0);
-
-    signal(SIGPIPE, SIG_IGN);
-
-    init_transport_registration();
-
-    // We need to call this even if auth isn't enabled because the file
-    // descriptor will always be open.
-    adbd_cloexec_auth_socket();
-
-    if (ALLOW_ADBD_NO_AUTH && property_get_bool("ro.adb.secure", 0) == 0) {
-        auth_required = false;
-    }
-
-    adbd_auth_init();
-
-    // Our external storage path may be different than apps, since
-    // we aren't able to bind mount after dropping root.
-    const char* adb_external_storage = getenv("ADB_EXTERNAL_STORAGE");
-    if (adb_external_storage != nullptr) {
-        setenv("EXTERNAL_STORAGE", adb_external_storage, 1);
-    } else {
-        D("Warning: ADB_EXTERNAL_STORAGE is not set.  Leaving EXTERNAL_STORAGE"
-          " unchanged.\n");
-    }
-
+static void drop_privileges(int server_port) {
     // Add extra groups:
     // AID_ADB to access the USB driver
     // AID_LOG to read system logs (adb logcat)
@@ -180,6 +155,36 @@
                 << error;
         }
     }
+}
+
+int adbd_main(int server_port) {
+    umask(0);
+
+    signal(SIGPIPE, SIG_IGN);
+
+    init_transport_registration();
+
+    // We need to call this even if auth isn't enabled because the file
+    // descriptor will always be open.
+    adbd_cloexec_auth_socket();
+
+    if (ALLOW_ADBD_NO_AUTH && property_get_bool("ro.adb.secure", 0) == 0) {
+        auth_required = false;
+    }
+
+    adbd_auth_init();
+
+    // Our external storage path may be different than apps, since
+    // we aren't able to bind mount after dropping root.
+    const char* adb_external_storage = getenv("ADB_EXTERNAL_STORAGE");
+    if (adb_external_storage != nullptr) {
+        setenv("EXTERNAL_STORAGE", adb_external_storage, 1);
+    } else {
+        D("Warning: ADB_EXTERNAL_STORAGE is not set.  Leaving EXTERNAL_STORAGE"
+          " unchanged.\n");
+    }
+
+    drop_privileges(server_port);
 
     bool is_usb = false;
     if (access(USB_ADB_PATH, F_OK) == 0 || access(USB_FFS_ADB_EP0, F_OK) == 0) {
@@ -217,16 +222,6 @@
     return 0;
 }
 
-static void close_stdin() {
-    int fd = unix_open("/dev/null", O_RDONLY);
-    if (fd == -1) {
-        perror("failed to open /dev/null, stdin will remain open");
-        return;
-    }
-    dup2(fd, STDIN_FILENO);
-    unix_close(fd);
-}
-
 int main(int argc, char** argv) {
     while (true) {
         static struct option opts[] = {
diff --git a/adb/file_sync_client.cpp b/adb/file_sync_client.cpp
index 463c1c0..239aaf8 100644
--- a/adb/file_sync_client.cpp
+++ b/adb/file_sync_client.cpp
@@ -76,6 +76,8 @@
             ReadOrderlyShutdown(fd);
         }
         adb_close(fd);
+
+        line_printer_.KeepInfoLine();
     }
 
     bool IsValid() { return fd >= 0; }
@@ -243,8 +245,7 @@
     }
 
     void Print(const std::string& s) {
-        // TODO: we actually don't want ELIDE; we want "ELIDE if smart, FULL if dumb".
-        line_printer_.Print(s, LinePrinter::ELIDE);
+        line_printer_.Print(s, LinePrinter::INFO);
     }
 
     void Printf(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
@@ -265,7 +266,7 @@
         android::base::StringAppendV(&s, fmt, ap);
         va_end(ap);
 
-        line_printer_.Print(s, LinePrinter::FULL);
+        line_printer_.Print(s, LinePrinter::ERROR);
     }
 
     void Warning(const char* fmt, ...) __attribute__((__format__(ADB_FORMAT_ARCHETYPE, 2, 3))) {
@@ -276,7 +277,7 @@
         android::base::StringAppendV(&s, fmt, ap);
         va_end(ap);
 
-        line_printer_.Print(s, LinePrinter::FULL);
+        line_printer_.Print(s, LinePrinter::WARNING);
     }
 
     uint64_t total_bytes;
@@ -664,7 +665,7 @@
         }
     }
 
-    sc.Printf("%s: %d file%s pushed. %d file%s skipped.%s\n", rpath.c_str(),
+    sc.Printf("%s: %d file%s pushed. %d file%s skipped.%s", rpath.c_str(),
               pushed, (pushed == 1) ? "" : "s", skipped,
               (skipped == 1) ? "" : "s", sc.TransferRate().c_str());
     return true;
@@ -739,7 +740,6 @@
         success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode);
     }
 
-    sc.Print("\n");
     return success;
 }
 
@@ -858,7 +858,7 @@
         }
     }
 
-    sc.Printf("%s: %d file%s pulled. %d file%s skipped.%s\n", rpath.c_str(),
+    sc.Printf("%s: %d file%s pulled. %d file%s skipped.%s", rpath.c_str(),
               pulled, (pulled == 1) ? "" : "s", skipped,
               (skipped == 1) ? "" : "s", sc.TransferRate().c_str());
     return true;
@@ -967,7 +967,6 @@
         }
     }
 
-    sc.Print("\n");
     return success;
 }
 
diff --git a/adb/line_printer.cpp b/adb/line_printer.cpp
index 4c57c7e..e8fe6c9 100644
--- a/adb/line_printer.cpp
+++ b/adb/line_printer.cpp
@@ -43,7 +43,7 @@
   return result;
 }
 
-LinePrinter::LinePrinter() : have_blank_line_(true), console_locked_(false) {
+LinePrinter::LinePrinter() : have_blank_line_(true) {
 #ifndef _WIN32
   const char* term = getenv("TERM");
   smart_terminal_ = unix_isatty(1) && term && string(term) != "dumb";
@@ -59,20 +59,24 @@
 #endif
 }
 
+static void Out(const std::string& s) {
+  // Avoid printf and C strings, since the actual output might contain null
+  // bytes like UTF-16 does (yuck).
+  fwrite(s.data(), 1, s.size(), stdout);
+}
+
 void LinePrinter::Print(string to_print, LineType type) {
-  if (console_locked_) {
-    line_buffer_ = to_print;
-    line_type_ = type;
+  if (!smart_terminal_) {
+    Out(to_print);
     return;
   }
 
-  if (smart_terminal_) {
-    printf("\r");  // Print over previous line, if any.
-    // On Windows, calling a C library function writing to stdout also handles
-    // pausing the executable when the "Pause" key or Ctrl-S is pressed.
-  }
+  // Print over previous line, if any.
+  // On Windows, calling a C library function writing to stdout also handles
+  // pausing the executable when the "Pause" key or Ctrl-S is pressed.
+  printf("\r");
 
-  if (smart_terminal_ && type == ELIDE) {
+  if (type == INFO) {
 #ifdef _WIN32
     CONSOLE_SCREEN_BUFFER_INFO csbi;
     GetConsoleScreenBufferInfo(console_, &csbi);
@@ -105,57 +109,19 @@
     if ((ioctl(0, TIOCGWINSZ, &size) == 0) && size.ws_col) {
       to_print = ElideMiddle(to_print, size.ws_col);
     }
-    printf("%s", to_print.c_str());
+    Out(to_print);
     printf("\x1B[K");  // Clear to end of line.
     fflush(stdout);
 #endif
 
     have_blank_line_ = false;
   } else {
-    printf("%s\n", to_print.c_str());
+    Out(to_print);
+    Out("\n");
+    have_blank_line_ = true;
   }
 }
 
-void LinePrinter::PrintOrBuffer(const char* data, size_t size) {
-  if (console_locked_) {
-    output_buffer_.append(data, size);
-  } else {
-    // Avoid printf and C strings, since the actual output might contain null
-    // bytes like UTF-16 does (yuck).
-    fwrite(data, 1, size, stdout);
-  }
-}
-
-void LinePrinter::PrintOnNewLine(const string& to_print) {
-  if (console_locked_ && !line_buffer_.empty()) {
-    output_buffer_.append(line_buffer_);
-    output_buffer_.append(1, '\n');
-    line_buffer_.clear();
-  }
-  if (!have_blank_line_) {
-    PrintOrBuffer("\n", 1);
-  }
-  if (!to_print.empty()) {
-    PrintOrBuffer(&to_print[0], to_print.size());
-  }
-  have_blank_line_ = to_print.empty() || *to_print.rbegin() == '\n';
-}
-
-void LinePrinter::SetConsoleLocked(bool locked) {
-  if (locked == console_locked_)
-    return;
-
-  if (locked)
-    PrintOnNewLine("");
-
-  console_locked_ = locked;
-
-  if (!locked) {
-    PrintOnNewLine(output_buffer_);
-    if (!line_buffer_.empty()) {
-      Print(line_buffer_, line_type_);
-    }
-    output_buffer_.clear();
-    line_buffer_.clear();
-  }
+void LinePrinter::KeepInfoLine() {
+  if (!have_blank_line_) Out("\n");
 }
diff --git a/adb/line_printer.h b/adb/line_printer.h
index 3d0a5bd..42345e2 100644
--- a/adb/line_printer.h
+++ b/adb/line_printer.h
@@ -26,20 +26,14 @@
   bool is_smart_terminal() const { return smart_terminal_; }
   void set_smart_terminal(bool smart) { smart_terminal_ = smart; }
 
-  enum LineType {
-    FULL,
-    ELIDE
-  };
-  /// Overprints the current line. If type is ELIDE, elides to_print to fit on
-  /// one line.
+  enum LineType { INFO, WARNING, ERROR };
+
+  /// Outputs the given line. INFO output will be overwritten.
+  /// WARNING and ERROR appear on a line to themselves.
   void Print(std::string to_print, LineType type);
 
-  /// Prints a string on a new line, not overprinting previous output.
-  void PrintOnNewLine(const std::string& to_print);
-
-  /// Lock or unlock the console.  Any output sent to the LinePrinter while the
-  /// console is locked will not be printed until it is unlocked.
-  void SetConsoleLocked(bool locked);
+  /// If there's an INFO line, keep it. If not, do nothing.
+  void KeepInfoLine();
 
  private:
   /// Whether we can do fancy terminal control codes.
@@ -48,24 +42,9 @@
   /// Whether the caret is at the beginning of a blank line.
   bool have_blank_line_;
 
-  /// Whether console is locked.
-  bool console_locked_;
-
-  /// Buffered current line while console is locked.
-  std::string line_buffer_;
-
-  /// Buffered line type while console is locked.
-  LineType line_type_;
-
-  /// Buffered console output while console is locked.
-  std::string output_buffer_;
-
 #ifdef _WIN32
   void* console_;
 #endif
-
-  /// Print the given data to the console, or buffer it if it is locked.
-  void PrintOrBuffer(const char *data, size_t size);
 };
 
 #endif  // NINJA_LINE_PRINTER_H_
diff --git a/adb/shell_service.cpp b/adb/shell_service.cpp
index 6c06669..3fc70b0 100644
--- a/adb/shell_service.cpp
+++ b/adb/shell_service.cpp
@@ -289,6 +289,7 @@
         // TODO: $HOSTNAME? Normally bash automatically sets that, but mksh doesn't.
         passwd* pw = getpwuid(getuid());
         if (pw != nullptr) {
+            setenv("HOME", pw->pw_dir, 1);
             setenv("LOGNAME", pw->pw_name, 1);
             setenv("SHELL", pw->pw_shell, 1);
             setenv("USER", pw->pw_name, 1);
@@ -297,8 +298,6 @@
             setenv("TERM", terminal_type_.c_str(), 1);
         }
 
-        setenv("HOME", "/data/local/tmp", 1);
-        chdir(getenv("HOME"));
         if (is_interactive()) {
             execl(_PATH_BSHELL, _PATH_BSHELL, "-", nullptr);
         } else {
diff --git a/adb/test_device.py b/adb/test_device.py
new file mode 100644
index 0000000..955b67a
--- /dev/null
+++ b/adb/test_device.py
@@ -0,0 +1,951 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from __future__ import print_function
+
+import contextlib
+import hashlib
+import os
+import posixpath
+import random
+import re
+import shlex
+import shutil
+import signal
+import socket
+import string
+import subprocess
+import sys
+import tempfile
+import unittest
+
+import mock
+
+import adb
+
+
+def requires_root(func):
+    def wrapper(self, *args):
+        if self.device.get_prop('ro.debuggable') != '1':
+            raise unittest.SkipTest('requires rootable build')
+
+        was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
+        if not was_root:
+            self.device.root()
+            self.device.wait()
+
+        try:
+            func(self, *args)
+        finally:
+            if not was_root:
+                self.device.unroot()
+                self.device.wait()
+
+    return wrapper
+
+
+def requires_non_root(func):
+    def wrapper(self, *args):
+        was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
+        if was_root:
+            self.device.unroot()
+            self.device.wait()
+
+        try:
+            func(self, *args)
+        finally:
+            if was_root:
+                self.device.root()
+                self.device.wait()
+
+    return wrapper
+
+
+class GetDeviceTest(unittest.TestCase):
+    def setUp(self):
+        self.android_serial = os.getenv('ANDROID_SERIAL')
+        if 'ANDROID_SERIAL' in os.environ:
+            del os.environ['ANDROID_SERIAL']
+
+    def tearDown(self):
+        if self.android_serial is not None:
+            os.environ['ANDROID_SERIAL'] = self.android_serial
+        else:
+            if 'ANDROID_SERIAL' in os.environ:
+                del os.environ['ANDROID_SERIAL']
+
+    @mock.patch('adb.device.get_devices')
+    def test_explicit(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo', 'bar']
+        device = adb.get_device('foo')
+        self.assertEqual(device.serial, 'foo')
+
+    @mock.patch('adb.device.get_devices')
+    def test_from_env(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo', 'bar']
+        os.environ['ANDROID_SERIAL'] = 'foo'
+        device = adb.get_device()
+        self.assertEqual(device.serial, 'foo')
+
+    @mock.patch('adb.device.get_devices')
+    def test_arg_beats_env(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo', 'bar']
+        os.environ['ANDROID_SERIAL'] = 'bar'
+        device = adb.get_device('foo')
+        self.assertEqual(device.serial, 'foo')
+
+    @mock.patch('adb.device.get_devices')
+    def test_no_such_device(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo', 'bar']
+        self.assertRaises(adb.DeviceNotFoundError, adb.get_device, ['baz'])
+
+        os.environ['ANDROID_SERIAL'] = 'baz'
+        self.assertRaises(adb.DeviceNotFoundError, adb.get_device)
+
+    @mock.patch('adb.device.get_devices')
+    def test_unique_device(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo']
+        device = adb.get_device()
+        self.assertEqual(device.serial, 'foo')
+
+    @mock.patch('adb.device.get_devices')
+    def test_no_unique_device(self, mock_get_devices):
+        mock_get_devices.return_value = ['foo', 'bar']
+        self.assertRaises(adb.NoUniqueDeviceError, adb.get_device)
+
+
+class DeviceTest(unittest.TestCase):
+    def setUp(self):
+        self.device = adb.get_device()
+
+
+class ForwardReverseTest(DeviceTest):
+    def _test_no_rebind(self, description, direction_list, direction,
+                       direction_no_rebind, direction_remove_all):
+        msg = direction_list()
+        self.assertEqual('', msg.strip(),
+                         description + ' list must be empty to run this test.')
+
+        # Use --no-rebind with no existing binding
+        direction_no_rebind('tcp:5566', 'tcp:6655')
+        msg = direction_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+
+        # Use --no-rebind with existing binding
+        with self.assertRaises(subprocess.CalledProcessError):
+            direction_no_rebind('tcp:5566', 'tcp:6677')
+        msg = direction_list()
+        self.assertFalse(re.search(r'tcp:5566.+tcp:6677', msg))
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+
+        # Use the absence of --no-rebind with existing binding
+        direction('tcp:5566', 'tcp:6677')
+        msg = direction_list()
+        self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6677', msg))
+
+        direction_remove_all()
+        msg = direction_list()
+        self.assertEqual('', msg.strip())
+
+    def test_forward_no_rebind(self):
+        self._test_no_rebind('forward', self.device.forward_list,
+                            self.device.forward, self.device.forward_no_rebind,
+                            self.device.forward_remove_all)
+
+    def test_reverse_no_rebind(self):
+        self._test_no_rebind('reverse', self.device.reverse_list,
+                            self.device.reverse, self.device.reverse_no_rebind,
+                            self.device.reverse_remove_all)
+
+    def test_forward(self):
+        msg = self.device.forward_list()
+        self.assertEqual('', msg.strip(),
+                         'Forwarding list must be empty to run this test.')
+        self.device.forward('tcp:5566', 'tcp:6655')
+        msg = self.device.forward_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.device.forward('tcp:7788', 'tcp:8877')
+        msg = self.device.forward_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
+        self.device.forward_remove('tcp:5566')
+        msg = self.device.forward_list()
+        self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
+        self.device.forward_remove_all()
+        msg = self.device.forward_list()
+        self.assertEqual('', msg.strip())
+
+    def test_reverse(self):
+        msg = self.device.reverse_list()
+        self.assertEqual('', msg.strip(),
+                         'Reverse forwarding list must be empty to run this test.')
+        self.device.reverse('tcp:5566', 'tcp:6655')
+        msg = self.device.reverse_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.device.reverse('tcp:7788', 'tcp:8877')
+        msg = self.device.reverse_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
+        self.device.reverse_remove('tcp:5566')
+        msg = self.device.reverse_list()
+        self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
+        self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
+        self.device.reverse_remove_all()
+        msg = self.device.reverse_list()
+        self.assertEqual('', msg.strip())
+
+    # Note: If you run this test when adb connect'd to a physical device over
+    # TCP, it will fail in adb reverse due to https://code.google.com/p/android/issues/detail?id=189821
+    def test_forward_reverse_echo(self):
+        """Send data through adb forward and read it back via adb reverse"""
+        forward_port = 12345
+        reverse_port = forward_port + 1
+        forward_spec = "tcp:" + str(forward_port)
+        reverse_spec = "tcp:" + str(reverse_port)
+        forward_setup = False
+        reverse_setup = False
+
+        try:
+            # listen on localhost:forward_port, connect to remote:forward_port
+            self.device.forward(forward_spec, forward_spec)
+            forward_setup = True
+            # listen on remote:forward_port, connect to localhost:reverse_port
+            self.device.reverse(forward_spec, reverse_spec)
+            reverse_setup = True
+
+            listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            with contextlib.closing(listener):
+                # Use SO_REUSEADDR so that 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)
+
+                # Listen on localhost:reverse_port before connecting to
+                # localhost:forward_port because that will cause adb to connect
+                # back to localhost:reverse_port.
+                listener.bind(('127.0.0.1', reverse_port))
+                listener.listen(4)
+
+                client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                with contextlib.closing(client):
+                    # Connect to the listener.
+                    client.connect(('127.0.0.1', forward_port))
+
+                    # Accept the client connection.
+                    accepted_connection, addr = listener.accept()
+                    with contextlib.closing(accepted_connection) as server:
+                        data = 'hello'
+
+                        # Send data into the port setup by adb forward.
+                        client.sendall(data)
+                        # Explicitly close() so that server gets EOF.
+                        client.close()
+
+                        # Verify that the data came back via adb reverse.
+                        self.assertEqual(data, server.makefile().read())
+        finally:
+            if reverse_setup:
+                self.device.reverse_remove(forward_spec)
+            if forward_setup:
+                self.device.forward_remove(forward_spec)
+
+
+class ShellTest(DeviceTest):
+    def _interactive_shell(self, shell_args, input):
+        """Runs an interactive adb shell.
+
+        Args:
+          shell_args: List of string arguments to `adb shell`.
+          input: String input to send to the interactive shell.
+
+        Returns:
+          The remote exit code.
+
+        Raises:
+          unittest.SkipTest: The device doesn't support exit codes.
+        """
+        if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features:
+            raise unittest.SkipTest('exit codes are unavailable on this device')
+
+        proc = subprocess.Popen(
+                self.device.adb_cmd + ['shell'] + shell_args,
+                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE)
+        # Closing host-side stdin doesn't trigger a PTY shell to exit so we need
+        # to explicitly add an exit command to close the session from the device
+        # side, plus the necessary newline to complete the interactive command.
+        proc.communicate(input + '; exit\n')
+        return proc.returncode
+
+    def test_cat(self):
+        """Check that we can at least cat a file."""
+        out = self.device.shell(['cat', '/proc/uptime'])[0].strip()
+        elements = out.split()
+        self.assertEqual(len(elements), 2)
+
+        uptime, idle = elements
+        self.assertGreater(float(uptime), 0.0)
+        self.assertGreater(float(idle), 0.0)
+
+    def test_throws_on_failure(self):
+        self.assertRaises(adb.ShellError, self.device.shell, ['false'])
+
+    def test_output_not_stripped(self):
+        out = self.device.shell(['echo', 'foo'])[0]
+        self.assertEqual(out, 'foo' + self.device.linesep)
+
+    def test_shell_nocheck_failure(self):
+        rc, out, _ = self.device.shell_nocheck(['false'])
+        self.assertNotEqual(rc, 0)
+        self.assertEqual(out, '')
+
+    def test_shell_nocheck_output_not_stripped(self):
+        rc, out, _ = self.device.shell_nocheck(['echo', 'foo'])
+        self.assertEqual(rc, 0)
+        self.assertEqual(out, 'foo' + self.device.linesep)
+
+    def test_can_distinguish_tricky_results(self):
+        # If result checking on ADB shell is naively implemented as
+        # `adb shell <cmd>; echo $?`, we would be unable to distinguish the
+        # output from the result for a cmd of `echo -n 1`.
+        rc, out, _ = self.device.shell_nocheck(['echo', '-n', '1'])
+        self.assertEqual(rc, 0)
+        self.assertEqual(out, '1')
+
+    def test_line_endings(self):
+        """Ensure that line ending translation is not happening in the pty.
+
+        Bug: http://b/19735063
+        """
+        output = self.device.shell(['uname'])[0]
+        self.assertEqual(output, 'Linux' + self.device.linesep)
+
+    def test_pty_logic(self):
+        """Tests that a PTY is allocated when it should be.
+
+        PTY allocation behavior should match ssh; some behavior requires
+        a terminal stdin to test so this test will be skipped if stdin
+        is not a terminal.
+        """
+        if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features:
+            raise unittest.SkipTest('PTY arguments unsupported on this device')
+        if not os.isatty(sys.stdin.fileno()):
+            raise unittest.SkipTest('PTY tests require stdin terminal')
+
+        def check_pty(args):
+            """Checks adb shell PTY allocation.
+
+            Tests |args| for terminal and non-terminal stdin.
+
+            Args:
+                args: -Tt args in a list (e.g. ['-t', '-t']).
+
+            Returns:
+                A tuple (<terminal>, <non-terminal>). True indicates
+                the corresponding shell allocated a remote PTY.
+            """
+            test_cmd = self.device.adb_cmd + ['shell'] + args + ['[ -t 0 ]']
+
+            terminal = subprocess.Popen(
+                    test_cmd, stdin=None,
+                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            terminal.communicate()
+
+            non_terminal = subprocess.Popen(
+                    test_cmd, stdin=subprocess.PIPE,
+                    stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            non_terminal.communicate()
+
+            return (terminal.returncode == 0, non_terminal.returncode == 0)
+
+        # -T: never allocate PTY.
+        self.assertEqual((False, False), check_pty(['-T']))
+
+        # No args: PTY only if stdin is a terminal and shell is interactive,
+        # which is difficult to reliably test from a script.
+        self.assertEqual((False, False), check_pty([]))
+
+        # -t: PTY if stdin is a terminal.
+        self.assertEqual((True, False), check_pty(['-t']))
+
+        # -t -t: always allocate PTY.
+        self.assertEqual((True, True), check_pty(['-t', '-t']))
+
+    def test_shell_protocol(self):
+        """Tests the shell protocol on the device.
+
+        If the device supports shell protocol, this gives us the ability
+        to separate stdout/stderr and return the exit code directly.
+
+        Bug: http://b/19734861
+        """
+        if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features:
+            raise unittest.SkipTest('shell protocol unsupported on this device')
+
+        # Shell protocol should be used by default.
+        result = self.device.shell_nocheck(
+                shlex.split('echo foo; echo bar >&2; exit 17'))
+        self.assertEqual(17, result[0])
+        self.assertEqual('foo' + self.device.linesep, result[1])
+        self.assertEqual('bar' + self.device.linesep, result[2])
+
+        self.assertEqual(17, self._interactive_shell([], 'exit 17'))
+
+        # -x flag should disable shell protocol.
+        result = self.device.shell_nocheck(
+                shlex.split('-x echo foo; echo bar >&2; exit 17'))
+        self.assertEqual(0, result[0])
+        self.assertEqual('foo{0}bar{0}'.format(self.device.linesep), result[1])
+        self.assertEqual('', result[2])
+
+        self.assertEqual(0, self._interactive_shell(['-x'], 'exit 17'))
+
+    def test_non_interactive_sigint(self):
+        """Tests that SIGINT in a non-interactive shell kills the process.
+
+        This requires the shell protocol in order to detect the broken
+        pipe; raw data transfer mode will only see the break once the
+        subprocess tries to read or write.
+
+        Bug: http://b/23825725
+        """
+        if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features:
+            raise unittest.SkipTest('shell protocol unsupported on this device')
+
+        # Start a long-running process.
+        sleep_proc = subprocess.Popen(
+                self.device.adb_cmd + shlex.split('shell echo $$; sleep 60'),
+                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT)
+        remote_pid = sleep_proc.stdout.readline().strip()
+        self.assertIsNone(sleep_proc.returncode, 'subprocess terminated early')
+        proc_query = shlex.split('ps {0} | grep {0}'.format(remote_pid))
+
+        # Verify that the process is running, send signal, verify it stopped.
+        self.device.shell(proc_query)
+        os.kill(sleep_proc.pid, signal.SIGINT)
+        sleep_proc.communicate()
+        self.assertEqual(1, self.device.shell_nocheck(proc_query)[0],
+                         'subprocess failed to terminate')
+
+    def test_non_interactive_stdin(self):
+        """Tests that non-interactive shells send stdin."""
+        if self.device.SHELL_PROTOCOL_FEATURE not in self.device.features:
+            raise unittest.SkipTest('non-interactive stdin unsupported '
+                                    'on this device')
+
+        # Test both small and large inputs.
+        small_input = 'foo'
+        large_input = '\n'.join(c * 100 for c in (string.ascii_letters +
+                                                  string.digits))
+
+        for input in (small_input, large_input):
+            proc = subprocess.Popen(self.device.adb_cmd + ['shell', 'cat'],
+                                    stdin=subprocess.PIPE,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            stdout, stderr = proc.communicate(input)
+            self.assertEqual(input.splitlines(), stdout.splitlines())
+            self.assertEqual('', stderr)
+
+
+class ArgumentEscapingTest(DeviceTest):
+    def test_shell_escaping(self):
+        """Make sure that argument escaping is somewhat sane."""
+
+        # http://b/19734868
+        # Note that this actually matches ssh(1)'s behavior --- it's
+        # converted to `sh -c echo hello; echo world` which sh interprets
+        # as `sh -c echo` (with an argument to that shell of "hello"),
+        # and then `echo world` back in the first shell.
+        result = self.device.shell(
+            shlex.split("sh -c 'echo hello; echo world'"))[0]
+        result = result.splitlines()
+        self.assertEqual(['', 'world'], result)
+        # If you really wanted "hello" and "world", here's what you'd do:
+        result = self.device.shell(
+            shlex.split(r'echo hello\;echo world'))[0].splitlines()
+        self.assertEqual(['hello', 'world'], result)
+
+        # http://b/15479704
+        result = self.device.shell(shlex.split("'true && echo t'"))[0].strip()
+        self.assertEqual('t', result)
+        result = self.device.shell(
+            shlex.split("sh -c 'true && echo t'"))[0].strip()
+        self.assertEqual('t', result)
+
+        # http://b/20564385
+        result = self.device.shell(shlex.split('FOO=a BAR=b echo t'))[0].strip()
+        self.assertEqual('t', result)
+        result = self.device.shell(
+            shlex.split(r'echo -n 123\;uname'))[0].strip()
+        self.assertEqual('123Linux', result)
+
+    def test_install_argument_escaping(self):
+        """Make sure that install argument escaping works."""
+        # http://b/20323053, http://b/3090932.
+        for file_suffix in ('-text;ls;1.apk', "-Live Hold'em.apk"):
+            tf = tempfile.NamedTemporaryFile('wb', suffix=file_suffix,
+                                             delete=False)
+            tf.close()
+
+            # Installing bogus .apks fails if the device supports exit codes.
+            try:
+                output = self.device.install(tf.name)
+            except subprocess.CalledProcessError as e:
+                output = e.output
+
+            self.assertIn(file_suffix, output)
+            os.remove(tf.name)
+
+
+class RootUnrootTest(DeviceTest):
+    def _test_root(self):
+        message = self.device.root()
+        if 'adbd cannot run as root in production builds' in message:
+            return
+        self.device.wait()
+        self.assertEqual('root', self.device.shell(['id', '-un'])[0].strip())
+
+    def _test_unroot(self):
+        self.device.unroot()
+        self.device.wait()
+        self.assertEqual('shell', self.device.shell(['id', '-un'])[0].strip())
+
+    def test_root_unroot(self):
+        """Make sure that adb root and adb unroot work, using id(1)."""
+        if self.device.get_prop('ro.debuggable') != '1':
+            raise unittest.SkipTest('requires rootable build')
+
+        original_user = self.device.shell(['id', '-un'])[0].strip()
+        try:
+            if original_user == 'root':
+                self._test_unroot()
+                self._test_root()
+            elif original_user == 'shell':
+                self._test_root()
+                self._test_unroot()
+        finally:
+            if original_user == 'root':
+                self.device.root()
+            else:
+                self.device.unroot()
+            self.device.wait()
+
+
+class TcpIpTest(DeviceTest):
+    def test_tcpip_failure_raises(self):
+        """adb tcpip requires a port.
+
+        Bug: http://b/22636927
+        """
+        self.assertRaises(
+            subprocess.CalledProcessError, self.device.tcpip, '')
+        self.assertRaises(
+            subprocess.CalledProcessError, self.device.tcpip, 'foo')
+
+
+class SystemPropertiesTest(DeviceTest):
+    def test_get_prop(self):
+        self.assertEqual(self.device.get_prop('init.svc.adbd'), 'running')
+
+    @requires_root
+    def test_set_prop(self):
+        prop_name = 'foo.bar'
+        self.device.shell(['setprop', prop_name, '""'])
+
+        self.device.set_prop(prop_name, 'qux')
+        self.assertEqual(
+            self.device.shell(['getprop', prop_name])[0].strip(), 'qux')
+
+
+def compute_md5(string):
+    hsh = hashlib.md5()
+    hsh.update(string)
+    return hsh.hexdigest()
+
+
+def get_md5_prog(device):
+    """Older platforms (pre-L) had the name md5 rather than md5sum."""
+    try:
+        device.shell(['md5sum', '/proc/uptime'])
+        return 'md5sum'
+    except adb.ShellError:
+        return 'md5'
+
+
+class HostFile(object):
+    def __init__(self, handle, checksum):
+        self.handle = handle
+        self.checksum = checksum
+        self.full_path = handle.name
+        self.base_name = os.path.basename(self.full_path)
+
+
+class DeviceFile(object):
+    def __init__(self, checksum, full_path):
+        self.checksum = checksum
+        self.full_path = full_path
+        self.base_name = posixpath.basename(self.full_path)
+
+
+def make_random_host_files(in_dir, num_files):
+    min_size = 1 * (1 << 10)
+    max_size = 16 * (1 << 10)
+
+    files = []
+    for _ in xrange(num_files):
+        file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
+
+        size = random.randrange(min_size, max_size, 1024)
+        rand_str = os.urandom(size)
+        file_handle.write(rand_str)
+        file_handle.flush()
+        file_handle.close()
+
+        md5 = compute_md5(rand_str)
+        files.append(HostFile(file_handle, md5))
+    return files
+
+
+def make_random_device_files(device, in_dir, num_files, prefix='device_tmpfile'):
+    min_size = 1 * (1 << 10)
+    max_size = 16 * (1 << 10)
+
+    files = []
+    for file_num in xrange(num_files):
+        size = random.randrange(min_size, max_size, 1024)
+
+        base_name = prefix + str(file_num)
+        full_path = posixpath.join(in_dir, base_name)
+
+        device.shell(['dd', 'if=/dev/urandom', 'of={}'.format(full_path),
+                      'bs={}'.format(size), 'count=1'])
+        dev_md5, _ = device.shell([get_md5_prog(device), full_path])[0].split()
+
+        files.append(DeviceFile(dev_md5, full_path))
+    return files
+
+
+class FileOperationsTest(DeviceTest):
+    SCRATCH_DIR = '/data/local/tmp'
+    DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
+    DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
+
+    def _verify_remote(self, checksum, remote_path):
+        dev_md5, _ = self.device.shell([get_md5_prog(self.device),
+                                        remote_path])[0].split()
+        self.assertEqual(checksum, dev_md5)
+
+    def _verify_local(self, checksum, local_path):
+        with open(local_path, 'rb') as host_file:
+            host_md5 = compute_md5(host_file.read())
+            self.assertEqual(host_md5, checksum)
+
+    def test_push(self):
+        """Push a randomly generated file to specified device."""
+        kbytes = 512
+        tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+        rand_str = os.urandom(1024 * kbytes)
+        tmp.write(rand_str)
+        tmp.close()
+
+        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+        self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
+
+        self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
+        self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
+
+        os.remove(tmp.name)
+
+    def test_push_dir(self):
+        """Push a randomly generated directory of files to the device."""
+        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            # Make sure the temp directory isn't setuid, or else adb will complain.
+            os.chmod(host_dir, 0o700)
+
+            # Create 32 random files.
+            temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
+            self.device.push(host_dir, self.DEVICE_TEMP_DIR)
+
+            for temp_file in temp_files:
+                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                             os.path.basename(host_dir),
+                                             temp_file.base_name)
+                self._verify_remote(temp_file.checksum, remote_path)
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+    @unittest.expectedFailure # b/25566053
+    def test_push_empty(self):
+        """Push a directory containing an empty directory to the device."""
+        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            # Make sure the temp directory isn't setuid, or else adb will complain.
+            os.chmod(host_dir, 0o700)
+
+            # Create an empty directory.
+            os.mkdir(os.path.join(host_dir, 'empty'))
+
+            self.device.push(host_dir, self.DEVICE_TEMP_DIR)
+
+            test_empty_cmd = ['[', '-d',
+                              os.path.join(self.DEVICE_TEMP_DIR, 'empty')]
+            rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
+            self.assertEqual(rc, 0)
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+    def test_multiple_push(self):
+        """Push multiple files to the device in one adb push command.
+
+        Bug: http://b/25324823
+        """
+
+        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            # Create some random files and a subdirectory containing more files.
+            temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
+
+            subdir = os.path.join(host_dir, "subdir")
+            os.mkdir(subdir)
+            subdir_temp_files = make_random_host_files(in_dir=subdir,
+                                                       num_files=4)
+
+            paths = map(lambda temp_file: temp_file.full_path, temp_files)
+            paths.append(subdir)
+            self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
+
+            for temp_file in temp_files:
+                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                             temp_file.base_name)
+                self._verify_remote(temp_file.checksum, remote_path)
+
+            for subdir_temp_file in subdir_temp_files:
+                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                             # BROKEN: http://b/25394682
+                                             # "subdir",
+                                             temp_file.base_name)
+                self._verify_remote(temp_file.checksum, remote_path)
+
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+
+    def _test_pull(self, remote_file, checksum):
+        tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+        tmp_write.close()
+        self.device.pull(remote=remote_file, local=tmp_write.name)
+        with open(tmp_write.name, 'rb') as tmp_read:
+            host_contents = tmp_read.read()
+            host_md5 = compute_md5(host_contents)
+        self.assertEqual(checksum, host_md5)
+        os.remove(tmp_write.name)
+
+    @requires_non_root
+    def test_pull_error_reporting(self):
+        self.device.shell(['touch', self.DEVICE_TEMP_FILE])
+        self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
+
+        try:
+            output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
+        except subprocess.CalledProcessError as e:
+            output = e.output
+
+        self.assertIn('Permission denied', output)
+
+        self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
+
+    def test_pull(self):
+        """Pull a randomly generated file from specified device."""
+        kbytes = 512
+        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+        cmd = ['dd', 'if=/dev/urandom',
+               'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
+               'count={}'.format(kbytes)]
+        self.device.shell(cmd)
+        dev_md5, _ = self.device.shell(
+            [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
+        self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
+        self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
+
+    def test_pull_dir(self):
+        """Pull a randomly generated directory of files from the device."""
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+
+            # Populate device directory with random files.
+            temp_files = make_random_device_files(
+                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+
+            self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
+
+            for temp_file in temp_files:
+                host_path = os.path.join(
+                    host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
+                    temp_file.base_name)
+                self._verify_local(temp_file.checksum, host_path)
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+    def test_pull_empty(self):
+        """Pull a directory containing an empty directory from the device."""
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            self.device.shell(['mkdir', '-p', remote_empty_path])
+
+            self.device.pull(remote=remote_empty_path, local=host_dir)
+            self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+    def test_multiple_pull(self):
+        """Pull a randomly generated directory of files from the device."""
+
+        try:
+            host_dir = tempfile.mkdtemp()
+
+            subdir = posixpath.join(self.DEVICE_TEMP_DIR, "subdir")
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            self.device.shell(['mkdir', '-p', subdir])
+
+            # Create some random files and a subdirectory containing more files.
+            temp_files = make_random_device_files(
+                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
+
+            subdir_temp_files = make_random_device_files(
+                self.device, in_dir=subdir, num_files=4, prefix='subdir_')
+
+            paths = map(lambda temp_file: temp_file.full_path, temp_files)
+            paths.append(subdir)
+            self.device._simple_call(['pull'] + paths + [host_dir])
+
+            for temp_file in temp_files:
+                local_path = os.path.join(host_dir, temp_file.base_name)
+                self._verify_local(temp_file.checksum, local_path)
+
+            for subdir_temp_file in subdir_temp_files:
+                local_path = os.path.join(host_dir,
+                                          "subdir",
+                                          subdir_temp_file.base_name)
+                self._verify_local(subdir_temp_file.checksum, local_path)
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if host_dir is not None:
+                shutil.rmtree(host_dir)
+
+    def test_sync(self):
+        """Sync a randomly generated directory of files to specified device."""
+
+        try:
+            base_dir = tempfile.mkdtemp()
+
+            # Create mirror device directory hierarchy within base_dir.
+            full_dir_path = base_dir + self.DEVICE_TEMP_DIR
+            os.makedirs(full_dir_path)
+
+            # Create 32 random files within the host mirror.
+            temp_files = make_random_host_files(in_dir=full_dir_path, num_files=32)
+
+            # Clean up any trash on the device.
+            device = adb.get_device(product=base_dir)
+            device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+
+            device.sync('data')
+
+            # Confirm that every file on the device mirrors that on the host.
+            for temp_file in temp_files:
+                device_full_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                                  temp_file.base_name)
+                dev_md5, _ = device.shell(
+                    [get_md5_prog(self.device), device_full_path])[0].split()
+                self.assertEqual(temp_file.checksum, dev_md5)
+
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+        finally:
+            if base_dir is not None:
+                shutil.rmtree(base_dir)
+
+    def test_unicode_paths(self):
+        """Ensure that we can support non-ASCII paths, even on Windows."""
+        name = u'로보카 폴리'
+
+        self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
+        remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
+
+        ## push.
+        tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
+        tf.close()
+        self.device.push(tf.name, remote_path)
+        os.remove(tf.name)
+        self.assertFalse(os.path.exists(tf.name))
+
+        # Verify that the device ended up with the expected UTF-8 path
+        output = self.device.shell(
+                ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
+        self.assertEqual(remote_path.encode('utf-8'), output)
+
+        # pull.
+        self.device.pull(remote_path, tf.name)
+        self.assertTrue(os.path.exists(tf.name))
+        os.remove(tf.name)
+        self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
+
+
+def main():
+    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')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/crash_reporter/Android.mk b/crash_reporter/Android.mk
index 81cb458..565963c 100644
--- a/crash_reporter/Android.mk
+++ b/crash_reporter/Android.mk
@@ -43,11 +43,12 @@
 LOCAL_C_INCLUDES := $(crash_reporter_includes)
 LOCAL_RTTI_FLAG := -frtti
 LOCAL_SHARED_LIBRARIES := libchrome \
+    libbinder \
     libbrillo \
     libcutils \
-    libdbus \
     libmetrics \
     libpcrecpp
+LOCAL_STATIC_LIBRARIES := libmetricscollectorservice
 LOCAL_SRC_FILES := $(crash_reporter_src)
 include $(BUILD_STATIC_LIBRARY)
 
@@ -60,18 +61,19 @@
 LOCAL_REQUIRED_MODULES := core2md \
     crash_reporter_logs.conf \
     crash_sender \
-    crash_server \
-    dbus-send
+    crash_server
 LOCAL_INIT_RC := crash_reporter.rc
 LOCAL_RTTI_FLAG := -frtti
 LOCAL_SHARED_LIBRARIES := libchrome \
+    libbinder \
     libbrillo \
     libcutils \
-    libdbus \
     libmetrics \
-    libpcrecpp
+    libpcrecpp \
+    libutils
 LOCAL_SRC_FILES := crash_reporter.cc
-LOCAL_STATIC_LIBRARIES := libcrash
+LOCAL_STATIC_LIBRARIES := libcrash \
+    libmetricscollectorservice
 include $(BUILD_EXECUTABLE)
 
 # Crash sender script.
@@ -140,7 +142,6 @@
 LOCAL_SHARED_LIBRARIES := libchrome \
     libbrillo \
     libcutils \
-    libdbus \
     libpcrecpp
 LOCAL_SRC_FILES := $(crash_reporter_test_src)
 LOCAL_STATIC_LIBRARIES := libcrash libgmock
diff --git a/crash_reporter/crash_reporter.cc b/crash_reporter/crash_reporter.cc
index 3955fe5..26ffa38 100644
--- a/crash_reporter/crash_reporter.cc
+++ b/crash_reporter/crash_reporter.cc
@@ -25,10 +25,13 @@
 #include <base/strings/string_split.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#include <binder/IServiceManager.h>
 #include <brillo/flag_helper.h>
-#include <brillo/process.h>
 #include <brillo/syslog_logging.h>
+#include <metrics/metrics_collector_service_client.h>
 #include <metrics/metrics_library.h>
+#include <utils/String16.h>
+
 
 #include "kernel_collector.h"
 #include "kernel_warning_collector.h"
@@ -37,8 +40,6 @@
 #include "user_collector.h"
 
 static const char kCrashCounterHistogram[] = "Logging.CrashCounter";
-static const char kUserCrashSignal[] =
-    "org.chromium.CrashReporter.UserCrash";
 static const char kKernelCrashDetected[] = "/var/run/kernel-crash-detected";
 static const char kUncleanShutdownDetected[] =
     "/var/run/unclean-shutdown-detected";
@@ -56,6 +57,7 @@
 
 static MetricsLibrary s_metrics_lib;
 
+using android::brillo::metrics::IMetricsCollectorService;
 using base::FilePath;
 using base::StringPrintf;
 
@@ -88,32 +90,14 @@
 
 static void CountUserCrash() {
   SendCrashMetrics(kCrashKindUser, "user");
-  // Announce through D-Bus whenever a user crash happens. This is
-  // used by the metrics daemon to log active use time between
-  // crashes.
-  //
-  // We run in the background in case dbus-daemon itself is crashed
-  // and not responding.  This allows us to not block and potentially
-  // deadlock on a dbus-daemon crash.  If dbus-daemon crashes without
-  // restarting, each crash will fork off a lot of dbus-send
-  // processes.  Such a system is in a unusable state and will need
-  // to be restarted anyway.
-  //
-  // Note: This will mean that the dbus-send process will become a zombie and
-  // reparent to init for reaping, but that's OK -- see above.
+  // Tell the metrics collector about the user crash, in order to log active
+  // use time between crashes.
+  MetricsCollectorServiceClient metrics_collector_service;
 
-  brillo::ProcessImpl dbus_send;
-  dbus_send.AddArg("/system/bin/dbus-send");
-  dbus_send.AddArg("--type=signal");
-  dbus_send.AddArg("--system");
-  dbus_send.AddArg("/");
-  dbus_send.AddArg(kUserCrashSignal);
-  bool status = dbus_send.Start();
-  if (status) {
-    dbus_send.Release();
-  } else {
-    PLOG(WARNING) << "Sending UserCrash DBus signal failed";
-  }
+  if (metrics_collector_service.Init())
+    metrics_collector_service.notifyUserCrash();
+  else
+    LOG(ERROR) << "Failed to send user crash notification to metrics_collector";
 }
 
 
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cc9f06c..1ab9436 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -279,6 +279,10 @@
             "                                           override the fs type and/or size\n"
             "                                           the bootloader reports.\n"
             "  getvar <variable>                        Display a bootloader variable.\n"
+            "  set_active <suffix>                      Sets the active slot. If slots are\n"
+            "                                           not supported, this does nothing.\n"
+            "                                           note: suffixes starting with a '-'\n"
+            "                                           must use set_active -- <suffix>\n"
             "  boot <kernel> [ <ramdisk> [ <second> ] ] Download and boot kernel.\n"
             "  flash:raw boot <kernel> [ <ramdisk> [ <second> ] ]\n"
             "                                           Create bootimage and flash it.\n"
@@ -321,7 +325,8 @@
             "  -a, --set-active[=<suffix>]              Sets the active slot. If no suffix is\n"
             "                                           provided, this will default to the value\n"
             "                                           given by --slot. If slots are not\n"
-            "                                           supported, this does nothing.\n"
+            "                                           supported, this does nothing. This will\n"
+            "                                           run after all non-reboot commands.\n"
             "  --unbuffered                             Do not buffer input or output.\n"
             "  --version                                Display version.\n"
             "  -h, --help                               show this message.\n"
@@ -724,9 +729,19 @@
     return android::base::Split(suffix_list, ",");
 }
 
-static std::string verify_slot(Transport* transport, const char *slot) {
+static std::string verify_slot(Transport* transport, const char *slot, bool allow_all) {
     if (strcmp(slot, "all") == 0) {
-        return "all";
+        if (allow_all) {
+            return "all";
+        } else {
+            std::vector<std::string> suffixes = get_suffixes(transport);
+            if (!suffixes.empty()) {
+                return suffixes[0];
+            } else {
+                fprintf(stderr, "No known slots.\n");
+                exit(1);
+            }
+        }
     }
     std::vector<std::string> suffixes = get_suffixes(transport);
     for (const std::string &suffix : suffixes) {
@@ -740,6 +755,10 @@
     exit(1);
 }
 
+static std::string verify_slot(Transport* transport, const char *slot) {
+   return verify_slot(transport, slot, true);
+}
+
 static void do_for_partition(Transport* transport, const char *part, const char *slot,
                              std::function<void(const std::string&)> func, bool force_slot) {
     std::string has_slot;
@@ -1220,14 +1239,14 @@
     if (slot_override != "")
         slot_override = verify_slot(transport, slot_override.c_str());
     if (next_active != "")
-        next_active = verify_slot(transport, next_active.c_str());
+        next_active = verify_slot(transport, next_active.c_str(), false);
 
     if (wants_set_active) {
         if (next_active == "") {
             if (slot_override == "") {
                 wants_set_active = false;
             } else {
-                next_active = slot_override;
+                next_active = verify_slot(transport, slot_override.c_str(), false);
             }
         }
     }
@@ -1385,6 +1404,12 @@
                 do_update(transport, "update.zip", slot_override.c_str(), erase_first);
                 skip(1);
             }
+            wants_reboot = 1;
+        } else if(!strcmp(*argv, "set_active")) {
+            require(2);
+            std::string slot = verify_slot(transport, argv[1], false);
+            fb_set_active(slot.c_str());
+            skip(2);
             wants_reboot = true;
         } else if(!strcmp(*argv, "oem")) {
             argc = do_oem_command(argc, argv);
diff --git a/fs_mgr/fs_mgr_verity.cpp b/fs_mgr/fs_mgr_verity.cpp
index 928d56c..b5141c9 100644
--- a/fs_mgr/fs_mgr_verity.cpp
+++ b/fs_mgr/fs_mgr_verity.cpp
@@ -254,7 +254,7 @@
         res = snprintf(buf, bufsize, "%s 2 " VERITY_TABLE_OPT_IGNZERO " %s", params->table,
                     mode_flag);
     } else {
-        res = strlcpy(buf, params->table, bufsize);
+        res = snprintf(buf, bufsize, "%s 1 " VERITY_TABLE_OPT_IGNZERO, params->table);
     }
 
     if (res < 0 || (size_t)res >= bufsize) {
@@ -695,31 +695,27 @@
     int match = 0;
     off64_t offset = 0;
 
+    /* unless otherwise specified, use EIO mode */
+    *mode = VERITY_MODE_EIO;
+
     /* use the kernel parameter if set */
     property_get("ro.boot.veritymode", propbuf, "");
 
     if (*propbuf != '\0') {
         if (!strcmp(propbuf, "enforcing")) {
             *mode = VERITY_MODE_DEFAULT;
-            return 0;
-        } else if (!strcmp(propbuf, "logging")) {
-            *mode = VERITY_MODE_LOGGING;
-            return 0;
-        } else {
-            INFO("Unknown value %s for veritymode; ignoring", propbuf);
         }
+        return 0;
     }
 
     if (get_verity_state_offset(fstab, &offset) < 0) {
         /* fall back to stateless behavior */
-        *mode = VERITY_MODE_EIO;
         return 0;
     }
 
     if (was_verity_restart()) {
         /* device was restarted after dm-verity detected a corrupted
-         * block, so switch to logging mode */
-        *mode = VERITY_MODE_LOGGING;
+         * block, so use EIO mode */
         return write_verity_state(fstab->verity_loc, offset, *mode);
     }
 
@@ -784,7 +780,6 @@
 int fs_mgr_update_verity_state(fs_mgr_verity_state_callback callback)
 {
     alignas(dm_ioctl) char buffer[DM_BUF_SIZE];
-    bool use_state = true;
     char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];
     char *mount_point;
     char propbuf[PROPERTY_VALUE_MAX];
@@ -793,15 +788,11 @@
     int i;
     int mode;
     int rc = -1;
-    off64_t offset = 0;
     struct dm_ioctl *io = (struct dm_ioctl *) buffer;
     struct fstab *fstab = NULL;
 
-    /* check if we need to store the state */
-    property_get("ro.boot.veritymode", propbuf, "");
-
-    if (*propbuf != '\0') {
-        use_state = false; /* state is kept by the bootloader */
+    if (!callback) {
+        return -1;
     }
 
     if (fs_mgr_load_verity_state(&mode) == -1) {
@@ -841,16 +832,7 @@
 
         status = &buffer[io->data_start + sizeof(struct dm_target_spec)];
 
-        if (use_state && *status == 'C') {
-            if (write_verity_state(fstab->recs[i].verity_loc, offset,
-                    VERITY_MODE_LOGGING) < 0) {
-                continue;
-            }
-        }
-
-        if (callback) {
-            callback(&fstab->recs[i], mount_point, mode, *status);
-        }
+        callback(&fstab->recs[i], mount_point, mode, *status);
     }
 
     rc = 0;
@@ -962,13 +944,43 @@
 
     // load the verity mapping table
     if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
-            format_verity_table) < 0 &&
-        // try the legacy format for backwards compatibility
-        load_verity_table(io, mount_point, verity.data_size, fd, &params,
-            format_legacy_verity_table) < 0) {
-        goto out;
+            format_verity_table) == 0) {
+        goto loaded;
     }
 
+    if (params.ecc.valid) {
+        // kernel may not support error correction, try without
+        INFO("Disabling error correction for %s\n", mount_point);
+        params.ecc.valid = false;
+
+        if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
+                format_verity_table) == 0) {
+            goto loaded;
+        }
+    }
+
+    // try the legacy format for backwards compatibility
+    if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
+            format_legacy_verity_table) == 0) {
+        goto loaded;
+    }
+
+    if (params.mode != VERITY_MODE_EIO) {
+        // as a last resort, EIO mode should always be supported
+        INFO("Falling back to EIO mode for %s\n", mount_point);
+        params.mode = VERITY_MODE_EIO;
+
+        if (load_verity_table(io, mount_point, verity.data_size, fd, &params,
+                format_legacy_verity_table) == 0) {
+            goto loaded;
+        }
+    }
+
+    ERROR("Failed to load verity table for %s\n", mount_point);
+    goto out;
+
+loaded:
+
     // activate the device
     if (resume_verity_table(io, mount_point, fd) < 0) {
         goto out;
diff --git a/include/log/logger.h b/include/log/logger.h
index b3928a7..60d47a2 100644
--- a/include/log/logger.h
+++ b/include/log/logger.h
@@ -64,12 +64,24 @@
     char        msg[0];    /* the entry's payload */
 } __attribute__((__packed__));
 
+struct logger_entry_v4 {
+    uint16_t    len;       /* length of the payload */
+    uint16_t    hdr_size;  /* sizeof(struct logger_entry_v4) */
+    int32_t     pid;       /* generating process's pid */
+    uint32_t    tid;       /* generating process's tid */
+    uint32_t    sec;       /* seconds since Epoch */
+    uint32_t    nsec;      /* nanoseconds */
+    uint32_t    lid;       /* log id of the payload, bottom 4 bits currently */
+    uint32_t    uid;       /* generating process's uid */
+    char        msg[0];    /* the entry's payload */
+} __attribute__((__packed__));
+
 /*
  * The maximum size of the log entry payload that can be
  * written to the logger. An attempt to write more than
  * this amount will result in a truncated log entry.
  */
-#define LOGGER_ENTRY_MAX_PAYLOAD	4076
+#define LOGGER_ENTRY_MAX_PAYLOAD	4068
 
 /*
  * The maximum size of a log entry which can be read from the
@@ -83,7 +95,8 @@
 struct log_msg {
     union {
         unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
-        struct logger_entry_v3 entry;
+        struct logger_entry_v4 entry;
+        struct logger_entry_v4 entry_v4;
         struct logger_entry_v3 entry_v3;
         struct logger_entry_v2 entry_v2;
         struct logger_entry    entry_v1;
diff --git a/include/log/logprint.h b/include/log/logprint.h
index 204b3f2..539d1dc 100644
--- a/include/log/logprint.h
+++ b/include/log/logprint.h
@@ -44,6 +44,7 @@
     FORMAT_MODIFIER_ZONE,      /* Adds zone to date */
     FORMAT_MODIFIER_EPOCH,     /* Print time as seconds since Jan 1 1970 */
     FORMAT_MODIFIER_MONOTONIC, /* Print cpu time as seconds since start */
+    FORMAT_MODIFIER_UID,       /* Adds uid */
 } AndroidLogPrintFormat;
 
 typedef struct AndroidLogFormat_t AndroidLogFormat;
@@ -52,6 +53,7 @@
     time_t tv_sec;
     long tv_nsec;
     android_LogPriority priority;
+    int32_t uid;
     int32_t pid;
     int32_t tid;
     const char * tag;
diff --git a/include/nativeloader/native_loader.h b/include/nativeloader/native_loader.h
index 18b7ba4..e7c69d6 100644
--- a/include/nativeloader/native_loader.h
+++ b/include/nativeloader/native_loader.h
@@ -24,7 +24,7 @@
 
 __attribute__((visibility("default")))
 void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
-                        jobject class_loader, jstring library_path);
+                        jobject class_loader, jstring library_path, jstring permitted_path);
 
 };  // namespace android
 
diff --git a/init/builtins.cpp b/init/builtins.cpp
index 58dbce1..10f9d81 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -642,7 +642,7 @@
 static int do_verity_load_state(const std::vector<std::string>& args) {
     int mode = -1;
     int rc = fs_mgr_load_verity_state(&mode);
-    if (rc == 0 && mode == VERITY_MODE_LOGGING) {
+    if (rc == 0 && mode != VERITY_MODE_DEFAULT) {
         ActionManager::GetInstance().QueueEventTrigger("verity-logging");
     }
     return rc;
diff --git a/init/init.cpp b/init/init.cpp
index 9f4f625..4aef823 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -350,6 +350,18 @@
     }
 }
 
+static void export_oem_lock_status() {
+    if (property_get("ro.oem_unlock_supported") != "1") {
+        return;
+    }
+
+    std::string value = property_get("ro.boot.verifiedbootstate");
+
+    if (!value.empty()) {
+        property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
+    }
+}
+
 static void export_kernel_boot_props() {
     struct {
         const char *src_prop;
@@ -614,6 +626,7 @@
     signal_handler_init();
 
     property_load_boot_defaults();
+    export_oem_lock_status();
     start_property_service();
 
     const BuiltinFunctionMap function_map;
diff --git a/libbacktrace/BacktraceOffline.cpp b/libbacktrace/BacktraceOffline.cpp
index 27dfb83..abc186b 100644
--- a/libbacktrace/BacktraceOffline.cpp
+++ b/libbacktrace/BacktraceOffline.cpp
@@ -22,7 +22,9 @@
 }
 
 #include <stdint.h>
+#include <stdio.h>
 #include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <ucontext.h>
 #include <unistd.h>
@@ -616,7 +618,30 @@
   return debug_frame;
 }
 
+static bool IsValidElfPath(const std::string& filename) {
+  static const char elf_magic[] = {0x7f, 'E', 'L', 'F'};
+
+  struct stat st;
+  if (stat(filename.c_str(), &st) != 0 || !S_ISREG(st.st_mode)) {
+    return false;
+  }
+  FILE* fp = fopen(filename.c_str(), "reb");
+  if (fp == nullptr) {
+    return false;
+  }
+  char buf[4];
+  if (fread(buf, 4, 1, fp) != 1) {
+    fclose(fp);
+    return false;
+  }
+  fclose(fp);
+  return memcmp(buf, elf_magic, 4) == 0;
+}
+
 static DebugFrameInfo* ReadDebugFrameFromFile(const std::string& filename) {
+  if (!IsValidElfPath(filename)) {
+    return nullptr;
+  }
   auto owning_binary = llvm::object::createBinary(llvm::StringRef(filename));
   if (owning_binary.getError()) {
     return nullptr;
diff --git a/liblog/log_read.c b/liblog/log_read.c
index 179f611..8aa887b 100644
--- a/liblog/log_read.c
+++ b/liblog/log_read.c
@@ -635,6 +635,7 @@
         android_log_header_t l;
     } buf;
     static uint8_t preread_count;
+    bool is_system;
 
     memset(log_msg, 0, sizeof(*log_msg));
 
@@ -691,12 +692,15 @@
             }
 
             uid = get_best_effective_uid();
-            if (!uid_has_log_permission(uid) && (uid != buf.p.uid)) {
+            is_system = uid_has_log_permission(uid);
+            if (!is_system && (uid != buf.p.uid)) {
                 break;
             }
 
             ret = TEMP_FAILURE_RETRY(read(logger_list->sock,
-                                          log_msg->entry_v3.msg,
+                                          is_system ?
+                                              log_msg->entry_v4.msg :
+                                              log_msg->entry_v3.msg,
                                           buf.p.len - sizeof(buf)));
             if (ret < 0) {
                 return -errno;
@@ -705,13 +709,18 @@
                 return -EIO;
             }
 
-            log_msg->entry_v3.len = buf.p.len - sizeof(buf);
-            log_msg->entry_v3.hdr_size = sizeof(log_msg->entry_v3);
-            log_msg->entry_v3.pid = buf.p.pid;
-            log_msg->entry_v3.tid = buf.l.tid;
-            log_msg->entry_v3.sec = buf.l.realtime.tv_sec;
-            log_msg->entry_v3.nsec = buf.l.realtime.tv_nsec;
-            log_msg->entry_v3.lid = buf.l.id;
+            log_msg->entry_v4.len = buf.p.len - sizeof(buf);
+            log_msg->entry_v4.hdr_size = is_system ?
+                sizeof(log_msg->entry_v4) :
+                sizeof(log_msg->entry_v3);
+            log_msg->entry_v4.pid = buf.p.pid;
+            log_msg->entry_v4.tid = buf.l.tid;
+            log_msg->entry_v4.sec = buf.l.realtime.tv_sec;
+            log_msg->entry_v4.nsec = buf.l.realtime.tv_nsec;
+            log_msg->entry_v4.lid = buf.l.id;
+            if (is_system) {
+                log_msg->entry_v4.uid = buf.p.uid;
+            }
 
             return ret;
         }
diff --git a/liblog/logprint.c b/liblog/logprint.c
index 40e13f4..746eb8a 100644
--- a/liblog/logprint.c
+++ b/liblog/logprint.c
@@ -32,6 +32,7 @@
 #include <cutils/list.h>
 #include <log/logd.h>
 #include <log/logprint.h>
+#include <private/android_filesystem_config.h>
 
 #define MS_PER_NSEC 1000000
 #define US_PER_NSEC 1000
@@ -56,6 +57,7 @@
     bool zone_output;
     bool epoch_output;
     bool monotonic_output;
+    bool uid_output;
 };
 
 /*
@@ -204,6 +206,7 @@
     p_ret->zone_output = false;
     p_ret->epoch_output = false;
     p_ret->monotonic_output = android_log_clockid() == CLOCK_MONOTONIC;
+    p_ret->uid_output = false;
 
     return p_ret;
 }
@@ -258,6 +261,9 @@
     case FORMAT_MODIFIER_MONOTONIC:
         p_format->monotonic_output = true;
         return 0;
+    case FORMAT_MODIFIER_UID:
+        p_format->uid_output = true;
+        return 0;
     default:
         break;
     }
@@ -290,6 +296,7 @@
     else if (strcmp(formatString, "zone") == 0) format = FORMAT_MODIFIER_ZONE;
     else if (strcmp(formatString, "epoch") == 0) format = FORMAT_MODIFIER_EPOCH;
     else if (strcmp(formatString, "monotonic") == 0) format = FORMAT_MODIFIER_MONOTONIC;
+    else if (strcmp(formatString, "uid") == 0) format = FORMAT_MODIFIER_UID;
     else {
         extern char *tzname[2];
         static const char gmt[] = "GMT";
@@ -451,6 +458,7 @@
 {
     entry->tv_sec = buf->sec;
     entry->tv_nsec = buf->nsec;
+    entry->uid = -1;
     entry->pid = buf->pid;
     entry->tid = buf->tid;
 
@@ -482,6 +490,9 @@
     struct logger_entry_v2 *buf2 = (struct logger_entry_v2 *)buf;
     if (buf2->hdr_size) {
         msg = ((char *)buf2) + buf2->hdr_size;
+        if (buf2->hdr_size >= sizeof(struct logger_entry_v4)) {
+            entry->uid = ((struct logger_entry_v4 *)buf)->uid;
+        }
     }
     for (i = 1; i < buf->len; i++) {
         if (msg[i] == '\0') {
@@ -734,6 +745,7 @@
     entry->tv_sec = buf->sec;
     entry->tv_nsec = buf->nsec;
     entry->priority = ANDROID_LOG_INFO;
+    entry->uid = -1;
     entry->pid = buf->pid;
     entry->tid = buf->tid;
 
@@ -744,6 +756,9 @@
     struct logger_entry_v2 *buf2 = (struct logger_entry_v2 *)buf;
     if (buf2->hdr_size) {
         eventData = ((unsigned char *)buf2) + buf2->hdr_size;
+        if (buf2->hdr_size >= sizeof(struct logger_entry_v4)) {
+            entry->uid = ((struct logger_entry_v4 *)buf)->uid;
+        }
     }
     inCount = buf->len;
     if (inCount < 4)
@@ -1238,7 +1253,7 @@
     char prefixBuf[128], suffixBuf[128];
     char priChar;
     int prefixSuffixIsHeaderFooter = 0;
-    char *ret = NULL;
+    char *ret;
     time_t now;
     unsigned long nsec;
 
@@ -1310,6 +1325,30 @@
         suffixLen = MIN(suffixLen, sizeof(suffixBuf));
     }
 
+    char uid[16];
+    uid[0] = '\0';
+    if (p_format->uid_output) {
+        if (entry->uid >= 0) {
+            const struct android_id_info *info = android_ids;
+            size_t i;
+
+            for (i = 0; i < android_id_count; ++i) {
+                if (info->aid == (unsigned int)entry->uid) {
+                    break;
+                }
+                ++info;
+            }
+            if ((i < android_id_count) && (strlen(info->name) <= 5)) {
+                 snprintf(uid, sizeof(uid), "%5s:", info->name);
+            } else {
+                 // Not worth parsing package list, names all longer than 5
+                 snprintf(uid, sizeof(uid), "%5d:", entry->uid);
+            }
+        } else {
+            snprintf(uid, sizeof(uid), "      ");
+        }
+    }
+
     switch (p_format->format) {
         case FORMAT_TAG:
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
@@ -1322,11 +1361,11 @@
                 "  (%s)\n", entry->tag);
             suffixLen += MIN(len, sizeof(suffixBuf) - suffixLen);
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c(%5d) ", priChar, entry->pid);
+                "%c(%s%5d) ", priChar, uid, entry->pid);
             break;
         case FORMAT_THREAD:
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c(%5d:%5d) ", priChar, entry->pid, entry->tid);
+                "%c(%s%5d:%5d) ", priChar, uid, entry->pid, entry->tid);
             strcpy(suffixBuf + suffixLen, "\n");
             ++suffixLen;
             break;
@@ -1338,21 +1377,26 @@
             break;
         case FORMAT_TIME:
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%s %c/%-8s(%5d): ", timeBuf, priChar, entry->tag, entry->pid);
+                "%s %c/%-8s(%s%5d): ", timeBuf, priChar, entry->tag,
+                uid, entry->pid);
             strcpy(suffixBuf + suffixLen, "\n");
             ++suffixLen;
             break;
         case FORMAT_THREADTIME:
+            ret = strchr(uid, ':');
+            if (ret) {
+                *ret = ' ';
+            }
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%s %5d %5d %c %-8s: ", timeBuf,
-                entry->pid, entry->tid, priChar, entry->tag);
+                "%s %s%5d %5d %c %-8s: ", timeBuf,
+                uid, entry->pid, entry->tid, priChar, entry->tag);
             strcpy(suffixBuf + suffixLen, "\n");
             ++suffixLen;
             break;
         case FORMAT_LONG:
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "[ %s %5d:%5d %c/%-8s ]\n",
-                timeBuf, entry->pid, entry->tid, priChar, entry->tag);
+                "[ %s %s%5d:%5d %c/%-8s ]\n",
+                timeBuf, uid, entry->pid, entry->tid, priChar, entry->tag);
             strcpy(suffixBuf + suffixLen, "\n\n");
             suffixLen += 2;
             prefixSuffixIsHeaderFooter = 1;
@@ -1360,7 +1404,7 @@
         case FORMAT_BRIEF:
         default:
             len = snprintf(prefixBuf + prefixLen, sizeof(prefixBuf) - prefixLen,
-                "%c/%-8s(%5d): ", priChar, entry->tag, entry->pid);
+                "%c/%-8s(%s%5d): ", priChar, entry->tag, uid, entry->pid);
             strcpy(suffixBuf + suffixLen, "\n");
             ++suffixLen;
             break;
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index 1046d25..621101c 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -433,8 +433,9 @@
 }
 
 static const char max_payload_tag[] = "TEST_max_payload_XXXX";
-static const char max_payload_buf[LOGGER_ENTRY_MAX_PAYLOAD
-    - sizeof(max_payload_tag) - 1] = "LEONATO\n\
+#define SIZEOF_MAX_PAYLOAD_BUF (LOGGER_ENTRY_MAX_PAYLOAD - \
+                                sizeof(max_payload_tag) - 1)
+static const char max_payload_buf[] = "LEONATO\n\
 I learn in this letter that Don Peter of Arragon\n\
 comes this night to Messina\n\
 MESSENGER\n\
@@ -560,7 +561,7 @@
 trouble: the fashion of the world is to avoid\n\
 cost, and you encounter it\n\
 LEONATO\n\
-Never came trouble to my house in the likeness";
+Never came trouble to my house in the likeness of your grace";
 
 TEST(liblog, max_payload) {
     pid_t pid = getpid();
@@ -619,7 +620,7 @@
 
     EXPECT_EQ(true, matches);
 
-    EXPECT_LE(sizeof(max_payload_buf), static_cast<size_t>(max_len));
+    EXPECT_LE(SIZEOF_MAX_PAYLOAD_BUF, static_cast<size_t>(max_len));
 }
 
 TEST(liblog, too_big_payload) {
@@ -1097,7 +1098,7 @@
     const int TAG = 123456782;
     const char SUBTAG[] = "test-subtag";
     const int UID = -1;
-    const int DATA_LEN = sizeof(max_payload_buf);
+    const int DATA_LEN = SIZEOF_MAX_PAYLOAD_BUF;
     struct logger_list *logger_list;
 
     pid_t pid = getpid();
@@ -1168,8 +1169,8 @@
         }
         eventData += dataLen;
 
-        // 4 bytes for the tag, and 512 bytes for the log since the max_payload_buf should be
-        // truncated.
+        // 4 bytes for the tag, and 512 bytes for the log since the
+        // max_payload_buf should be truncated.
         ASSERT_EQ(4 + 512, eventData - original);
 
         ++count;
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 654bcb8..cef2d75 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -55,10 +55,18 @@
  public:
   LibraryNamespaces() : initialized_(false) { }
 
-  android_namespace_t* GetOrCreate(JNIEnv* env, jobject class_loader, jstring library_path) {
-    ScopedUtfChars libraryPath(env, library_path);
+  android_namespace_t* GetOrCreate(JNIEnv* env, jobject class_loader,
+                                   jstring java_library_path,
+                                   jstring java_permitted_path) {
+    ScopedUtfChars library_path(env, java_library_path);
 
-    if (!initialized_ && !InitPublicNamespace(libraryPath.c_str())) {
+    std::string permitted_path;
+    if (java_permitted_path != nullptr) {
+      ScopedUtfChars path(env, java_permitted_path);
+      permitted_path = path.c_str();
+    }
+
+    if (!initialized_ && !InitPublicNamespace(library_path.c_str())) {
       return nullptr;
     }
 
@@ -73,8 +81,11 @@
     android_namespace_t* ns =
             android_create_namespace("classloader-namespace",
                                      nullptr,
-                                     libraryPath.c_str(),
-                                     true);
+                                     library_path.c_str(),
+                                     true,
+                                     java_permitted_path != nullptr ?
+                                        permitted_path.c_str() :
+                                        nullptr);
 
     namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), ns));
 
@@ -118,13 +129,16 @@
 
 
 void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
-                        jobject class_loader, jstring library_path) {
+                        jobject class_loader, jstring java_library_path,
+                        jstring java_permitted_path) {
 #if defined(__ANDROID__)
   if (target_sdk_version == 0 || class_loader == nullptr) {
     return dlopen(path, RTLD_NOW);
   }
 
-  android_namespace_t* ns = g_namespaces->GetOrCreate(env, class_loader, library_path);
+  android_namespace_t* ns =
+      g_namespaces->GetOrCreate(env, class_loader, java_library_path,
+                                java_permitted_path);
 
   if (ns == nullptr) {
     return nullptr;
@@ -136,7 +150,8 @@
 
   return android_dlopen_ext(path, RTLD_NOW, &extinfo);
 #else
-  UNUSED(env, target_sdk_version, class_loader, library_path);
+  UNUSED(env, target_sdk_version, class_loader,
+         java_library_path, java_permitted_path);
   return dlopen(path, RTLD_NOW);
 #endif
 }
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 8ceeba5..ddc91ca 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -265,7 +265,7 @@
                     "  -v <format>     Sets the log print format, where <format> is:\n"
                     "  --format=<format>\n"
                     "                      brief color epoch long monotonic printable process raw\n"
-                    "                      tag thread threadtime time usec UTC year zone\n\n"
+                    "                      tag thread threadtime time uid usec UTC year zone\n\n"
                     "  -D              print dividers between each log buffer\n"
                     "  --dividers\n"
                     "  -c              clear (flush) the entire log and exit\n"
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 3ce6b61..1e4f3b0 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -848,7 +848,7 @@
         pthread_mutex_unlock(&mLogElementsLock);
 
         // range locking in LastLogTimes looks after us
-        max = element->flushTo(reader, this);
+        max = element->flushTo(reader, this, privileged);
 
         if (max == element->FLUSH_ERROR) {
             return max;
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index f92a085..fde9ad7 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -197,21 +197,25 @@
     return retval;
 }
 
-uint64_t LogBufferElement::flushTo(SocketClient *reader, LogBuffer *parent) {
-    struct logger_entry_v3 entry;
+uint64_t LogBufferElement::flushTo(SocketClient *reader, LogBuffer *parent,
+                                   bool privileged) {
+    struct logger_entry_v4 entry;
 
-    memset(&entry, 0, sizeof(struct logger_entry_v3));
+    memset(&entry, 0, sizeof(struct logger_entry_v4));
 
-    entry.hdr_size = sizeof(struct logger_entry_v3);
+    entry.hdr_size = privileged ?
+                         sizeof(struct logger_entry_v4) :
+                         sizeof(struct logger_entry_v3);
     entry.lid = mLogId;
     entry.pid = mPid;
     entry.tid = mTid;
+    entry.uid = mUid;
     entry.sec = mRealTime.tv_sec;
     entry.nsec = mRealTime.tv_nsec;
 
     struct iovec iovec[2];
     iovec[0].iov_base = &entry;
-    iovec[0].iov_len = sizeof(struct logger_entry_v3);
+    iovec[0].iov_len = entry.hdr_size;
 
     char *buffer = NULL;
 
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 09987ea..e7f88b9 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -80,7 +80,7 @@
     uint32_t getTag(void) const;
 
     static const uint64_t FLUSH_ERROR;
-    uint64_t flushTo(SocketClient *writer, LogBuffer *parent);
+    uint64_t flushTo(SocketClient *writer, LogBuffer *parent, bool privileged);
 };
 
 #endif
diff --git a/logd/LogKlog.cpp b/logd/LogKlog.cpp
index 2a3f52f..db7e682 100644
--- a/logd/LogKlog.cpp
+++ b/logd/LogKlog.cpp
@@ -778,6 +778,31 @@
     memcpy(np, p, b);
     np[b] = '\0';
 
+    if (!isMonotonic()) {
+        // Watch out for singular race conditions with timezone causing near
+        // integer quarter-hour jumps in the time and compensate accordingly.
+        // Entries will be temporal within near_seconds * 2. b/21868540
+        static uint32_t vote_time[3];
+        vote_time[2] = vote_time[1];
+        vote_time[1] = vote_time[0];
+        vote_time[0] = now.tv_sec;
+
+        if (vote_time[1] && vote_time[2]) {
+            static const unsigned near_seconds = 10;
+            static const unsigned timezones_seconds = 900;
+            int diff0 = (vote_time[0] - vote_time[1]) / near_seconds;
+            unsigned abs0 = (diff0 < 0) ? -diff0 : diff0;
+            int diff1 = (vote_time[1] - vote_time[2]) / near_seconds;
+            unsigned abs1 = (diff1 < 0) ? -diff1 : diff1;
+            if ((abs1 <= 1) && // last two were in agreement on timezone
+                    ((abs0 + 1) % (timezones_seconds / near_seconds)) <= 2) {
+                abs0 = (abs0 + 1) / (timezones_seconds / near_seconds) *
+                                     timezones_seconds;
+                now.tv_sec -= (diff0 < 0) ? -abs0 : abs0;
+            }
+        }
+    }
+
     // Log message
     int rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,
                          (unsigned short) n);
diff --git a/logd/main.cpp b/logd/main.cpp
index 8e75b37..0f55d60 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -349,7 +349,7 @@
         memset(&p, 0, sizeof(p));
         p.fd = sock;
         p.events = POLLIN;
-        ret = TEMP_FAILURE_RETRY(poll(&p, 1, 100));
+        ret = TEMP_FAILURE_RETRY(poll(&p, 1, 1000));
         if (ret < 0) {
             return -errno;
         }
diff --git a/metricsd/Android.mk b/metricsd/Android.mk
index 839ab65..2149a4b 100644
--- a/metricsd/Android.mk
+++ b/metricsd/Android.mk
@@ -28,6 +28,7 @@
   collectors/cpu_usage_collector.cc \
   collectors/disk_usage_collector.cc \
   metrics_collector.cc \
+  metrics_collector_service_trampoline.cc \
   persistent_integer.cc
 
 metricsd_common := \
@@ -69,6 +70,7 @@
   $(LOCAL_PATH)/include
 libmetrics_shared_libraries := libchrome libbinder libbrillo libutils
 metrics_collector_shared_libraries := $(libmetrics_shared_libraries) \
+  libbrillo-binder \
   libbrillo-dbus \
   libbrillo-http \
   libchrome-dbus \
@@ -77,6 +79,8 @@
   librootdev \
   libweaved
 
+metrics_collector_static_libraries := libmetricscollectorservice
+
 metricsd_shared_libraries := \
   libbinder \
   libbrillo \
@@ -86,7 +90,7 @@
   libupdate_engine_client \
   libutils
 
-# Static proxy library for the binder interface.
+# Static proxy library for the metricsd binder interface.
 # ========================================================
 include $(CLEAR_VARS)
 LOCAL_MODULE := metricsd_binder_proxy
@@ -94,6 +98,21 @@
 LOCAL_SRC_FILES := aidl/android/brillo/metrics/IMetricsd.aidl
 include $(BUILD_STATIC_LIBRARY)
 
+# Static library for the metrics_collector binder interface.
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetricscollectorservice
+LOCAL_SHARED_LIBRARIES := libbinder libbrillo-binder libchrome libutils
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SRC_FILES := \
+  aidl/android/brillo/metrics/IMetricsCollectorService.aidl \
+  metrics_collector_service_impl.cc \
+  metrics_collector_service_client.cc
+LOCAL_RTTI_FLAG := -fno-rtti
+include $(BUILD_STATIC_LIBRARY)
+
 # Shared library for metrics.
 # ========================================================
 include $(CLEAR_VARS)
@@ -146,14 +165,13 @@
 LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
 LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
 LOCAL_INIT_RC := metrics_collector.rc
-LOCAL_REQUIRED_MODULES := \
-  metrics.json \
-  metrics.schema.json
+LOCAL_REQUIRED_MODULES := metrics.json
 LOCAL_RTTI_FLAG := -frtti
 LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
 LOCAL_SRC_FILES := $(metrics_collector_common) \
   metrics_collector_main.cc
-LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy \
+  $(metrics_collector_static_libraries)
 include $(BUILD_EXECUTABLE)
 
 # metricsd daemon.
@@ -199,7 +217,8 @@
 LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
 LOCAL_SRC_FILES := $(metrics_collector_tests_sources) \
   $(metrics_collector_common)
-LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy
+LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \
+  $(metrics_collector_static_libraries)
 include $(BUILD_NATIVE_TEST)
 
 # Weave schema files
@@ -207,13 +226,6 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := metrics.json
 LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/commands
-LOCAL_SRC_FILES := etc/weaved/commands/$(LOCAL_MODULE)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := metrics.schema.json
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/states
-LOCAL_SRC_FILES := etc/weaved/states/$(LOCAL_MODULE)
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits
+LOCAL_SRC_FILES := etc/weaved/traits/$(LOCAL_MODULE)
 include $(BUILD_PREBUILT)
diff --git a/metricsd/aidl/android/brillo/metrics/IMetricsCollectorService.aidl b/metricsd/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
new file mode 100644
index 0000000..49f484f
--- /dev/null
+++ b/metricsd/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.brillo.metrics;
+
+interface IMetricsCollectorService {
+  oneway void notifyUserCrash();
+}
diff --git a/metricsd/etc/weaved/commands/metrics.json b/metricsd/etc/weaved/commands/metrics.json
deleted file mode 100644
index b7f32d5..0000000
--- a/metricsd/etc/weaved/commands/metrics.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "_metrics": {
-    "_enableAnalyticsReporting": {
-      "minimalRole": "manager"
-    },
-    "_disableAnalyticsReporting": {
-      "minimalRole": "manager"
-    }
-  }
-}
diff --git a/metricsd/etc/weaved/states/metrics.schema.json b/metricsd/etc/weaved/states/metrics.schema.json
deleted file mode 100644
index 130ac46..0000000
--- a/metricsd/etc/weaved/states/metrics.schema.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "_metrics": {
-    "_AnalyticsReportingState": {
-      "enum": ["enabled", "disabled"],
-      "default": "disabled"
-    }
-  }
-}
diff --git a/metricsd/etc/weaved/traits/metrics.json b/metricsd/etc/weaved/traits/metrics.json
new file mode 100644
index 0000000..7d17c77
--- /dev/null
+++ b/metricsd/etc/weaved/traits/metrics.json
@@ -0,0 +1,18 @@
+{
+  "_metrics": {
+    "commands": {
+      "_enableAnalyticsReporting": {
+        "minimalRole": "manager"
+      },
+      "_disableAnalyticsReporting": {
+        "minimalRole": "manager"
+      }
+    },
+    "state": {
+      "_AnalyticsReportingState": {
+        "type": "string",
+        "enum": [ "enabled", "disabled" ]
+      }
+    }
+  }
+}
diff --git a/metricsd/include/metrics/metrics_collector_service_client.h b/metricsd/include/metrics/metrics_collector_service_client.h
new file mode 100644
index 0000000..c800eae
--- /dev/null
+++ b/metricsd/include/metrics/metrics_collector_service_client.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#ifndef METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+#define METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+class MetricsCollectorServiceClient {
+ public:
+  MetricsCollectorServiceClient() = default;
+  ~MetricsCollectorServiceClient() = default;
+
+  // Initialize.  Returns true if OK, or false if IMetricsCollectorService
+  // is not registered.
+  bool Init();
+
+  // Called by crash_reporter to report a userspace crash event.  Returns
+  // true if successfully called the IMetricsCollectorService method of the
+  // same name, or false if the service was not registered at Init() time.
+  bool notifyUserCrash();
+
+ private:
+  // IMetricsCollectorService binder proxy
+  android::sp<android::brillo::metrics::IMetricsCollectorService>
+      metrics_collector_service_;
+};
+
+#endif  // METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
diff --git a/metricsd/metrics_collector.cc b/metricsd/metrics_collector.cc
index 28f9ad3..b5c2289 100644
--- a/metricsd/metrics_collector.cc
+++ b/metricsd/metrics_collector.cc
@@ -33,6 +33,7 @@
 #include <dbus/message.h>
 
 #include "constants.h"
+#include "metrics_collector_service_trampoline.h"
 
 using base::FilePath;
 using base::StringPrintf;
@@ -40,19 +41,12 @@
 using base::TimeDelta;
 using base::TimeTicks;
 using chromeos_metrics::PersistentInteger;
-using com::android::Weave::CommandProxy;
-using com::android::Weave::ManagerProxy;
 using std::map;
 using std::string;
 using std::vector;
 
 namespace {
 
-const char kCrashReporterInterface[] = "org.chromium.CrashReporter";
-const char kCrashReporterUserCrashSignal[] = "UserCrash";
-const char kCrashReporterMatchRule[] =
-    "type='signal',interface='%s',path='/',member='%s'";
-
 const int kSecondsPerMinute = 60;
 const int kMinutesPerHour = 60;
 const int kHoursPerDay = 24;
@@ -73,6 +67,8 @@
 const char kMeminfoFileName[] = "/proc/meminfo";
 const char kVmStatFileName[] = "/proc/vmstat";
 
+const char kWeaveComponent[] = "metrics";
+
 }  // namespace
 
 // Zram sysfs entries.
@@ -130,6 +126,10 @@
     version_cumulative_cpu_use_->Set(0);
   }
 
+  // Start metricscollectorservice via trampoline
+  MetricsCollectorServiceTrampoline metricscollectorservice_trampoline(this);
+  metricscollectorservice_trampoline.Run();
+
   return brillo::DBusDaemon::Run();
 }
 
@@ -223,35 +223,16 @@
   bus_->AssertOnDBusThread();
   CHECK(bus_->SetUpAsyncOperations());
 
-  if (bus_->is_connected()) {
-    const std::string match_rule =
-        base::StringPrintf(kCrashReporterMatchRule,
-                           kCrashReporterInterface,
-                           kCrashReporterUserCrashSignal);
-
-    bus_->AddFilterFunction(&MetricsCollector::MessageFilter, this);
-
-    DBusError error;
-    dbus_error_init(&error);
-    bus_->AddMatch(match_rule, &error);
-
-    if (dbus_error_is_set(&error)) {
-      LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got "
-          << error.name << ": " << error.message;
-      return EX_SOFTWARE;
-    }
-  } else {
-    LOG(ERROR) << "DBus isn't connected.";
-    return EX_UNAVAILABLE;
-  }
-
   device_ = weaved::Device::CreateInstance(
       bus_,
       base::Bind(&MetricsCollector::UpdateWeaveState, base::Unretained(this)));
+  device_->AddComponent(kWeaveComponent, {"_metrics"});
   device_->AddCommandHandler(
+      kWeaveComponent,
       "_metrics._enableAnalyticsReporting",
       base::Bind(&MetricsCollector::OnEnableMetrics, base::Unretained(this)));
   device_->AddCommandHandler(
+      kWeaveComponent,
       "_metrics._disableAnalyticsReporting",
       base::Bind(&MetricsCollector::OnDisableMetrics, base::Unretained(this)));
 
@@ -265,23 +246,6 @@
 }
 
 void MetricsCollector::OnShutdown(int* return_code) {
-  if (!testing_ && bus_->is_connected()) {
-    const std::string match_rule =
-        base::StringPrintf(kCrashReporterMatchRule,
-                           kCrashReporterInterface,
-                           kCrashReporterUserCrashSignal);
-
-    bus_->RemoveFilterFunction(&MetricsCollector::MessageFilter, this);
-
-    DBusError error;
-    dbus_error_init(&error);
-    bus_->RemoveMatch(match_rule, &error);
-
-    if (dbus_error_is_set(&error)) {
-      LOG(ERROR) << "Failed to remove match rule \"" << match_rule << "\". Got "
-          << error.name << ": " << error.message;
-    }
-  }
   brillo::DBusDaemon::OnShutdown(return_code);
 }
 
@@ -326,46 +290,17 @@
   if (!device_)
     return;
 
-  brillo::VariantDictionary state_change{
-    { "_metrics._AnalyticsReportingState",
-      metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled" }
-  };
+  std::string enabled =
+      metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled";
 
-  if (!device_->SetStateProperties(state_change, nullptr)) {
+  if (!device_->SetStateProperty(kWeaveComponent,
+                                 "_metrics._AnalyticsReportingState",
+                                 enabled,
+                                 nullptr)) {
     LOG(ERROR) << "failed to update weave's state";
   }
 }
 
-// static
-DBusHandlerResult MetricsCollector::MessageFilter(DBusConnection* connection,
-                                                   DBusMessage* message,
-                                                   void* user_data) {
-  int message_type = dbus_message_get_type(message);
-  if (message_type != DBUS_MESSAGE_TYPE_SIGNAL) {
-    DLOG(WARNING) << "unexpected message type " << message_type;
-    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-  }
-
-  // Signal messages always have interfaces.
-  const std::string interface(dbus_message_get_interface(message));
-  const std::string member(dbus_message_get_member(message));
-  DLOG(INFO) << "Got " << interface << "." << member << " D-Bus signal";
-
-  MetricsCollector* daemon = static_cast<MetricsCollector*>(user_data);
-
-  DBusMessageIter iter;
-  dbus_message_iter_init(message, &iter);
-  if (interface == kCrashReporterInterface) {
-    CHECK_EQ(member, kCrashReporterUserCrashSignal);
-    daemon->ProcessUserCrash();
-  } else {
-    // Ignore messages from the bus itself.
-    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-  }
-
-  return DBUS_HANDLER_RESULT_HANDLED;
-}
-
 void MetricsCollector::ProcessUserCrash() {
   // Counts the active time up to now.
   UpdateStats(TimeTicks::Now(), Time::Now());
diff --git a/metricsd/metrics_collector.h b/metricsd/metrics_collector.h
index 69747d0..422ed7c 100644
--- a/metricsd/metrics_collector.h
+++ b/metricsd/metrics_collector.h
@@ -63,6 +63,10 @@
   // Returns the active time since boot (uptime minus sleep time) in seconds.
   static double GetActiveTime();
 
+  // Updates the active use time and logs time between user-space
+  // process crashes.  Called via MetricsCollectorServiceTrampoline.
+  void ProcessUserCrash();
+
  protected:
   // Used also by the unit tests.
   static const char kComprDataSizeName[];
@@ -108,11 +112,6 @@
     int value;               // value from /proc/meminfo
   };
 
-  // D-Bus filter callback.
-  static DBusHandlerResult MessageFilter(DBusConnection* connection,
-                                         DBusMessage* message,
-                                         void* user_data);
-
   // Enables metrics reporting.
   void OnEnableMetrics(const std::weak_ptr<weaved::Command>& cmd);
 
@@ -122,10 +121,6 @@
   // Updates the weave device state.
   void UpdateWeaveState();
 
-  // Updates the active use time and logs time between user-space
-  // process crashes.
-  void ProcessUserCrash();
-
   // Updates the active use time and logs time between kernel crashes.
   void ProcessKernelCrash();
 
diff --git a/metricsd/metrics_collector_service_client.cc b/metricsd/metrics_collector_service_client.cc
new file mode 100644
index 0000000..08aaa4a
--- /dev/null
+++ b/metricsd/metrics_collector_service_client.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#include "metrics/metrics_collector_service_client.h"
+
+#include <base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+namespace {
+const char kMetricsCollectorServiceName[] =
+    "android.brillo.metrics.IMetricsCollectorService";
+}
+
+bool MetricsCollectorServiceClient::Init() {
+  const android::String16 name(kMetricsCollectorServiceName);
+  metrics_collector_service_ = android::interface_cast<
+      android::brillo::metrics::IMetricsCollectorService>(
+      android::defaultServiceManager()->checkService(name));
+
+  if (metrics_collector_service_ == nullptr)
+    LOG(ERROR) << "Unable to lookup service " << kMetricsCollectorServiceName;
+
+  return metrics_collector_service_ != nullptr;
+}
+
+bool MetricsCollectorServiceClient::notifyUserCrash() {
+  if (metrics_collector_service_ == nullptr)
+    return false;
+
+  metrics_collector_service_->notifyUserCrash();
+  return true;
+}
diff --git a/metricsd/metrics_collector_service_impl.cc b/metricsd/metrics_collector_service_impl.cc
new file mode 100644
index 0000000..dbb0578
--- /dev/null
+++ b/metricsd/metrics_collector_service_impl.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics_collector_service_impl.h"
+
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <brillo/binder_watcher.h>
+#include <utils/Errors.h>
+
+#include "metrics_collector_service_trampoline.h"
+
+using namespace android;
+
+BnMetricsCollectorServiceImpl::BnMetricsCollectorServiceImpl(
+    MetricsCollectorServiceTrampoline* metrics_collector_service_trampoline) {
+  metrics_collector_service_trampoline_ = metrics_collector_service_trampoline;
+}
+
+void BnMetricsCollectorServiceImpl::Run() {
+  status_t status =
+      defaultServiceManager()->addService(getInterfaceDescriptor(), this);
+  CHECK(status == OK) << "libmetricscollectorservice: failed to add service";
+  binder_watcher_.reset(new ::brillo::BinderWatcher);
+  CHECK(binder_watcher_->Init()) << "Binder FD watcher init failed";
+}
+
+android::binder::Status BnMetricsCollectorServiceImpl::notifyUserCrash() {
+  metrics_collector_service_trampoline_->ProcessUserCrash();
+  return android::binder::Status::ok();
+}
diff --git a/metricsd/metrics_collector_service_impl.h b/metricsd/metrics_collector_service_impl.h
new file mode 100644
index 0000000..bdcab50
--- /dev/null
+++ b/metricsd/metrics_collector_service_impl.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+#define METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+
+// metrics_collector binder service implementation.  Constructed by
+// MetricsCollectorServiceTrampoline, which we use to call back into
+// MetricsCollector.  The trampoline isolates us from the -frtti code of
+// metrics_collector / libbrillo.
+
+#include "android/brillo/metrics/BnMetricsCollectorService.h"
+
+#include <memory>
+
+#include <binder/Status.h>
+#include <brillo/binder_watcher.h>
+
+class MetricsCollectorServiceTrampoline;
+
+//#include "metrics_collector_service_trampoline.h"
+
+class BnMetricsCollectorServiceImpl
+    : public android::brillo::metrics::BnMetricsCollectorService {
+ public:
+  // Passed a this pointer from the MetricsCollectorServiceTrampoline
+  // object that constructs us.
+  explicit BnMetricsCollectorServiceImpl(
+      MetricsCollectorServiceTrampoline* metrics_collector_service_trampoline);
+
+  virtual ~BnMetricsCollectorServiceImpl() = default;
+
+  // Starts the binder main loop.
+  void Run();
+
+  // Called by crash_reporter to report a userspace crash event.  We relay
+  // this to MetricsCollector using the trampoline.
+  android::binder::Status notifyUserCrash();
+
+ private:
+  // Trampoline object that constructs us, we use this to call MetricsCollector
+  // methods via the trampoline.
+  MetricsCollectorServiceTrampoline* metrics_collector_service_trampoline_;
+
+  // BinderWatcher object we construct for handling Binder traffic
+  std::unique_ptr<brillo::BinderWatcher> binder_watcher_;
+};
+
+#endif  // METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
diff --git a/metricsd/metrics_collector_service_trampoline.cc b/metricsd/metrics_collector_service_trampoline.cc
new file mode 100644
index 0000000..12b80a1
--- /dev/null
+++ b/metricsd/metrics_collector_service_trampoline.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "metrics_collector_service_trampoline.h"
+#include "metrics_collector.h"
+#include "metrics_collector_service_impl.h"
+
+MetricsCollectorServiceTrampoline::MetricsCollectorServiceTrampoline(
+    MetricsCollector* metrics_collector) {
+  metrics_collector_ = metrics_collector;
+}
+
+void MetricsCollectorServiceTrampoline::Run() {
+  // Start metricscollectorservice binder service
+  metrics_collector_service.reset(new BnMetricsCollectorServiceImpl(this));
+  metrics_collector_service->Run();
+}
+
+void MetricsCollectorServiceTrampoline::ProcessUserCrash() {
+  metrics_collector_->ProcessUserCrash();
+}
diff --git a/metricsd/metrics_collector_service_trampoline.h b/metricsd/metrics_collector_service_trampoline.h
new file mode 100644
index 0000000..5da9fa5
--- /dev/null
+++ b/metricsd/metrics_collector_service_trampoline.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef METRICSD_METRICS_COLLECTOR_SERVICE_TRAMPOLINE_H_
+#define METRICSD_METRICS_COLLECTOR_SERVICE_TRAMPOLINE_H_
+
+// Trampoline between the -fno-rtti compile of libmetricsservice and the
+// -frtti compile of metrics_collector.  MetricsCollectorServiceTrampoline
+// is called from MetricsCollector to run the IMetricsCollectorService
+// server, and acts as a go-between for calls from server back to
+// MetricsCollector.
+
+#include <memory>
+
+#include "metrics_collector_service_impl.h"
+
+// Forward declaration of MetricsCollector.  Don't include the header file
+// for the class here, as it pulls in -frtti stuff.
+class MetricsCollector;
+
+class MetricsCollectorServiceTrampoline {
+ public:
+  // Constructor take a this pointer from the MetricsCollector class that
+  // constructs these objects.
+  explicit MetricsCollectorServiceTrampoline(
+      MetricsCollector* metrics_collector);
+
+  // Initialize and run the IMetricsCollectorService
+  void Run();
+
+  // Called from IMetricsCollectorService to trampoline into the
+  // MetricsCollector method of the same name.
+  void ProcessUserCrash();
+
+ private:
+  // The MetricsCollector object that constructs us, for which we act as
+  // the go-between for MetricsCollectorServiceImpl use.
+  MetricsCollector* metrics_collector_;
+
+  // The IMetricsCollectorService implementation we construct.
+  std::unique_ptr<BnMetricsCollectorServiceImpl> metrics_collector_service;
+};
+
+#endif  // METRICSD_METRICS_COLLECTOR_SERVICE_TRAMPOLINE_H_
diff --git a/metricsd/metrics_collector_test.cc b/metricsd/metrics_collector_test.cc
index 956e56b..5fb3ac8 100644
--- a/metricsd/metrics_collector_test.cc
+++ b/metricsd/metrics_collector_test.cc
@@ -115,37 +115,6 @@
   StrictMock<MetricsLibraryMock> metrics_lib_;
 };
 
-TEST_F(MetricsCollectorTest, MessageFilter) {
-  // Ignore calls to SendToUMA.
-  EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AnyNumber());
-
-  DBusMessage* msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
-  DBusHandlerResult res =
-      MetricsCollector::MessageFilter(/* connection */ nullptr, msg, &daemon_);
-  EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
-  DeleteDBusMessage(msg);
-
-  vector<string> signal_args;
-  msg = NewDBusSignalString("/",
-                            "org.chromium.CrashReporter",
-                            "UserCrash",
-                            signal_args);
-  res = MetricsCollector::MessageFilter(/* connection */ nullptr, msg, &daemon_);
-  EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
-  DeleteDBusMessage(msg);
-
-  signal_args.clear();
-  signal_args.push_back("randomstate");
-  signal_args.push_back("bob");  // arbitrary username
-  msg = NewDBusSignalString("/",
-                            "org.chromium.UnknownService.Manager",
-                            "StateChanged",
-                            signal_args);
-  res = MetricsCollector::MessageFilter(/* connection */ nullptr, msg, &daemon_);
-  EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
-  DeleteDBusMessage(msg);
-}
-
 TEST_F(MetricsCollectorTest, SendSample) {
   ExpectSample("Dummy.Metric", 3);
   daemon_.SendSample("Dummy.Metric", /* sample */ 3,
diff --git a/metricsd/uploader/bn_metricsd_impl.cc b/metricsd/uploader/bn_metricsd_impl.cc
index 113a705..2cbc2da 100644
--- a/metricsd/uploader/bn_metricsd_impl.cc
+++ b/metricsd/uploader/bn_metricsd_impl.cc
@@ -21,6 +21,7 @@
 #include <base/metrics/statistics_recorder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
+#include <utils/Errors.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
 
@@ -33,11 +34,14 @@
 
 BnMetricsdImpl::BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters)
     : counters_(counters) {
-  CHECK(counters_);
+  CHECK(counters_) << "Invalid counters argument to constructor";
 }
 
 void BnMetricsdImpl::Run() {
-  android::defaultServiceManager()->addService(getInterfaceDescriptor(), this);
+  android::status_t status =
+      android::defaultServiceManager()->addService(getInterfaceDescriptor(),
+                                                   this);
+  CHECK(status == android::OK) << "Metricsd service registration failed";
   android::ProcessState::self()->setThreadPoolMaxThreadCount(0);
   android::IPCThreadState::self()->disableBackgroundScheduling(true);
   android::IPCThreadState::self()->joinThreadPool();
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b40b9ba..011defb 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -493,6 +493,8 @@
     class_start core
 
 on nonencrypted
+    # A/B update verifier that marks a successful boot.
+    exec - root -- /system/bin/update_verifier nonencrypted
     class_start main
     class_start late_start
 
@@ -521,9 +523,13 @@
     trigger post-fs-data
 
 on property:vold.decrypt=trigger_restart_min_framework
+    # A/B update verifier that marks a successful boot.
+    exec - root -- /system/bin/update_verifier trigger_restart_min_framework
     class_start main
 
 on property:vold.decrypt=trigger_restart_framework
+    # A/B update verifier that marks a successful boot.
+    exec - root -- /system/bin/update_verifier trigger_restart_framework
     class_start main
     class_start late_start