[automerger skipped] DO NOT MERGE ANYWHERE resolve merge conflicts of bcf5e2388e608e4ec4372d1eeb918bbe0e095b2e to oc-dr1-dev am: 2ad06909c9 -s ours am skip reason: subject contains skip directive
am: 1fb9beca3f -s ours
am skip reason: subject contains skip directive

Change-Id: Idf3e102c8176259eba32d253cd0d127f8d08d21b
diff --git a/cmds/atrace/Android.bp b/cmds/atrace/Android.bp
index b3cbdef..bb84a18 100644
--- a/cmds/atrace/Android.bp
+++ b/cmds/atrace/Android.bp
@@ -3,6 +3,10 @@
 cc_binary {
     name: "atrace",
     srcs: ["atrace.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 
     shared_libs: [
         "libbinder",
@@ -14,8 +18,6 @@
         "libcutils",
         "libz",
         "libbase",
-    ],
-    static_libs: [
         "libpdx_default_transport",
     ],
 
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 9cdc9e9..31e73fb 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -56,9 +56,9 @@
 using std::string;
 
 #define MAX_SYS_FILES 10
-#define MAX_PACKAGES 16
 
 const char* k_traceTagsProperty = "debug.atrace.tags.enableflags";
+const char* k_userInitiatedTraceProperty = "debug.atrace.user_initiated";
 
 const char* k_traceAppsNumberProperty = "debug.atrace.app_number";
 const char* k_traceAppsPropertyTemplate = "debug.atrace.app_%d";
@@ -94,6 +94,7 @@
 static const TracingCategory k_categories[] = {
     { "gfx",        "Graphics",         ATRACE_TAG_GRAPHICS, {
         { OPT,      "events/mdss/enable" },
+        { OPT,      "events/sde/enable" },
     } },
     { "input",      "Input",            ATRACE_TAG_INPUT, { } },
     { "view",       "View System",      ATRACE_TAG_VIEW, { } },
@@ -105,7 +106,6 @@
     { "video",      "Video",            ATRACE_TAG_VIDEO, { } },
     { "camera",     "Camera",           ATRACE_TAG_CAMERA, { } },
     { "hal",        "Hardware Modules", ATRACE_TAG_HAL, { } },
-    { "app",        "Application",      ATRACE_TAG_APP, { } },
     { "res",        "Resource Loading", ATRACE_TAG_RESOURCES, { } },
     { "dalvik",     "Dalvik VM",        ATRACE_TAG_DALVIK, { } },
     { "rs",         "RenderScript",     ATRACE_TAG_RS, { } },
@@ -116,6 +116,8 @@
     { "database",   "Database",         ATRACE_TAG_DATABASE, { } },
     { "network",    "Network",          ATRACE_TAG_NETWORK, { } },
     { "adb",        "ADB",              ATRACE_TAG_ADB, { } },
+    { "vibrator",   "Vibrator",         ATRACE_TAG_VIBRATOR, { } },
+    { "aidl",       "AIDL calls",       ATRACE_TAG_AIDL, { } },
     { k_coreServiceCategory, "Core services", 0, { } },
     { k_pdxServiceCategory, "PDX services", 0, { } },
     { "sched",      "CPU Scheduling",   0, {
@@ -124,12 +126,21 @@
         { OPT,      "events/sched/sched_waking/enable" },
         { OPT,      "events/sched/sched_blocked_reason/enable" },
         { OPT,      "events/sched/sched_cpu_hotplug/enable" },
+        { OPT,      "events/sched/sched_pi_setprio/enable" },
         { OPT,      "events/cgroup/enable" },
     } },
     { "irq",        "IRQ Events",   0, {
         { REQ,      "events/irq/enable" },
         { OPT,      "events/ipi/enable" },
     } },
+    { "irqoff",     "IRQ-disabled code section tracing", 0, {
+        { REQ,      "events/preemptirq/irq_enable/enable" },
+        { REQ,      "events/preemptirq/irq_disable/enable" },
+    } },
+    { "preemptoff", "Preempt-disabled code section tracing", 0, {
+        { REQ,      "events/preemptirq/preempt_enable/enable" },
+        { REQ,      "events/preemptirq/preempt_disable/enable" },
+    } },
     { "i2c",        "I2C Events",   0, {
         { REQ,      "events/i2c/enable" },
         { REQ,      "events/i2c/i2c_read/enable" },
@@ -144,6 +155,11 @@
     { "freq",       "CPU Frequency",    0, {
         { REQ,      "events/power/cpu_frequency/enable" },
         { OPT,      "events/power/clock_set_rate/enable" },
+        { OPT,      "events/power/clock_disable/enable" },
+        { OPT,      "events/power/clock_enable/enable" },
+        { OPT,      "events/clk/clk_set_rate/enable" },
+        { OPT,      "events/clk/clk_disable/enable" },
+        { OPT,      "events/clk/clk_enable/enable" },
         { OPT,      "events/power/cpu_frequency_limits/enable" },
     } },
     { "membus",     "Memory Bus Utilization", 0, {
@@ -171,7 +187,10 @@
         { REQ,      "events/cpufreq_interactive/enable" },
     } },
     { "sync",       "Synchronization",  0, {
-        { REQ,      "events/sync/enable" },
+        // before linux kernel 4.9
+        { OPT,      "events/sync/enable" },
+        // starting in linux kernel 4.9
+        { OPT,      "events/fence/enable" },
     } },
     { "workq",      "Kernel Workqueues", 0, {
         { REQ,      "events/workqueue/enable" },
@@ -181,7 +200,7 @@
         { REQ,      "events/vmscan/mm_vmscan_direct_reclaim_end/enable" },
         { REQ,      "events/vmscan/mm_vmscan_kswapd_wake/enable" },
         { REQ,      "events/vmscan/mm_vmscan_kswapd_sleep/enable" },
-        { REQ,      "events/lowmemorykiller/enable" },
+        { OPT,      "events/lowmemorykiller/enable" },
     } },
     { "regulators",  "Voltage and Current Regulators", 0, {
         { REQ,      "events/regulator/enable" },
@@ -226,8 +245,11 @@
 static const char* k_traceBufferSizePath =
     "buffer_size_kb";
 
+#if 0
+// TODO: Re-enable after stabilization
 static const char* k_traceCmdlineSizePath =
     "saved_cmdlines_size";
+#endif
 
 static const char* k_tracingOverwriteEnablePath =
     "options/overwrite";
@@ -250,9 +272,6 @@
 static const char* k_funcgraphFlatPath =
     "options/funcgraph-flat";
 
-static const char* k_funcgraphDurationPath =
-    "options/funcgraph-duration";
-
 static const char* k_ftraceFilterPath =
     "set_ftrace_filter";
 
@@ -429,6 +448,16 @@
     return setKernelOptionEnable(k_tracingOverwriteEnablePath, enable);
 }
 
+// Set the user initiated trace property
+static bool setUserInitiatedTraceProperty(bool enable)
+{
+    if (!android::base::SetProperty(k_userInitiatedTraceProperty, enable ? "1" : "")) {
+        fprintf(stderr, "error setting user initiated strace system property\n");
+        return false;
+    }
+    return true;
+}
+
 // Enable or disable kernel tracing.
 static bool setTracingEnabled(bool enable)
 {
@@ -445,7 +474,6 @@
 static bool setTraceBufferSizeKB(int size)
 {
     char str[32] = "1";
-    int len;
     if (size < 1) {
         size = 1;
     }
@@ -453,6 +481,8 @@
     return writeStr(k_traceBufferSizePath, str);
 }
 
+#if 0
+// TODO: Re-enable after stabilization
 // Set the default size of cmdline hashtable
 static bool setCmdlineSize()
 {
@@ -461,6 +491,7 @@
     }
     return true;
 }
+#endif
 
 // Set the clock to the best available option while tracing. Use 'boot' if it's
 // available; otherwise, use 'mono'. If neither are available use 'global'.
@@ -481,8 +512,8 @@
         newClock = "global";
     }
 
-    size_t begin = clockStr.find("[") + 1;
-    size_t end = clockStr.find("]");
+    size_t begin = clockStr.find('[') + 1;
+    size_t end = clockStr.find(']');
     if (newClock.compare(0, std::string::npos, clockStr, begin, end-begin) == 0) {
         return true;
     }
@@ -543,7 +574,7 @@
     auto listRet = sm->list([&](const auto &interfaces) {
         for (size_t i = 0; i < interfaces.size(); i++) {
             string fqInstanceName = interfaces[i];
-            string::size_type n = fqInstanceName.find("/");
+            string::size_type n = fqInstanceName.find('/');
             if (n == std::string::npos || interfaces[i].size() == n+1)
                 continue;
             hidl_string fqInterfaceName = fqInstanceName.substr(0, n);
@@ -586,12 +617,6 @@
 
 static void clearAppProperties()
 {
-    for (int i = 0; i < MAX_PACKAGES; i++) {
-        std::string key = android::base::StringPrintf(k_traceAppsPropertyTemplate, i);
-        if (!android::base::SetProperty(key, "")) {
-            fprintf(stderr, "failed to clear system property: %s\n", key.c_str());
-        }
-    }
     if (!android::base::SetProperty(k_traceAppsNumberProperty, "")) {
         fprintf(stderr, "failed to clear system property: %s",
               k_traceAppsNumberProperty);
@@ -605,11 +630,6 @@
     int i = 0;
     char* start = cmdline;
     while (start != NULL) {
-        if (i == MAX_PACKAGES) {
-            fprintf(stderr, "error: only 16 packages could be traced at once\n");
-            clearAppProperties();
-            return false;
-        }
         char* end = strchr(start, ',');
         if (end != NULL) {
             *end = '\0';
@@ -769,22 +789,10 @@
     return ok;
 }
 
-// Set all the kernel tracing settings to the desired state for this trace
-// capture.
-static bool setUpTrace()
+static bool setUpUserspaceTracing()
 {
     bool ok = true;
 
-    // Set up the tracing options.
-    ok &= setCategoriesEnableFromFile(g_categoriesFile);
-    ok &= setTraceOverwriteEnable(g_traceOverwrite);
-    ok &= setTraceBufferSizeKB(g_traceBufferSizeKB);
-    // TODO: Re-enable after stabilization
-    //ok &= setCmdlineSize();
-    ok &= setClock();
-    ok &= setPrintTgidEnableIfPresent(true);
-    ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
-
     // Set up the tags property.
     uint64_t tags = 0;
     for (size_t i = 0; i < arraysize(k_categories); i++) {
@@ -822,6 +830,39 @@
         ok &= ServiceUtility::PokeServices();
     }
 
+    return ok;
+}
+
+static void cleanUpUserspaceTracing()
+{
+    setTagsProperty(0);
+    clearAppProperties();
+    pokeBinderServices();
+
+    if (g_tracePdx) {
+        ServiceUtility::PokeServices();
+    }
+}
+
+
+// Set all the kernel tracing settings to the desired state for this trace
+// capture.
+static bool setUpKernelTracing()
+{
+    bool ok = true;
+
+    ok &= setUserInitiatedTraceProperty(true);
+
+    // Set up the tracing options.
+    ok &= setCategoriesEnableFromFile(g_categoriesFile);
+    ok &= setTraceOverwriteEnable(g_traceOverwrite);
+    ok &= setTraceBufferSizeKB(g_traceBufferSizeKB);
+    // TODO: Re-enable after stabilization
+    //ok &= setCmdlineSize();
+    ok &= setClock();
+    ok &= setPrintTgidEnableIfPresent(true);
+    ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
+
     // Disable all the sysfs enables.  This is done as a separate loop from
     // the enables to allow the same enable to exist in multiple categories.
     ok &= disableKernelTraceEvents();
@@ -849,28 +890,19 @@
 }
 
 // Reset all the kernel tracing settings to their default state.
-static void cleanUpTrace()
+static void cleanUpKernelTracing()
 {
     // Disable all tracing that we're able to.
     disableKernelTraceEvents();
 
-    // Reset the system properties.
-    setTagsProperty(0);
-    clearAppProperties();
-    pokeBinderServices();
-
-    if (g_tracePdx) {
-        ServiceUtility::PokeServices();
-    }
-
     // Set the options back to their defaults.
     setTraceOverwriteEnable(true);
     setTraceBufferSizeKB(1);
     setPrintTgidEnableIfPresent(false);
     setKernelTraceFuncs(NULL);
+    setUserInitiatedTraceProperty(false);
 }
 
-
 // Enable tracing in the kernel.
 static bool startTrace()
 {
@@ -1047,7 +1079,7 @@
     fprintf(stderr, "usage: %s [options] [categories...]\n", cmd);
     fprintf(stderr, "options include:\n"
                     "  -a appname      enable app-level tracing for a comma "
-                        "separated list of cmdlines\n"
+                        "separated list of cmdlines; * is a wildcard matching any process\n"
                     "  -b N            use a trace buffer size of N KB\n"
                     "  -c              trace into a circular buffer\n"
                     "  -f filename     use the categories written in a file as space-separated\n"
@@ -1102,6 +1134,7 @@
     bool traceStop = true;
     bool traceDump = true;
     bool traceStream = false;
+    bool onlyUserspace = false;
 
     if (argc == 2 && 0 == strcmp(argv[1], "--help")) {
         showHelp(argv[0]);
@@ -1117,12 +1150,13 @@
         int ret;
         int option_index = 0;
         static struct option long_options[] = {
-            {"async_start",     no_argument, 0,  0 },
-            {"async_stop",      no_argument, 0,  0 },
-            {"async_dump",      no_argument, 0,  0 },
-            {"list_categories", no_argument, 0,  0 },
-            {"stream",          no_argument, 0,  0 },
-            {           0,                0, 0,  0 }
+            {"async_start",       no_argument, 0,  0 },
+            {"async_stop",        no_argument, 0,  0 },
+            {"async_dump",        no_argument, 0,  0 },
+            {"only_userspace",    no_argument, 0,  0 },
+            {"list_categories",   no_argument, 0,  0 },
+            {"stream",            no_argument, 0,  0 },
+            {           0,                  0, 0,  0 }
         };
 
         ret = getopt_long(argc, argv, "a:b:cf:k:ns:t:zo:",
@@ -1192,6 +1226,8 @@
                     async = true;
                     traceStart = false;
                     traceStop = false;
+                } else if (!strcmp(long_options[option_index].name, "only_userspace")) {
+                    onlyUserspace = true;
                 } else if (!strcmp(long_options[option_index].name, "stream")) {
                     traceStream = true;
                     traceDump = false;
@@ -1209,6 +1245,14 @@
         }
     }
 
+    if (onlyUserspace) {
+        if (!async || !(traceStart || traceStop)) {
+            fprintf(stderr, "--only_userspace can only be used with "
+                    "--async_start or --async_stop\n");
+            exit(1);
+        }
+    }
+
     registerSigHandler();
 
     if (g_initialSleepSecs > 0) {
@@ -1216,11 +1260,19 @@
     }
 
     bool ok = true;
-    ok &= setUpTrace();
-    ok &= startTrace();
+
+    if (traceStart) {
+        ok &= setUpUserspaceTracing();
+    }
+
+    if (ok && traceStart && !onlyUserspace) {
+        ok &= setUpKernelTracing();
+        ok &= startTrace();
+    }
 
     if (ok && traceStart) {
-        if (!traceStream) {
+
+        if (!traceStream && !onlyUserspace) {
             printf("capturing trace...");
             fflush(stdout);
         }
@@ -1230,9 +1282,12 @@
         // contain entries from only one CPU can cause "begin" entries without a
         // matching "end" entry to show up if a task gets migrated from one CPU to
         // another.
-        ok = clearTrace();
+        if (!onlyUserspace)
+            ok = clearTrace();
 
-        writeClockSyncMarker();
+        if (!onlyUserspace)
+            writeClockSyncMarker();
+
         if (ok && !async && !traceStream) {
             // Sleep to allow the trace to be captured.
             struct timespec timeLeft;
@@ -1251,10 +1306,10 @@
     }
 
     // Stop the trace and restore the default settings.
-    if (traceStop)
+    if (traceStop && !onlyUserspace)
         stopTrace();
 
-    if (ok && traceDump) {
+    if (ok && traceDump && !onlyUserspace) {
         if (!g_traceAborted) {
             printf(" done\n");
             fflush(stdout);
@@ -1281,8 +1336,11 @@
     }
 
     // Reset the trace buffer size to 1.
-    if (traceStop)
-        cleanUpTrace();
+    if (traceStop) {
+        cleanUpUserspaceTracing();
+        if (!onlyUserspace)
+            cleanUpKernelTracing();
+    }
 
     return g_traceAborted ? 1 : 0;
 }
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index 3ea1d56..d3d0711 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -1,146 +1,129 @@
 ## Permissions to allow system-wide tracing to the kernel trace buffer.
 ##
-on post-fs
+on late-init
 
 # Allow writing to the kernel trace log.
     chmod 0222 /sys/kernel/debug/tracing/trace_marker
     chmod 0222 /sys/kernel/tracing/trace_marker
 
-# Allow the shell group to enable (some) kernel tracing.
-    chown root shell /sys/kernel/debug/tracing/trace_clock
-    chown root shell /sys/kernel/tracing/trace_clock
-    chown root shell /sys/kernel/debug/tracing/buffer_size_kb
-    chown root shell /sys/kernel/tracing/buffer_size_kb
-    chown root shell /sys/kernel/debug/tracing/options/overwrite
-    chown root shell /sys/kernel/tracing/options/overwrite
-    chown root shell /sys/kernel/debug/tracing/options/print-tgid
-    chown root shell /sys/kernel/tracing/options/print-tgid
-    chown root shell /sys/kernel/debug/tracing/saved_cmdlines_size
-    chown root shell /sys/kernel/tracing/saved_cmdlines_size
-    chown root shell /sys/kernel/debug/tracing/events/sched/sched_switch/enable
-    chown root shell /sys/kernel/tracing/events/sched/sched_switch/enable
-    chown root shell /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
-    chown root shell /sys/kernel/tracing/events/sched/sched_wakeup/enable
-    chown root shell /sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable
-    chown root shell /sys/kernel/tracing/events/sched/sched_blocked_reason/enable
-    chown root shell /sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable
-    chown root shell /sys/kernel/tracing/events/sched/sched_cpu_hotplug/enable
-    chown root shell /sys/kernel/debug/tracing/events/cgroup/enable
-    chown root shell /sys/kernel/tracing/events/cgroup/enable
-    chown root shell /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
-    chown root shell /sys/kernel/tracing/events/power/cpu_frequency/enable
-    chown root shell /sys/kernel/debug/tracing/events/power/cpu_idle/enable
-    chown root shell /sys/kernel/tracing/events/power/cpu_idle/enable
-    chown root shell /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
-    chown root shell /sys/kernel/tracing/events/power/clock_set_rate/enable
-    chown root shell /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
-    chown root shell /sys/kernel/tracing/events/power/cpu_frequency_limits/enable
-    chown root shell /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
-    chown root shell /sys/kernel/tracing/events/cpufreq_interactive/enable
-    chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
-    chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
-    chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
-    chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
-    chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
-    chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
-    chown root shell /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
-    chown root shell /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
-    chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
-    chown root shell /sys/kernel/tracing/events/binder/binder_transaction/enable
-    chown root shell /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
-    chown root shell /sys/kernel/tracing/events/binder/binder_transaction_received/enable
-    chown root shell /sys/kernel/debug/tracing/events/binder/binder_lock/enable
-    chown root shell /sys/kernel/tracing/events/binder/binder_lock/enable
-    chown root shell /sys/kernel/debug/tracing/events/binder/binder_locked/enable
-    chown root shell /sys/kernel/tracing/events/binder/binder_locked/enable
-    chown root shell /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
-    chown root shell /sys/kernel/tracing/events/binder/binder_unlock/enable
-    chown root shell /sys/kernel/debug/tracing/events/lowmemorykiller/enable
-    chown root shell /sys/kernel/tracing/events/lowmemorykiller/enable
+# Scheduler tracepoints require schedstats=enable
+    write /proc/sys/kernel/sched_schedstats 1
 
-    chown root shell /sys/kernel/debug/tracing/tracing_on
-    chown root shell /sys/kernel/tracing/tracing_on
+# Grant unix world read/write permissions to kernel tracepoints.
+# Access control to these files is now entirely in selinux policy.
+    chmod 0666 /sys/kernel/debug/tracing/trace_clock
+    chmod 0666 /sys/kernel/tracing/trace_clock
+    chmod 0666 /sys/kernel/debug/tracing/buffer_size_kb
+    chmod 0666 /sys/kernel/tracing/buffer_size_kb
+    chmod 0666 /sys/kernel/debug/tracing/options/overwrite
+    chmod 0666 /sys/kernel/tracing/options/overwrite
+    chmod 0666 /sys/kernel/debug/tracing/options/print-tgid
+    chmod 0666 /sys/kernel/tracing/options/print-tgid
+    chmod 0666 /sys/kernel/debug/tracing/saved_cmdlines_size
+    chmod 0666 /sys/kernel/tracing/saved_cmdlines_size
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_switch/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_switch/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_wakeup/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_blocked_reason/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_cpu_hotplug/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sched/sched_pi_setprio/enable
+    chmod 0666 /sys/kernel/tracing/events/sched/sched_pi_setprio/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/cgroup/enable
+    chmod 0666 /sys/kernel/tracing/events/cgroup/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
+    chmod 0666 /sys/kernel/tracing/events/power/cpu_frequency/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_idle/enable
+    chmod 0666 /sys/kernel/tracing/events/power/cpu_idle/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
+    chmod 0666 /sys/kernel/tracing/events/power/clock_set_rate/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
+    chmod 0666 /sys/kernel/tracing/events/power/cpu_frequency_limits/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
+    chmod 0666 /sys/kernel/tracing/events/cpufreq_interactive/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+    chmod 0666 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+    chmod 0666 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+    chmod 0666 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
+    chmod 0666 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
+    chmod 0666 /sys/kernel/debug/tracing/tracing_on
+    chmod 0666 /sys/kernel/tracing/tracing_on
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_transaction/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_transaction_received/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_lock/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_lock/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_locked/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_locked/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
+    chmod 0666 /sys/kernel/tracing/events/binder/binder_unlock/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/i2c_read/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_write/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/i2c_write/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_result/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/i2c_result/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_reply/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/i2c_reply/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/smbus_read/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/smbus_read/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/smbus_write/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/smbus_write/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/smbus_result/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/smbus_result/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/i2c/smbus_reply/enable
+    chmod 0666 /sys/kernel/tracing/events/i2c/smbus_reply/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/lowmemorykiller/enable
+    chmod 0666 /sys/kernel/tracing/events/lowmemorykiller/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sync/enable
+    chmod 0666 /sys/kernel/tracing/events/sync/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/fence/enable
+    chmod 0666 /sys/kernel/tracing/events/fence/enable
 
-    chmod 0664 /sys/kernel/debug/tracing/trace_clock
-    chmod 0664 /sys/kernel/tracing/trace_clock
-    chmod 0664 /sys/kernel/debug/tracing/buffer_size_kb
-    chmod 0664 /sys/kernel/tracing/buffer_size_kb
-    chmod 0664 /sys/kernel/debug/tracing/options/overwrite
-    chmod 0664 /sys/kernel/tracing/options/overwrite
-    chmod 0664 /sys/kernel/debug/tracing/options/print-tgid
-    chmod 0664 /sys/kernel/tracing/options/print-tgid
-    chmod 0664 /sys/kernel/debug/tracing/saved_cmdlines_size
-    chmod 0664 /sys/kernel/tracing/saved_cmdlines_size
-    chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_switch/enable
-    chmod 0664 /sys/kernel/tracing/events/sched/sched_switch/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
-    chmod 0664 /sys/kernel/tracing/events/sched/sched_wakeup/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_blocked_reason/enable
-    chmod 0664 /sys/kernel/tracing/events/sched/sched_blocked_reason/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/sched/sched_cpu_hotplug/enable
-    chmod 0664 /sys/kernel/tracing/events/sched/sched_cpu_hotplug/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/cgroup/enable
-    chmod 0664 /sys/kernel/tracing/events/cgroup/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_frequency/enable
-    chmod 0664 /sys/kernel/tracing/events/power/cpu_frequency/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_idle/enable
-    chmod 0664 /sys/kernel/tracing/events/power/cpu_idle/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/power/clock_set_rate/enable
-    chmod 0664 /sys/kernel/tracing/events/power/clock_set_rate/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/power/cpu_frequency_limits/enable
-    chmod 0664 /sys/kernel/tracing/events/power/cpu_frequency_limits/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/cpufreq_interactive/enable
-    chmod 0664 /sys/kernel/tracing/events/cpufreq_interactive/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
-    chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_begin/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
-    chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_direct_reclaim_end/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
-    chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_wake/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
-    chmod 0664 /sys/kernel/tracing/events/vmscan/mm_vmscan_kswapd_sleep/enable
-    chmod 0664 /sys/kernel/debug/tracing/tracing_on
-    chmod 0664 /sys/kernel/tracing/tracing_on
-    chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction/enable
-    chmod 0664 /sys/kernel/tracing/events/binder/binder_transaction/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_transaction_received/enable
-    chmod 0664 /sys/kernel/tracing/events/binder/binder_transaction_received/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_lock/enable
-    chmod 0664 /sys/kernel/tracing/events/binder/binder_lock/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_locked/enable
-    chmod 0664 /sys/kernel/tracing/events/binder/binder_locked/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/binder/binder_unlock/enable
-    chmod 0664 /sys/kernel/tracing/events/binder/binder_unlock/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/i2c_read/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_write/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/i2c_write/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_result/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/i2c_result/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/i2c_reply/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/i2c_reply/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_read/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/smbus_read/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_write/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/smbus_write/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_result/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/smbus_result/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/i2c/smbus_reply/enable
-    chmod 0664 /sys/kernel/tracing/events/i2c/smbus_reply/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/lowmemorykiller/enable
-    chmod 0664 /sys/kernel/tracing/events/lowmemorykiller/enable
 
-    # Tracing disabled by default
+    # disk
+    chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_sync_file_enter/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
+    chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_sync_file_exit/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
+    chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_write_begin/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable
+    chmod 0666 /sys/kernel/tracing/events/f2fs/f2fs_write_end/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable
+    chmod 0666 /sys/kernel/tracing/events/ext4/ext4_da_write_begin/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable
+    chmod 0666 /sys/kernel/tracing/events/ext4/ext4_da_write_end/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable
+    chmod 0666 /sys/kernel/tracing/events/ext4/ext4_sync_file_enter/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
+    chmod 0666 /sys/kernel/tracing/events/ext4/ext4_sync_file_exit/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable
+    chmod 0666 /sys/kernel/tracing/events/block/block_rq_issue/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
+    chmod 0666 /sys/kernel/tracing/events/block/block_rq_complete/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
+
+    # graphics
+    chmod 0666 /sys/kernel/tracing/events/sde/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/sde/enable
+    chmod 0666 /sys/kernel/tracing/events/mdss/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/mdss/enable
+
+# Tracing disabled by default
     write /sys/kernel/debug/tracing/tracing_on 0
     write /sys/kernel/tracing/tracing_on 0
 
-# Allow only the shell group to read and truncate the kernel trace.
-    chown root shell /sys/kernel/debug/tracing/trace
-    chown root shell /sys/kernel/tracing/trace
-    chmod 0660 /sys/kernel/debug/tracing/trace
-    chmod 0660 /sys/kernel/tracing/trace
+# Read and truncate the kernel trace.
+    chmod 0666 /sys/kernel/debug/tracing/trace
+    chmod 0666 /sys/kernel/tracing/trace
 
 on property:persist.debug.atrace.boottrace=1
     start boottrace
diff --git a/cmds/atrace/atrace_userdebug.rc b/cmds/atrace/atrace_userdebug.rc
index 5fd28e2..f4e5b98 100644
--- a/cmds/atrace/atrace_userdebug.rc
+++ b/cmds/atrace/atrace_userdebug.rc
@@ -1,47 +1,19 @@
 ## Permissions to allow additional system-wide tracing to the kernel trace buffer.
 ## The default list of permissions is set in frameworks/native/cmds/atrace/atrace.rc
 
-# Allow the shell group to enable kernel tracepoints:
+# Grant unix world read/write permissions to enable kernel tracepoints.
+# Access control to these files is now entirely in selinux policy.
 
 on post-fs
-    chown root shell /sys/kernel/debug/tracing/events/sync/enable
-    chown root shell /sys/kernel/debug/tracing/events/workqueue/enable
-    chown root shell /sys/kernel/debug/tracing/events/regulator/enable
-    chown root shell /sys/kernel/debug/tracing/events/pagecache/enable
+    chmod 0666 /sys/kernel/tracing/events/workqueue/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/workqueue/enable
+    chmod 0666 /sys/kernel/tracing/events/regulator/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/regulator/enable
+    chmod 0666 /sys/kernel/tracing/events/pagecache/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/pagecache/enable
 
     # irq
-    chown root shell /sys/kernel/debug/tracing/events/irq/enable
-    chown root shell /sys/kernel/debug/tracing/events/ipi/enable
-
-    # disk
-    chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
-    chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
-    chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable
-    chown root shell /sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable
-    chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable
-    chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable
-    chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
-    chown root shell /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable
-    chown root shell /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
-    chown root shell /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
-
-    chmod 0664 /sys/kernel/debug/tracing/events/sync/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/workqueue/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/regulator/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/pagecache/enable
-
-    # irq
-    chmod 0664 /sys/kernel/debug/tracing/events/irq/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/ipi/enable
-
-    # disk
-    chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/block/block_rq_issue/enable
-    chmod 0664 /sys/kernel/debug/tracing/events/block/block_rq_complete/enable
+    chmod 0666 /sys/kernel/tracing/events/irq/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/irq/enable
+    chmod 0666 /sys/kernel/tracing/events/ipi/enable
+    chmod 0666 /sys/kernel/debug/tracing/events/ipi/enable
diff --git a/cmds/bugreport/Android.bp b/cmds/bugreport/Android.bp
index 139e4b2..24044a6 100644
--- a/cmds/bugreport/Android.bp
+++ b/cmds/bugreport/Android.bp
@@ -1,6 +1,6 @@
 cc_binary {
     name: "bugreport",
     srcs: ["bugreport.cpp"],
-    cflags: ["-Wall"],
+    cflags: ["-Wall", "-Werror"],
     shared_libs: ["libcutils"],
 }
diff --git a/cmds/cmd/Android.mk b/cmds/cmd/Android.mk
index d565e57..4868555 100644
--- a/cmds/cmd/Android.mk
+++ b/cmds/cmd/Android.mk
@@ -10,6 +10,8 @@
     libselinux \
 	libbinder
 
+LOCAL_CFLAGS := -Wall -Werror
+
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE)
 
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index 014c995..48d5d4a 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -61,7 +61,8 @@
 public:
     bool mActive = true;
 
-    virtual int openOutputFile(const String16& path, const String16& seLinuxContext) {
+    virtual int openFile(const String16& path, const String16& seLinuxContext,
+            const String16& mode) {
         String8 path8(path);
         char cwd[256];
         getcwd(cwd, 256);
@@ -71,23 +72,67 @@
             aerr << "Open attempt after active for: " << fullPath << endl;
             return -EPERM;
         }
-        int fd = open(fullPath.string(), O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG);
+#if DEBUG
+        ALOGD("openFile: %s, full=%s", path8.string(), fullPath.string());
+#endif
+        int flags = 0;
+        bool checkRead = false;
+        bool checkWrite = false;
+        if (mode == String16("w")) {
+            flags = O_WRONLY|O_CREAT|O_TRUNC;
+            checkWrite = true;
+        } else if (mode == String16("w+")) {
+            flags = O_RDWR|O_CREAT|O_TRUNC;
+            checkRead = checkWrite = true;
+        } else if (mode == String16("r")) {
+            flags = O_RDONLY;
+            checkRead = true;
+        } else if (mode == String16("r+")) {
+            flags = O_RDWR;
+            checkRead = checkWrite = true;
+        } else {
+            aerr << "Invalid mode requested: " << mode.string() << endl;
+            return -EINVAL;
+        }
+        int fd = open(fullPath.string(), flags, S_IRWXU|S_IRWXG);
+#if DEBUG
+        ALOGD("openFile: fd=%d", fd);
+#endif
         if (fd < 0) {
             return fd;
         }
         if (is_selinux_enabled() && seLinuxContext.size() > 0) {
             String8 seLinuxContext8(seLinuxContext);
             security_context_t tmp = NULL;
-            int ret = getfilecon(fullPath.string(), &tmp);
+            getfilecon(fullPath.string(), &tmp);
             Unique_SecurityContext context(tmp);
-            int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
-                    "file", "write", NULL);
-            if (accessGranted != 0) {
-                close(fd);
-                aerr << "System server has no access to file context " << context.get()
-                        << " (from path " << fullPath.string() << ", context "
-                        << seLinuxContext8.string() << ")" << endl;
-                return -EPERM;
+            if (checkWrite) {
+                int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+                        "file", "write", NULL);
+                if (accessGranted != 0) {
+#if DEBUG
+                    ALOGD("openFile: failed selinux write check!");
+#endif
+                    close(fd);
+                    aerr << "System server has no access to write file context " << context.get()
+                            << " (from path " << fullPath.string() << ", context "
+                            << seLinuxContext8.string() << ")" << endl;
+                    return -EPERM;
+                }
+            }
+            if (checkRead) {
+                int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+                        "file", "read", NULL);
+                if (accessGranted != 0) {
+#if DEBUG
+                    ALOGD("openFile: failed selinux read check!");
+#endif
+                    close(fd);
+                    aerr << "System server has no access to read file context " << context.get()
+                            << " (from path " << fullPath.string() << ", context "
+                            << seLinuxContext8.string() << ")" << endl;
+                    return -EPERM;
+                }
             }
         }
         return fd;
@@ -131,6 +176,9 @@
     proc->setThreadPoolMaxThreadCount(0);
     proc->startThreadPool();
 
+#if DEBUG
+    ALOGD("cmd: starting");
+#endif
     sp<IServiceManager> sm = defaultServiceManager();
     fflush(stdout);
     if (sm == NULL) {
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index f908cbf..b04543b 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -14,7 +14,7 @@
 // limitations under the License.
 
 cc_defaults {
-    name: "dumpstate_defaults",
+    name: "dumpstate_cflag_defaults",
     cflags: [
         "-Wall",
         "-Werror",
@@ -24,29 +24,13 @@
     ],
 }
 
-cc_library_headers {
-    name: "dumpstate_headers",
-    vendor_available: true,
-    export_include_dirs: ["."],
-    header_libs: [
-        "libbase_headers",
-        "libutils_headers",
-    ],
-    export_header_lib_headers: [
-        "libbase_headers",
-        "libutils_headers",
-    ],
-}
-
 cc_library_shared {
     name: "libdumpstateutil",
-    defaults: ["dumpstate_defaults"],
+    defaults: ["dumpstate_cflag_defaults"],
     vendor_available: true,
     vndk: {
         enabled: true,
     },
-    header_libs: ["dumpstate_headers"],
-    export_header_lib_headers: ["dumpstate_headers"],
     srcs: [
         "DumpstateInternal.cpp",
         "DumpstateUtil.cpp",
@@ -55,11 +39,15 @@
         "libbase",
         "liblog",
     ],
+    export_include_dirs: ["."],
+    export_shared_lib_headers: [
+        "libbase",
+    ],
 }
 
 cc_library_shared {
     name: "libdumpstateaidl",
-    defaults: ["dumpstate_defaults"],
+    defaults: ["dumpstate_cflag_defaults"],
     shared_libs: [
         "libbinder",
         "libutils",
@@ -75,10 +63,9 @@
     ],
 }
 
-cc_binary {
-    name: "dumpstate",
-    defaults: ["dumpstate_defaults"],
-    header_libs: ["dumpstate_headers"],
+cc_defaults {
+    name: "dumpstate_defaults",
+    defaults: ["dumpstate_cflag_defaults"],
     shared_libs: [
         "android.hardware.dumpstate@1.0",
         "libziparchive",
@@ -89,16 +76,29 @@
         "libdebuggerd_client",
         "libdumpstateaidl",
         "libdumpstateutil",
+        "libdumputils",
         "libhidlbase",
         "libhidltransport",
         "liblog",
         "libutils",
     ],
     srcs: [
-        "DumpstateInternal.cpp",
+        "DumpstateSectionReporter.cpp",
         "DumpstateService.cpp",
         "utils.cpp",
+    ],
+    static_libs: [
+        "libdumpsys",
+        "libserviceutils"
+    ],
+}
+
+cc_binary {
+    name: "dumpstate",
+    defaults: ["dumpstate_defaults"],
+    srcs: [
         "dumpstate.cpp",
+        "main.cpp",
     ],
     init_rc: ["dumpstate.rc"],
 }
@@ -106,25 +106,18 @@
 cc_test {
     name: "dumpstate_test",
     defaults: ["dumpstate_defaults"],
-    header_libs: ["dumpstate_headers"],
-    shared_libs: [
-        "libziparchive",
-        "libbase",
-        "libbinder",
-        "libcutils",
-        "libdebuggerd_client",
-        "libdumpstateaidl",
-        "libdumpstateutil",
-        "libhidlbase",
-        "libhidltransport",
-        "liblog",
-        "libutils",
-    ],
     srcs: [
-        "DumpstateInternal.cpp",
-        "DumpstateService.cpp",
-        "utils.cpp",
         "tests/dumpstate_test.cpp",
     ],
     static_libs: ["libgmock"],
 }
+
+cc_test {
+    name: "dumpstate_smoke_test",
+    defaults: ["dumpstate_defaults"],
+    srcs: [
+        "dumpstate.cpp",
+        "tests/dumpstate_smoke_test.cpp",
+    ],
+    static_libs: ["libgmock"],
+}
diff --git a/cmds/dumpstate/DumpstateInternal.cpp b/cmds/dumpstate/DumpstateInternal.cpp
index f0b6203..33e35f7 100644
--- a/cmds/dumpstate/DumpstateInternal.cpp
+++ b/cmds/dumpstate/DumpstateInternal.cpp
@@ -18,7 +18,9 @@
 
 #include "DumpstateInternal.h"
 
+#include <errno.h>
 #include <grp.h>
+#include <poll.h>
 #include <pwd.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -34,6 +36,7 @@
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/macros.h>
 #include <log/log.h>
 
 uint64_t Nanotime() {
@@ -97,13 +100,25 @@
     capheader.version = _LINUX_CAPABILITY_VERSION_3;
     capheader.pid = 0;
 
-    capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
-    capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
-    capdata[0].inheritable = 0;
-    capdata[1].inheritable = 0;
+    if (capget(&capheader, &capdata[0]) != 0) {
+        MYLOGE("capget failed: %s\n", strerror(errno));
+        return false;
+    }
 
-    if (capset(&capheader, &capdata[0]) < 0) {
-        MYLOGE("capset failed: %s\n", strerror(errno));
+    const uint32_t cap_syslog_mask = CAP_TO_MASK(CAP_SYSLOG);
+    const uint32_t cap_syslog_index = CAP_TO_INDEX(CAP_SYSLOG);
+    bool has_cap_syslog = (capdata[cap_syslog_index].effective & cap_syslog_mask) != 0;
+
+    memset(&capdata, 0, sizeof(capdata));
+    if (has_cap_syslog) {
+        // Only attempt to keep CAP_SYSLOG if it was present to begin with.
+        capdata[cap_syslog_index].permitted |= cap_syslog_mask;
+        capdata[cap_syslog_index].effective |= cap_syslog_mask;
+    }
+
+    if (capset(&capheader, &capdata[0]) != 0) {
+        MYLOGE("capset({%#x, %#x}) failed: %s\n", capdata[0].effective,
+               capdata[1].effective, strerror(errno));
         return false;
     }
 
@@ -141,22 +156,16 @@
         return 0;
     }
     bool newline = false;
-    fd_set read_set;
-    timeval tm;
     while (true) {
-        FD_ZERO(&read_set);
-        FD_SET(fd, &read_set);
-        /* Timeout if no data is read for 30 seconds. */
-        tm.tv_sec = 30;
-        tm.tv_usec = 0;
-        uint64_t elapsed = Nanotime();
-        int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, nullptr, nullptr, &tm));
+        uint64_t start_time = Nanotime();
+        pollfd fds[] = { { .fd = fd, .events = POLLIN } };
+        int ret = TEMP_FAILURE_RETRY(poll(fds, arraysize(fds), 30 * 1000));
         if (ret == -1) {
-            dprintf(out_fd, "*** %s: select failed: %s\n", path, strerror(errno));
+            dprintf(out_fd, "*** %s: poll failed: %s\n", path, strerror(errno));
             newline = true;
             break;
         } else if (ret == 0) {
-            elapsed = Nanotime() - elapsed;
+            uint64_t elapsed = Nanotime() - start_time;
             dprintf(out_fd, "*** %s: Timed out after %.3fs\n", path, (float)elapsed / NANOS_PER_SEC);
             newline = true;
             break;
@@ -175,7 +184,6 @@
             }
         }
     }
-    close(fd);
 
     if (!newline) dprintf(out_fd, "\n");
     if (!title.empty()) dprintf(out_fd, "\n");
diff --git a/cmds/dumpstate/DumpstateSectionReporter.cpp b/cmds/dumpstate/DumpstateSectionReporter.cpp
new file mode 100644
index 0000000..f814bde
--- /dev/null
+++ b/cmds/dumpstate/DumpstateSectionReporter.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include "DumpstateSectionReporter.h"
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+DumpstateSectionReporter::DumpstateSectionReporter(const std::string& title,
+                                                   sp<android::os::IDumpstateListener> listener,
+                                                   bool sendReport)
+    : title_(title), listener_(listener), sendReport_(sendReport), status_(OK), size_(-1) {
+    started_ = std::chrono::steady_clock::now();
+}
+
+DumpstateSectionReporter::~DumpstateSectionReporter() {
+    if ((listener_ != nullptr) && (sendReport_)) {
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - started_);
+        listener_->onSectionComplete(title_, status_, size_, (int32_t)elapsed.count());
+    }
+}
+
+}  // namespace dumpstate
+}  // namespace os
+}  // namespace android
diff --git a/cmds/dumpstate/DumpstateSectionReporter.h b/cmds/dumpstate/DumpstateSectionReporter.h
new file mode 100644
index 0000000..e971de8
--- /dev/null
+++ b/cmds/dumpstate/DumpstateSectionReporter.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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 ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
+#define ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
+
+#include <android/os/IDumpstateListener.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+
+/*
+ * Helper class used to report per section details to a listener.
+ *
+ * Typical usage:
+ *
+ *    DumpstateSectionReporter sectionReporter(title, listener, sendReport);
+ *    sectionReporter.setSize(5000);
+ *
+ */
+class DumpstateSectionReporter {
+  public:
+    DumpstateSectionReporter(const std::string& title, sp<android::os::IDumpstateListener> listener,
+                             bool sendReport);
+
+    ~DumpstateSectionReporter();
+
+    void setStatus(status_t status) {
+        status_ = status;
+    }
+
+    void setSize(int size) {
+        size_ = size;
+    }
+
+  private:
+    std::string title_;
+    android::sp<android::os::IDumpstateListener> listener_;
+    bool sendReport_;
+    status_t status_;
+    int size_;
+    std::chrono::time_point<std::chrono::steady_clock> started_;
+};
+
+}  // namespace dumpstate
+}  // namespace os
+}  // namespace android
+
+#endif  // ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index efe0466..49a78e7 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -52,6 +52,7 @@
 
 binder::Status DumpstateService::setListener(const std::string& name,
                                              const sp<IDumpstateListener>& listener,
+                                             bool getSectionDetails,
                                              sp<IDumpstateToken>* returned_token) {
     *returned_token = nullptr;
     if (name.empty()) {
@@ -70,6 +71,7 @@
 
     ds_.listener_name_ = name;
     ds_.listener_ = listener;
+    ds_.report_section_ = getSectionDetails;
     *returned_token = new DumpstateToken();
 
     return binder::Status::ok();
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 4352d3d..7bca24a 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -38,6 +38,7 @@
 
     status_t dump(int fd, const Vector<String16>& args) override;
     binder::Status setListener(const std::string& name, const sp<IDumpstateListener>& listener,
+                               bool getSectionDetails,
                                sp<IDumpstateToken>* returned_token) override;
 
   private:
diff --git a/cmds/dumpstate/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
index e866b8b..85eb464 100644
--- a/cmds/dumpstate/DumpstateUtil.cpp
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -30,6 +30,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <log/log.h>
 
 #include "DumpstateInternal.h"
@@ -42,7 +43,7 @@
 
 static constexpr const char* kSuPath = "/system/xbin/su";
 
-static bool waitpid_with_timeout(pid_t pid, int timeout_seconds, int* status) {
+static bool waitpid_with_timeout(pid_t pid, int timeout_ms, int* status) {
     sigset_t child_mask, old_mask;
     sigemptyset(&child_mask);
     sigaddset(&child_mask, SIGCHLD);
@@ -53,10 +54,11 @@
     }
 
     timespec ts;
-    ts.tv_sec = timeout_seconds;
-    ts.tv_nsec = 0;
+    ts.tv_sec = MSEC_TO_SEC(timeout_ms);
+    ts.tv_nsec = (timeout_ms % 1000) * 1000000;
     int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts));
     int saved_errno = errno;
+
     // Set the signals back the way they were.
     if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
         printf("*** sigprocmask failed: %s\n", strerror(errno));
@@ -90,7 +92,7 @@
 CommandOptions CommandOptions::DEFAULT = CommandOptions::WithTimeout(10).Build();
 CommandOptions CommandOptions::AS_ROOT = CommandOptions::WithTimeout(10).AsRoot().Build();
 
-CommandOptions::CommandOptionsBuilder::CommandOptionsBuilder(int64_t timeout) : values(timeout) {
+CommandOptions::CommandOptionsBuilder::CommandOptionsBuilder(int64_t timeout_ms) : values(timeout_ms) {
 }
 
 CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::Always() {
@@ -103,6 +105,12 @@
     return *this;
 }
 
+CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::AsRootIfAvailable() {
+    if (!PropertiesHelper::IsUserBuild())
+        values.account_mode_ = SU_ROOT;
+    return *this;
+}
+
 CommandOptions::CommandOptionsBuilder& CommandOptions::CommandOptionsBuilder::DropRoot() {
     values.account_mode_ = DROP_ROOT;
     return *this;
@@ -123,8 +131,8 @@
     return CommandOptions(values);
 }
 
-CommandOptions::CommandOptionsValues::CommandOptionsValues(int64_t timeout)
-    : timeout_(timeout),
+CommandOptions::CommandOptionsValues::CommandOptionsValues(int64_t timeout_ms)
+    : timeout_ms_(timeout_ms),
       always_(false),
       account_mode_(DONT_DROP_ROOT),
       output_mode_(NORMAL_OUTPUT),
@@ -135,7 +143,11 @@
 }
 
 int64_t CommandOptions::Timeout() const {
-    return values.timeout_;
+    return MSEC_TO_SEC(values.timeout_ms_);
+}
+
+int64_t CommandOptions::TimeoutInMs() const {
+    return values.timeout_ms_;
 }
 
 bool CommandOptions::Always() const {
@@ -154,8 +166,12 @@
     return values.logging_message_;
 }
 
-CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeout(int64_t timeout) {
-    return CommandOptions::CommandOptionsBuilder(timeout);
+CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeout(int64_t timeout_sec) {
+    return CommandOptions::CommandOptionsBuilder(SEC_TO_MSEC(timeout_sec));
+}
+
+CommandOptions::CommandOptionsBuilder CommandOptions::WithTimeoutInMs(int64_t timeout_ms) {
+    return CommandOptions::CommandOptionsBuilder(timeout_ms);
 }
 
 std::string PropertiesHelper::build_type_ = "";
@@ -176,8 +192,8 @@
 }
 
 int DumpFileToFd(int out_fd, const std::string& title, const std::string& path) {
-    int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
-    if (fd < 0) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+    if (fd.get() < 0) {
         int err = errno;
         if (title.empty()) {
             dprintf(out_fd, "*** Error dumping %s: %s\n", path.c_str(), strerror(err));
@@ -188,7 +204,7 @@
         fsync(out_fd);
         return -1;
     }
-    return DumpFileFromFdToFd(title, path, fd, out_fd, PropertiesHelper::IsDryRun());
+    return DumpFileFromFdToFd(title, path, fd.get(), out_fd, PropertiesHelper::IsDryRun());
 }
 
 int RunCommandToFd(int fd, const std::string& title, const std::vector<std::string>& full_command,
@@ -307,7 +323,7 @@
 
     /* handle parent case */
     int status;
-    bool ret = waitpid_with_timeout(pid, options.Timeout(), &status);
+    bool ret = waitpid_with_timeout(pid, options.TimeoutInMs(), &status);
     fsync(fd);
 
     uint64_t elapsed = Nanotime() - start;
@@ -326,9 +342,9 @@
                    static_cast<float>(elapsed) / NANOS_PER_SEC, pid);
         }
         kill(pid, SIGTERM);
-        if (!waitpid_with_timeout(pid, 5, nullptr)) {
+        if (!waitpid_with_timeout(pid, 5000, nullptr)) {
             kill(pid, SIGKILL);
-            if (!waitpid_with_timeout(pid, 5, nullptr)) {
+            if (!waitpid_with_timeout(pid, 5000, nullptr)) {
                 if (!silent)
                     dprintf(fd, "could not kill command '%s' (pid %d) even with SIGKILL.\n",
                             command, pid);
diff --git a/cmds/dumpstate/DumpstateUtil.h b/cmds/dumpstate/DumpstateUtil.h
index 5a8ce5b..8342099 100644
--- a/cmds/dumpstate/DumpstateUtil.h
+++ b/cmds/dumpstate/DumpstateUtil.h
@@ -19,6 +19,16 @@
 #include <cstdint>
 #include <string>
 
+/*
+ * Converts seconds to milliseconds.
+ */
+#define SEC_TO_MSEC(second) (second * 1000)
+
+/*
+ * Converts milliseconds to seconds.
+ */
+#define MSEC_TO_SEC(millisecond) (millisecond / 1000)
+
 namespace android {
 namespace os {
 namespace dumpstate {
@@ -66,9 +76,9 @@
   private:
     class CommandOptionsValues {
       private:
-        CommandOptionsValues(int64_t timeout);
+        CommandOptionsValues(int64_t timeout_ms);
 
-        int64_t timeout_;
+        int64_t timeout_ms_;
         bool always_;
         PrivilegeMode account_mode_;
         OutputMode output_mode_;
@@ -89,6 +99,8 @@
         CommandOptionsBuilder& Always();
         /* Sets the command's PrivilegeMode as `SU_ROOT` */
         CommandOptionsBuilder& AsRoot();
+        /* If !IsUserBuild(), sets the command's PrivilegeMode as `SU_ROOT` */
+        CommandOptionsBuilder& AsRootIfAvailable();
         /* Sets the command's PrivilegeMode as `DROP_ROOT` */
         CommandOptionsBuilder& DropRoot();
         /* Sets the command's OutputMode as `REDIRECT_TO_STDERR` */
@@ -100,13 +112,15 @@
         CommandOptions Build();
 
       private:
-        CommandOptionsBuilder(int64_t timeout);
+        CommandOptionsBuilder(int64_t timeout_ms);
         CommandOptionsValues values;
         friend class CommandOptions;
     };
 
-    /** Gets the command timeout, in seconds. */
+    /** Gets the command timeout in seconds. */
     int64_t Timeout() const;
+    /** Gets the command timeout in milliseconds. */
+    int64_t TimeoutInMs() const;
     /* Checks whether the command should always be run, even on dry-run mode. */
     bool Always() const;
     /** Gets the PrivilegeMode of the command. */
@@ -116,8 +130,11 @@
     /** Gets the logging message header, it any. */
     std::string LoggingMessage() const;
 
-    /** Creates a builder with the requied timeout. */
-    static CommandOptionsBuilder WithTimeout(int64_t timeout);
+    /** Creates a builder with the requied timeout in seconds. */
+    static CommandOptionsBuilder WithTimeout(int64_t timeout_sec);
+
+    /** Creates a builder with the requied timeout in milliseconds. */
+    static CommandOptionsBuilder WithTimeoutInMs(int64_t timeout_ms);
 
     // Common options.
     static CommandOptions DEFAULT;
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 4becccf..9b11b96 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -30,6 +30,9 @@
      *
      * Returns a token used to monitor dumpstate death, or `nullptr` if the listener was already
      * set (the listener behaves like a Highlander: There Can be Only One).
+     * Set {@code getSectionDetails} to true in order to receive callbacks with per section
+     * progress details
      */
-    IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener);
+    IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener,
+                                boolean getSectionDetails);
 }
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 32717f4..030d69d 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -24,4 +24,16 @@
 interface IDumpstateListener {
     void onProgressUpdated(int progress);
     void onMaxProgressUpdated(int maxProgress);
+
+    /**
+    * Called after every section is complete.
+    * @param  name          section name
+    * @param  status        values from status_t
+    *                       {@code OK} section completed successfully
+    *                       {@code TIMEOUT} dump timed out
+    *                       {@code != OK} error
+    * @param  size          size in bytes, may be invalid if status != OK
+    * @param  durationMs    duration in ms
+    */
+    void onSectionComplete(@utf8InCpp String name, int status, int size, int durationMs);
 }
diff --git a/cmds/dumpstate/bugreport-format.md b/cmds/dumpstate/bugreport-format.md
index b995b80..39e70d1 100644
--- a/cmds/dumpstate/bugreport-format.md
+++ b/cmds/dumpstate/bugreport-format.md
@@ -56,8 +56,37 @@
 - `description.txt`: whose value is a multi-line, detailed description of the problem.
 
 ## Android O versions
-On _Android O (OhMightyAndroidWhatsYourNextReleaseName?)_, the following changes were made:
-- The ANR traces are added to the `FS` folder, typically under `FS/data/anr` (version `2.0-dev-1`).
+On _Android O (Oreo)_, the following changes were made:
+- The ANR traces are added to the `FS` folder, typically under `FS/data/anr` (version `2.0-dev-split-anr`).
+
+## Version 2.0 (Android P)
+On _Android P_, the following changes were made:
+- Framework services are dumped by priority. Supported priorities can be specified
+  when registering the service. If a service does not specify its priority, its
+  assumed to be NORMAL.
+  Supported priorities:
+    - CRITICAL - services that must dump first, and fast (under 100ms). Ex: cpuinfo.
+    - HIGH - services that also must dump first, but can take longer (under 250ms)
+      to dump. Ex: meminfo.
+    - NORMAL - services that have no rush to dump and can take a long time (under 10s).
+
+  Format changes:
+    - Two additional dumpsys sections are generated. The two new sections can be
+      identified by their HEADER `DUMPSYS CRITICAL` and `DUMPSYS HIGH`.
+    - Services in the new sections will have a new header containing the
+      priority.
+      `DUMP OF SERVICE CRITICAL <servicename>` and
+      `DUMP OF SERVICE HIGH <servicename>`.
+    For example, cpuinfo will now move to `DUMPSYS CRITICAL` and will have a
+    header `DUMP OF SERVICE CRITICAL CPUINFO`.
+
+- Bug report will contain proto dumps from all supporting services. Support can be
+  specified when registering framework services.
+  Format changes:
+    - All protos will be generated into separate files per service, per priority. The files
+      will be stored in `proto/<servicename>(_CRITICAL|_HIGH|).proto`
+
+- ANR trace feature has been pushed to version `3.0-dev-split-anr`
 
 ## Intermediate versions
 During development, the versions will be suffixed with _-devX_ or
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 5f93e99..1d951be 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -25,41 +25,68 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/poll.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/wait.h>
 #include <unistd.h>
+
+#include <chrono>
+#include <functional>
+#include <future>
 #include <memory>
 #include <regex>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android-base/file.h>
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/hardware/dumpstate/1.0/IDumpstateDevice.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
+#include <dumpsys.h>
+#include <hidl/ServiceManagement.h>
 #include <openssl/sha.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
-
+#include <serviceutils/PriorityDumper.h>
+#include <utils/StrongPointer.h>
 #include "DumpstateInternal.h"
+#include "DumpstateSectionReporter.h"
 #include "DumpstateService.h"
 #include "dumpstate.h"
 
 using ::android::hardware::dumpstate::V1_0::IDumpstateDevice;
+using ::std::literals::chrono_literals::operator""ms;
+using ::std::literals::chrono_literals::operator""s;
 
 // TODO: remove once moved to namespace
+using android::defaultServiceManager;
+using android::Dumpsys;
+using android::INVALID_OPERATION;
+using android::IServiceManager;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::String16;
+using android::String8;
+using android::TIMED_OUT;
+using android::UNKNOWN_ERROR;
+using android::Vector;
 using android::os::dumpstate::CommandOptions;
 using android::os::dumpstate::DumpFileToFd;
-using android::os::dumpstate::PropertiesHelper;
+using android::os::dumpstate::DumpstateSectionReporter;
 using android::os::dumpstate::GetPidByName;
+using android::os::dumpstate::PropertiesHelper;
 
 /* read before root is shed */
 static char cmdline_buf[16384] = "(unknown)";
@@ -77,10 +104,12 @@
 #define RAFT_DIR "/data/misc/raft"
 #define RECOVERY_DIR "/cache/recovery"
 #define RECOVERY_DATA_DIR "/data/misc/recovery"
+#define UPDATE_ENGINE_LOG_DIR "/data/misc/update_engine_log"
 #define LOGPERSIST_DATA_DIR "/data/misc/logd"
 #define PROFILE_DATA_DIR_CUR "/data/misc/profiles/cur"
 #define PROFILE_DATA_DIR_REF "/data/misc/profiles/ref"
 #define WLUTIL "/vendor/xbin/wlutil"
+#define WMTRACE_DATA_DIR "/data/misc/wmtrace"
 
 // TODO(narayan): Since this information has to be kept in sync
 // with tombstoned, we should just put it in a common header.
@@ -91,19 +120,6 @@
 static const std::string ANR_DIR = "/data/anr/";
 static const std::string ANR_FILE_PREFIX = "anr_";
 
-struct DumpData {
-    std::string name;
-    int fd;
-    time_t mtime;
-};
-
-static bool operator<(const DumpData& d1, const DumpData& d2) {
-    return d1.mtime > d2.mtime;
-}
-
-static std::unique_ptr<std::vector<DumpData>> tombstone_data;
-static std::unique_ptr<std::vector<DumpData>> anr_data;
-
 // TODO: temporary variables and functions used during C++ refactoring
 static Dumpstate& ds = Dumpstate::GetInstance();
 static int RunCommand(const std::string& title, const std::vector<std::string>& fullCommand,
@@ -112,8 +128,8 @@
 }
 static void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsysArgs,
                        const CommandOptions& options = Dumpstate::DEFAULT_DUMPSYS,
-                       long dumpsysTimeout = 0) {
-    return ds.RunDumpsys(title, dumpsysArgs, options, dumpsysTimeout);
+                       long dumpsysTimeoutMs = 0) {
+    return ds.RunDumpsys(title, dumpsysArgs, options, dumpsysTimeoutMs);
 }
 static int DumpFile(const std::string& title, const std::string& path) {
     return ds.DumpFile(title, path);
@@ -124,14 +140,14 @@
 
 // Must be hardcoded because dumpstate HAL implementation need SELinux access to it
 static const std::string kDumpstateBoardPath = "/bugreports/";
+static const std::string kProtoPath = "proto/";
+static const std::string kProtoExt = ".proto";
 static const std::string kDumpstateBoardFiles[] = {
     "dumpstate_board.txt",
     "dumpstate_board.bin"
 };
 static const int NUM_OF_DUMPS = arraysize(kDumpstateBoardFiles);
 
-static const std::string kLsHalDebugPath = "/bugreports/dumpstate_lshal.txt";
-
 static constexpr char PROPERTY_EXTRA_OPTIONS[] = "dumpstate.options";
 static constexpr char PROPERTY_LAST_ID[] = "dumpstate.last_id";
 static constexpr char PROPERTY_VERSION[] = "dumpstate.version";
@@ -146,15 +162,20 @@
  * is set, the vector only contains files that were written in the last 30 minutes.
  * If |limit_by_count| is set, the vector only contains the ten latest files.
  */
-static std::vector<DumpData>* GetDumpFds(const std::string& dir_path,
-                                         const std::string& file_prefix,
-                                         bool limit_by_mtime,
-                                         bool limit_by_count = true) {
+static std::vector<DumpData> GetDumpFds(const std::string& dir_path,
+                                        const std::string& file_prefix,
+                                        bool limit_by_mtime,
+                                        bool limit_by_count = true) {
     const time_t thirty_minutes_ago = ds.now_ - 60 * 30;
 
-    std::unique_ptr<std::vector<DumpData>> dump_data(new std::vector<DumpData>());
     std::unique_ptr<DIR, decltype(&closedir)> dump_dir(opendir(dir_path.c_str()), closedir);
 
+    if (dump_dir == nullptr) {
+        MYLOGW("Unable to open directory %s: %s\n", dir_path.c_str(), strerror(errno));
+        return std::vector<DumpData>();
+    }
+
+    std::vector<DumpData> dump_data;
     struct dirent* entry = nullptr;
     while ((entry = readdir(dump_dir.get()))) {
         if (entry->d_type != DT_REG) {
@@ -170,33 +191,34 @@
         android::base::unique_fd fd(
             TEMP_FAILURE_RETRY(open(abs_path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK)));
         if (fd == -1) {
-            MYLOGW("Unable to open dump file: %s %s\n", abs_path.c_str(), strerror(errno));
+            MYLOGW("Unable to open dump file %s: %s\n", abs_path.c_str(), strerror(errno));
             break;
         }
 
         struct stat st = {};
         if (fstat(fd, &st) == -1) {
-            MYLOGW("Unable to stat dump file: %s %s\n", abs_path.c_str(), strerror(errno));
+            MYLOGW("Unable to stat dump file %s: %s\n", abs_path.c_str(), strerror(errno));
             continue;
         }
 
-        if (limit_by_mtime && st.st_mtime >= thirty_minutes_ago) {
+        if (limit_by_mtime && st.st_mtime < thirty_minutes_ago) {
             MYLOGI("Excluding stale dump file: %s\n", abs_path.c_str());
             continue;
         }
 
-        DumpData data = {.name = abs_path, .fd = fd.release(), .mtime = st.st_mtime};
-
-        dump_data->push_back(data);
+        dump_data.emplace_back(DumpData{abs_path, std::move(fd), st.st_mtime});
     }
 
-    std::sort(dump_data->begin(), dump_data->end());
+    // Sort in descending modification time so that we only keep the newest
+    // reports if |limit_by_count| is true.
+    std::sort(dump_data.begin(), dump_data.end(),
+              [](const DumpData& d1, const DumpData& d2) { return d1.mtime > d2.mtime; });
 
-    if (limit_by_count && dump_data->size() > 10) {
-        dump_data->erase(dump_data->begin() + 10, dump_data->end());
+    if (limit_by_count && dump_data.size() > 10) {
+        dump_data.erase(dump_data.begin() + 10, dump_data.end());
     }
 
-    return dump_data.release();
+    return dump_data;
 }
 
 static bool AddDumps(const std::vector<DumpData>::const_iterator start,
@@ -207,15 +229,25 @@
         const std::string& name = it->name;
         const int fd = it->fd;
         dumped = true;
+
+        // Seek to the beginning of the file before dumping any data. A given
+        // DumpData entry might be dumped multiple times in the report.
+        //
+        // For example, the most recent ANR entry is dumped to the body of the
+        // main entry and it also shows up as a separate entry in the bugreport
+        // ZIP file.
+        if (lseek(fd, 0, SEEK_SET) != static_cast<off_t>(0)) {
+            MYLOGE("Unable to add %s to zip file, lseek failed: %s\n", name.c_str(),
+                   strerror(errno));
+        }
+
         if (ds.IsZipping() && add_to_zip) {
-            if (!ds.AddZipEntryFromFd(ZIP_ROOT_DIR + name, fd)) {
-                MYLOGE("Unable to add %s %s to zip file\n", name.c_str(), type_name);
+            if (ds.AddZipEntryFromFd(ZIP_ROOT_DIR + name, fd, /* timeout = */ 0ms) != OK) {
+                MYLOGE("Unable to add %s to zip file, addZipEntryFromFd failed\n", name.c_str());
             }
         } else {
             dump_file_from_fd(type_name, name.c_str(), fd);
         }
-
-        close(fd);
     }
 
     return dumped;
@@ -640,12 +672,18 @@
     return 0;
 }
 
-/* timeout in ms */
-static unsigned long logcat_timeout(const char *name) {
-    log_id_t id = android_name_to_log_id(name);
-    unsigned long property_size = __android_logger_get_buffer_size(id);
-    /* Engineering margin is ten-fold our guess */
-    return 10 * (property_size + worst_write_perf) / worst_write_perf;
+static const long MINIMUM_LOGCAT_TIMEOUT_MS = 50000;
+
+/* timeout in ms to read a list of buffers */
+static unsigned long logcat_timeout(const std::vector<std::string>& buffers) {
+    unsigned long timeout_ms = 0;
+    for (const auto& buffer : buffers) {
+        log_id_t id = android_name_to_log_id(buffer.c_str());
+        unsigned long property_size = __android_logger_get_buffer_size(id);
+        /* Engineering margin is ten-fold our guess */
+        timeout_ms += 10 * (property_size + worst_write_perf) / worst_write_perf;
+    }
+    return timeout_ms > MINIMUM_LOGCAT_TIMEOUT_MS ? timeout_ms : MINIMUM_LOGCAT_TIMEOUT_MS;
 }
 
 void Dumpstate::PrintHeader() const {
@@ -674,6 +712,9 @@
     printf("Kernel: ");
     DumpFileToFd(STDOUT_FILENO, "", "/proc/version");
     printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
+    printf("Uptime: ");
+    RunCommandToFd(STDOUT_FILENO, "", {"uptime", "-p"},
+                   CommandOptions::WithTimeout(1).Always().Build());
     printf("Bugreport format version: %s\n", version_.c_str());
     printf("Dumpstate info: id=%d pid=%d dry_run=%d args=%s extra_options=%s\n", id_, pid_,
            PropertiesHelper::IsDryRun(), args_.c_str(), extra_options_.c_str());
@@ -688,16 +729,17 @@
       ".shb", ".sys", ".vb",  ".vbe", ".vbs", ".vxd", ".wsc", ".wsf", ".wsh"
 };
 
-bool Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd) {
+status_t Dumpstate::AddZipEntryFromFd(const std::string& entry_name, int fd,
+                                      std::chrono::milliseconds timeout = 0ms) {
     if (!IsZipping()) {
         MYLOGD("Not adding zip entry %s from fd because it's not a zipped bugreport\n",
                entry_name.c_str());
-        return false;
+        return INVALID_OPERATION;
     }
     std::string valid_name = entry_name;
 
     // Rename extension if necessary.
-    size_t idx = entry_name.rfind(".");
+    size_t idx = entry_name.rfind('.');
     if (idx != std::string::npos) {
         std::string extension = entry_name.substr(idx);
         std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
@@ -714,32 +756,55 @@
     if (err != 0) {
         MYLOGE("zip_writer_->StartEntryWithTime(%s): %s\n", valid_name.c_str(),
                ZipWriter::ErrorCodeString(err));
-        return false;
+        return UNKNOWN_ERROR;
     }
+    auto start = std::chrono::steady_clock::now();
+    auto end = start + timeout;
+    struct pollfd pfd = {fd, POLLIN};
 
     std::vector<uint8_t> buffer(65536);
     while (1) {
+        if (timeout.count() > 0) {
+            // lambda to recalculate the timeout.
+            auto time_left_ms = [end]() {
+                auto now = std::chrono::steady_clock::now();
+                auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
+                return std::max(diff.count(), 0LL);
+            };
+
+            int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
+            if (rc < 0) {
+                MYLOGE("Error in poll while adding from fd to zip entry %s:%s", entry_name.c_str(),
+                       strerror(errno));
+                return -errno;
+            } else if (rc == 0) {
+                MYLOGE("Timed out adding from fd to zip entry %s:%s Timeout:%lldms",
+                       entry_name.c_str(), strerror(errno), timeout.count());
+                return TIMED_OUT;
+            }
+        }
+
         ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer.data(), buffer.size()));
         if (bytes_read == 0) {
             break;
         } else if (bytes_read == -1) {
             MYLOGE("read(%s): %s\n", entry_name.c_str(), strerror(errno));
-            return false;
+            return -errno;
         }
         err = zip_writer_->WriteBytes(buffer.data(), bytes_read);
         if (err) {
             MYLOGE("zip_writer_->WriteBytes(): %s\n", ZipWriter::ErrorCodeString(err));
-            return false;
+            return UNKNOWN_ERROR;
         }
     }
 
     err = zip_writer_->FinishEntry();
     if (err != 0) {
         MYLOGE("zip_writer_->FinishEntry(): %s\n", ZipWriter::ErrorCodeString(err));
-        return false;
+        return UNKNOWN_ERROR;
     }
 
-    return true;
+    return OK;
 }
 
 bool Dumpstate::AddZipEntry(const std::string& entry_name, const std::string& entry_path) {
@@ -750,12 +815,12 @@
         return false;
     }
 
-    return AddZipEntryFromFd(entry_name, fd.get());
+    return (AddZipEntryFromFd(entry_name, fd.get()) == OK);
 }
 
 /* adds a file to the existing zipped bugreport */
 static int _add_file_from_fd(const char* title __attribute__((unused)), const char* path, int fd) {
-    return ds.AddZipEntryFromFd(ZIP_ROOT_DIR + path, fd) ? 0 : 1;
+    return (ds.AddZipEntryFromFd(ZIP_ROOT_DIR + path, fd) == OK) ? 0 : 1;
 }
 
 void Dumpstate::AddDir(const std::string& dir, bool recursive) {
@@ -811,41 +876,43 @@
     }
 }
 
+static void DoKernelLogcat() {
+    unsigned long timeout_ms = logcat_timeout({"kernel"});
+    RunCommand(
+        "KERNEL LOG",
+        {"logcat", "-b", "kernel", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+        CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+}
+
 static void DoLogcat() {
-    unsigned long timeout;
+    unsigned long timeout_ms;
     // DumpFile("EVENT LOG TAGS", "/etc/event-log-tags");
     // calculate timeout
-    timeout = logcat_timeout("main") + logcat_timeout("system") + logcat_timeout("crash");
-    if (timeout < 20000) {
-        timeout = 20000;
-    }
+    timeout_ms = logcat_timeout({"main", "system", "crash"});
     RunCommand("SYSTEM LOG",
-               {"logcat", "-v", "threadtime", "-v", "printable", "-v", "uid",
-                        "-d", "*:v"},
-               CommandOptions::WithTimeout(timeout / 1000).Build());
-    timeout = logcat_timeout("events");
-    if (timeout < 20000) {
-        timeout = 20000;
-    }
-    RunCommand("EVENT LOG",
-               {"logcat", "-b", "events", "-v", "threadtime", "-v", "printable", "-v", "uid",
-                        "-d", "*:v"},
-               CommandOptions::WithTimeout(timeout / 1000).Build());
-    timeout = logcat_timeout("radio");
-    if (timeout < 20000) {
-        timeout = 20000;
-    }
-    RunCommand("RADIO LOG",
-               {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid",
-                        "-d", "*:v"},
-               CommandOptions::WithTimeout(timeout / 1000).Build());
+               {"logcat", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+               CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+    timeout_ms = logcat_timeout({"events"});
+    RunCommand(
+        "EVENT LOG",
+        {"logcat", "-b", "events", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+        CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+    timeout_ms = logcat_timeout({"stats"});
+    RunCommand(
+        "STATS LOG",
+        {"logcat", "-b", "stats", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+        CommandOptions::WithTimeoutInMs(timeout_ms).Build());
+    timeout_ms = logcat_timeout({"radio"});
+    RunCommand(
+        "RADIO LOG",
+        {"logcat", "-b", "radio", "-v", "threadtime", "-v", "printable", "-v", "uid", "-d", "*:v"},
+        CommandOptions::WithTimeoutInMs(timeout_ms).Build());
 
     RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"});
 
     /* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */
-    RunCommand("LAST LOGCAT",
-                {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable", "-v", "uid",
-                        "-d", "*:v"});
+    RunCommand("LAST LOGCAT", {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable",
+                               "-v", "uid", "-d", "*:v"});
 }
 
 static void DumpIpTablesAsRoot() {
@@ -887,9 +954,9 @@
     MYLOGD("AddGlobalAnrTraceFile(): dump_traces_dir=%s, anr_traces_dir=%s, already_dumped=%d\n",
            dump_traces_dir.c_str(), anr_traces_dir.c_str(), already_dumped);
 
-    int fd = TEMP_FAILURE_RETRY(
-        open(anr_traces_file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
-    if (fd < 0) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+        open(anr_traces_file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK)));
+    if (fd.get() < 0) {
         printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_file.c_str(), strerror(errno));
     } else {
         if (add_to_zip) {
@@ -901,7 +968,7 @@
         } else {
             MYLOGD("Dumping last ANR traces (%s) to the main bugreport entry\n",
                    anr_traces_file.c_str());
-            dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_file.c_str(), fd);
+            dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_file.c_str(), fd.get());
         }
     }
 }
@@ -930,16 +997,16 @@
     }
 
     // Add a specific message for the first ANR Dump.
-    if (anr_data->size() > 0) {
-        AddDumps(anr_data->begin(), anr_data->begin() + 1,
+    if (ds.anr_data_.size() > 0) {
+        AddDumps(ds.anr_data_.begin(), ds.anr_data_.begin() + 1,
                  "VM TRACES AT LAST ANR", add_to_zip);
 
-        if (anr_data->size() > 1) {
-            // NOTE: Historical ANRs are always added as separate entries in the
-            // bugreport zip file.
-            AddDumps(anr_data->begin() + 1, anr_data->end(),
-                     "HISTORICAL ANR", true /* add_to_zip */);
-        }
+        // The "last" ANR will always be included as separate entry in the zip file. In addition,
+        // it will be present in the body of the main entry if |add_to_zip| == false.
+        //
+        // Historical ANRs are always included as separate entries in the bugreport zip file.
+        AddDumps(ds.anr_data_.begin() + ((add_to_zip) ? 1 : 0), ds.anr_data_.end(),
+                 "HISTORICAL ANR", true /* add_to_zip */);
     } else {
         printf("*** NO ANRs to dump in %s\n\n", ANR_DIR.c_str());
     }
@@ -1038,6 +1105,187 @@
     RunCommand("IP RULES v6", {"ip", "-6", "rule", "show"});
 }
 
+static void RunDumpsysTextByPriority(const std::string& title, int priority,
+                                     std::chrono::milliseconds timeout,
+                                     std::chrono::milliseconds service_timeout) {
+    auto start = std::chrono::steady_clock::now();
+    sp<android::IServiceManager> sm = defaultServiceManager();
+    Dumpsys dumpsys(sm.get());
+    Vector<String16> args;
+    Dumpsys::setServiceArgs(args, /* asProto = */ false, priority);
+    Vector<String16> services = dumpsys.listServices(priority, /* supports_proto = */ false);
+    for (const String16& service : services) {
+        std::string path(title);
+        path.append(" - ").append(String8(service).c_str());
+        DumpstateSectionReporter section_reporter(path, ds.listener_, ds.report_section_);
+        size_t bytes_written = 0;
+        status_t status = dumpsys.startDumpThread(service, args);
+        if (status == OK) {
+            dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
+            std::chrono::duration<double> elapsed_seconds;
+            status = dumpsys.writeDump(STDOUT_FILENO, service, service_timeout,
+                                       /* as_proto = */ false, elapsed_seconds, bytes_written);
+            section_reporter.setSize(bytes_written);
+            dumpsys.writeDumpFooter(STDOUT_FILENO, service, elapsed_seconds);
+            bool dump_complete = (status == OK);
+            dumpsys.stopDumpThread(dump_complete);
+        }
+        section_reporter.setStatus(status);
+
+        auto elapsed_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - start);
+        if (elapsed_duration > timeout) {
+            MYLOGE("*** command '%s' timed out after %llums\n", title.c_str(),
+                   elapsed_duration.count());
+            break;
+        }
+    }
+}
+
+static void RunDumpsysText(const std::string& title, int priority,
+                           std::chrono::milliseconds timeout,
+                           std::chrono::milliseconds service_timeout) {
+    DurationReporter duration_reporter(title);
+    dprintf(STDOUT_FILENO, "------ %s (/system/bin/dumpsys) ------\n", title.c_str());
+    fsync(STDOUT_FILENO);
+    RunDumpsysTextByPriority(title, priority, timeout, service_timeout);
+}
+
+/* Dump all services registered with Normal or Default priority. */
+static void RunDumpsysTextNormalPriority(const std::string& title,
+                                         std::chrono::milliseconds timeout,
+                                         std::chrono::milliseconds service_timeout) {
+    DurationReporter duration_reporter(title);
+    dprintf(STDOUT_FILENO, "------ %s (/system/bin/dumpsys) ------\n", title.c_str());
+    fsync(STDOUT_FILENO);
+    RunDumpsysTextByPriority(title, IServiceManager::DUMP_FLAG_PRIORITY_NORMAL, timeout,
+                             service_timeout);
+    RunDumpsysTextByPriority(title, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT, timeout,
+                             service_timeout);
+}
+
+static void RunDumpsysProto(const std::string& title, int priority,
+                            std::chrono::milliseconds timeout,
+                            std::chrono::milliseconds service_timeout) {
+    if (!ds.IsZipping()) {
+        MYLOGD("Not dumping %s because it's not a zipped bugreport\n", title.c_str());
+        return;
+    }
+    sp<android::IServiceManager> sm = defaultServiceManager();
+    Dumpsys dumpsys(sm.get());
+    Vector<String16> args;
+    Dumpsys::setServiceArgs(args, /* asProto = */ true, priority);
+    DurationReporter duration_reporter(title);
+
+    auto start = std::chrono::steady_clock::now();
+    Vector<String16> services = dumpsys.listServices(priority, /* supports_proto = */ true);
+    for (const String16& service : services) {
+        std::string path(kProtoPath);
+        path.append(String8(service).c_str());
+        if (priority == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL) {
+            path.append("_CRITICAL");
+        } else if (priority == IServiceManager::DUMP_FLAG_PRIORITY_HIGH) {
+            path.append("_HIGH");
+        }
+        path.append(kProtoExt);
+        DumpstateSectionReporter section_reporter(path, ds.listener_, ds.report_section_);
+        status_t status = dumpsys.startDumpThread(service, args);
+        if (status == OK) {
+            status = ds.AddZipEntryFromFd(path, dumpsys.getDumpFd(), service_timeout);
+            bool dumpTerminated = (status == OK);
+            dumpsys.stopDumpThread(dumpTerminated);
+        }
+        ZipWriter::FileEntry file_entry;
+        ds.zip_writer_->GetLastEntry(&file_entry);
+        section_reporter.setSize(file_entry.compressed_size);
+        section_reporter.setStatus(status);
+
+        auto elapsed_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - start);
+        if (elapsed_duration > timeout) {
+            MYLOGE("*** command '%s' timed out after %llums\n", title.c_str(),
+                   elapsed_duration.count());
+            break;
+        }
+    }
+}
+
+// Runs dumpsys on services that must dump first and and will take less than 100ms to dump.
+static void RunDumpsysCritical() {
+    RunDumpsysText("DUMPSYS CRITICAL", IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL,
+                   /* timeout= */ 5s, /* service_timeout= */ 500ms);
+    RunDumpsysProto("DUMPSYS CRITICAL PROTO", IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL,
+                    /* timeout= */ 5s, /* service_timeout= */ 500ms);
+}
+
+// Runs dumpsys on services that must dump first but can take up to 250ms to dump.
+static void RunDumpsysHigh() {
+    // TODO meminfo takes ~10s, connectivity takes ~5sec to dump. They are both
+    // high priority. Reduce timeout once they are able to dump in a shorter time or
+    // moved to a parallel task.
+    RunDumpsysText("DUMPSYS HIGH", IServiceManager::DUMP_FLAG_PRIORITY_HIGH,
+                   /* timeout= */ 90s, /* service_timeout= */ 30s);
+    RunDumpsysProto("DUMPSYS HIGH PROTO", IServiceManager::DUMP_FLAG_PRIORITY_HIGH,
+                    /* timeout= */ 5s, /* service_timeout= */ 1s);
+}
+
+// Runs dumpsys on services that must dump but can take up to 10s to dump.
+static void RunDumpsysNormal() {
+    RunDumpsysTextNormalPriority("DUMPSYS", /* timeout= */ 90s, /* service_timeout= */ 10s);
+    RunDumpsysProto("DUMPSYS PROTO", IServiceManager::DUMP_FLAG_PRIORITY_NORMAL,
+                    /* timeout= */ 90s, /* service_timeout= */ 10s);
+}
+
+static void DumpHals() {
+    using android::hidl::manager::V1_0::IServiceManager;
+    using android::hardware::defaultServiceManager;
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    if (sm == nullptr) {
+        MYLOGE("Could not retrieve hwservicemanager to dump hals.\n");
+        return;
+    }
+
+    auto ret = sm->list([&](const auto& interfaces) {
+        for (const std::string& interface : interfaces) {
+            std::string cleanName = interface;
+            std::replace_if(cleanName.begin(),
+                            cleanName.end(),
+                            [](char c) {
+                                return !isalnum(c) &&
+                                    std::string("@-_:.").find(c) == std::string::npos;
+                            }, '_');
+            const std::string path = kDumpstateBoardPath + "lshal_debug_" + cleanName;
+
+            {
+                auto fd = android::base::unique_fd(
+                    TEMP_FAILURE_RETRY(open(path.c_str(),
+                    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+                if (fd < 0) {
+                    MYLOGE("Could not open %s to dump additional hal information.\n", path.c_str());
+                    continue;
+                }
+                RunCommandToFd(fd,
+                        "",
+                        {"lshal", "debug", "-E", interface},
+                        CommandOptions::WithTimeout(2).AsRootIfAvailable().Build());
+
+                bool empty = 0 == lseek(fd, 0, SEEK_END);
+                if (!empty) {
+                    ds.AddZipEntry("lshal-debug/" + cleanName + ".txt", path);
+                }
+            }
+
+            unlink(path.c_str());
+        }
+    });
+
+    if (!ret.isOk()) {
+        MYLOGE("Could not list hals from hwservicemanager.\n");
+    }
+}
+
 static void dumpstate() {
     DurationReporter duration_reporter("DUMPSTATE");
 
@@ -1062,21 +1310,14 @@
     DumpFile("KERNEL SYNC", "/d/sync");
 
     RunCommand("PROCESSES AND THREADS",
-               {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy"});
+               {"ps", "-A", "-T", "-Z", "-O", "pri,nice,rtprio,sched,pcy,time"});
     RunCommand("LIBRANK", {"librank"}, CommandOptions::AS_ROOT);
 
     if (ds.IsZipping()) {
-        RunCommand(
-                "HARDWARE HALS",
-                {"lshal", std::string("--debug=") + kLsHalDebugPath},
-                CommandOptions::AS_ROOT);
-
-        ds.AddZipEntry("lshal-debug.txt", kLsHalDebugPath);
-
-        unlink(kLsHalDebugPath.c_str());
+        RunCommand("HARDWARE HALS", {"lshal"}, CommandOptions::WithTimeout(2).AsRootIfAvailable().Build());
+        DumpHals();
     } else {
-        RunCommand(
-                "HARDWARE HALS", {"lshal", "--debug"}, CommandOptions::AS_ROOT);
+        RunCommand("HARDWARE HALS", {"lshal", "--debug"}, CommandOptions::WithTimeout(10).AsRootIfAvailable().Build());
     }
 
     RunCommand("PRINTENV", {"printenv"});
@@ -1088,7 +1329,12 @@
         RunCommand("LSMOD", {"lsmod"});
     }
 
-    do_dmesg();
+    if (__android_logger_property_get_bool(
+            "ro.logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE)) {
+        DoKernelLogcat();
+    } else {
+        do_dmesg();
+    }
 
     RunCommand("LIST OF OPEN FILES", {"lsof"}, CommandOptions::AS_ROOT);
     for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
@@ -1109,7 +1355,7 @@
 
     // NOTE: tombstones are always added as separate entries in the zip archive
     // and are not interspersed with the main report.
-    const bool tombstones_dumped = AddDumps(tombstone_data->begin(), tombstone_data->end(),
+    const bool tombstones_dumped = AddDumps(ds.tombstone_data_.begin(), ds.tombstone_data_.end(),
                                             "TOMBSTONE", true /* add_to_zip */);
     if (!tombstones_dumped) {
         printf("*** NO TOMBSTONES to dump in %s\n\n", TOMBSTONE_DIR.c_str());
@@ -1127,33 +1373,16 @@
     RunCommand("IPv6 ND CACHE", {"ip", "-6", "neigh", "show"});
     RunCommand("MULTICAST ADDRESSES", {"ip", "maddr"});
 
-    RunDumpsys("NETWORK DIAGNOSTICS", {"connectivity", "--diag"},
-               CommandOptions::WithTimeout(10).Build());
+    RunDumpsysHigh();
 
     RunCommand("SYSTEM PROPERTIES", {"getprop"});
 
-    RunCommand("VOLD DUMP", {"vdc", "dump"});
-    RunCommand("SECURE CONTAINERS", {"vdc", "asec", "list"});
-
-    RunCommand("STORAGED TASKIOINFO", {"storaged", "-u"}, CommandOptions::WithTimeout(10).Build());
+    RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
 
     RunCommand("FILESYSTEMS & FREE SPACE", {"df"});
 
     RunCommand("LAST RADIO LOG", {"parse_radio_log", "/proc/last_radio_log"});
 
-    printf("------ BACKLIGHTS ------\n");
-    printf("LCD brightness=");
-    DumpFile("", "/sys/class/leds/lcd-backlight/brightness");
-    printf("Button brightness=");
-    DumpFile("", "/sys/class/leds/button-backlight/brightness");
-    printf("Keyboard brightness=");
-    DumpFile("", "/sys/class/leds/keyboard-backlight/brightness");
-    printf("ALS mode=");
-    DumpFile("", "/sys/class/leds/lcd-backlight/als");
-    printf("LCD driver registers:\n");
-    DumpFile("", "/sys/class/leds/lcd-backlight/registers");
-    printf("\n");
-
     /* Binder state is expensive to look at as it uses a lot of memory. */
     DumpFile("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
     DumpFile("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
@@ -1161,6 +1390,11 @@
     DumpFile("BINDER STATS", "/sys/kernel/debug/binder/stats");
     DumpFile("BINDER STATE", "/sys/kernel/debug/binder/state");
 
+    /* Add window and surface trace files. */
+    if (!PropertiesHelper::IsUserBuild()) {
+        ds.AddDir(WMTRACE_DATA_DIR, false);
+    }
+
     ds.DumpstateBoard();
 
     /* Migrate the ril_dumpstate to a device specific dumpstate? */
@@ -1181,8 +1415,7 @@
     printf("== Android Framework Services\n");
     printf("========================================================\n");
 
-    RunDumpsys("DUMPSYS", {"--skip", "meminfo", "cpuinfo"}, CommandOptions::WithTimeout(90).Build(),
-               10);
+    RunDumpsysNormal();
 
     printf("========================================================\n");
     printf("== Checkins\n");
@@ -1199,19 +1432,40 @@
     printf("== Running Application Activities\n");
     printf("========================================================\n");
 
-    RunDumpsys("APP ACTIVITIES", {"activity", "-v", "all"});
+    // The following dumpsys internally collects output from running apps, so it can take a long
+    // time. So let's extend the timeout.
+
+    const CommandOptions DUMPSYS_COMPONENTS_OPTIONS = CommandOptions::WithTimeout(60).Build();
+
+    RunDumpsys("APP ACTIVITIES", {"activity", "-v", "all"}, DUMPSYS_COMPONENTS_OPTIONS);
 
     printf("========================================================\n");
-    printf("== Running Application Services\n");
+    printf("== Running Application Services (platform)\n");
     printf("========================================================\n");
 
-    RunDumpsys("APP SERVICES", {"activity", "service", "all"});
+    RunDumpsys("APP SERVICES PLATFORM", {"activity", "service", "all-platform"},
+            DUMPSYS_COMPONENTS_OPTIONS);
 
     printf("========================================================\n");
-    printf("== Running Application Providers\n");
+    printf("== Running Application Services (non-platform)\n");
     printf("========================================================\n");
 
-    RunDumpsys("APP PROVIDERS", {"activity", "provider", "all"});
+    RunDumpsys("APP SERVICES NON-PLATFORM", {"activity", "service", "all-non-platform"},
+            DUMPSYS_COMPONENTS_OPTIONS);
+
+    printf("========================================================\n");
+    printf("== Running Application Providers (platform)\n");
+    printf("========================================================\n");
+
+    RunDumpsys("APP PROVIDERS PLATFORM", {"activity", "provider", "all-platform"},
+            DUMPSYS_COMPONENTS_OPTIONS);
+
+    printf("========================================================\n");
+    printf("== Running Application Providers (non-platform)\n");
+    printf("========================================================\n");
+
+    RunDumpsys("APP PROVIDERS NON-PLATFORM", {"activity", "provider", "all-non-platform"},
+            DUMPSYS_COMPONENTS_OPTIONS);
 
     printf("========================================================\n");
     printf("== Dropbox crashes\n");
@@ -1228,10 +1482,8 @@
     printf("========================================================\n");
 }
 
-// This method collects dumpsys for telephony debugging only
-static void DumpstateTelephonyOnly() {
-    DurationReporter duration_reporter("DUMPSTATE");
-
+// This method collects common dumpsys for telephony and wifi
+static void DumpstateRadioCommon() {
     DumpIpTablesAsRoot();
 
     if (!DropRootUser()) {
@@ -1247,6 +1499,14 @@
 
     RunDumpsys("NETWORK DIAGNOSTICS", {"connectivity", "--diag"},
                CommandOptions::WithTimeout(10).Build());
+}
+
+// This method collects dumpsys for telephony debugging only
+static void DumpstateTelephonyOnly() {
+    DurationReporter duration_reporter("DUMPSTATE");
+    const CommandOptions DUMPSYS_COMPONENTS_OPTIONS = CommandOptions::WithTimeout(60).Build();
+
+    DumpstateRadioCommon();
 
     RunCommand("SYSTEM PROPERTIES", {"getprop"});
 
@@ -1254,8 +1514,14 @@
     printf("== Android Framework Services\n");
     printf("========================================================\n");
 
-    RunDumpsys("DUMPSYS", {"connectivity"}, CommandOptions::WithTimeout(90).Build(), 10);
-    RunDumpsys("DUMPSYS", {"carrier_config"}, CommandOptions::WithTimeout(90).Build(), 10);
+    RunDumpsys("DUMPSYS", {"connectivity"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+    RunDumpsys("DUMPSYS", {"carrier_config"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+    RunDumpsys("DUMPSYS", {"wifi"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+    RunDumpsys("BATTERYSTATS", {"batterystats"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
 
     printf("========================================================\n");
     printf("== Running Application Services\n");
@@ -1264,6 +1530,39 @@
     RunDumpsys("TELEPHONY SERVICES", {"activity", "service", "TelephonyDebugService"});
 
     printf("========================================================\n");
+    printf("== Running Application Services (non-platform)\n");
+    printf("========================================================\n");
+
+    RunDumpsys("APP SERVICES NON-PLATFORM", {"activity", "service", "all-non-platform"},
+            DUMPSYS_COMPONENTS_OPTIONS);
+
+    printf("========================================================\n");
+    printf("== Checkins\n");
+    printf("========================================================\n");
+
+    RunDumpsys("CHECKIN BATTERYSTATS", {"batterystats", "-c"});
+
+    printf("========================================================\n");
+    printf("== dumpstate: done (id %d)\n", ds.id_);
+    printf("========================================================\n");
+}
+
+// This method collects dumpsys for wifi debugging only
+static void DumpstateWifiOnly() {
+    DurationReporter duration_reporter("DUMPSTATE");
+
+    DumpstateRadioCommon();
+
+    printf("========================================================\n");
+    printf("== Android Framework Services\n");
+    printf("========================================================\n");
+
+    RunDumpsys("DUMPSYS", {"connectivity"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+    RunDumpsys("DUMPSYS", {"wifi"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+
+    printf("========================================================\n");
     printf("== dumpstate: done (id %d)\n", ds.id_);
     printf("========================================================\n");
 }
@@ -1274,77 +1573,112 @@
     printf("== Board\n");
     printf("========================================================\n");
 
-    ::android::sp<IDumpstateDevice> dumpstate_device(IDumpstateDevice::getService());
-    if (dumpstate_device == nullptr) {
-        MYLOGE("No IDumpstateDevice implementation\n");
-        return;
-    }
-
     if (!IsZipping()) {
         MYLOGD("Not dumping board info because it's not a zipped bugreport\n");
         return;
     }
 
-    std::string path[NUM_OF_DUMPS];
-    android::base::unique_fd fd[NUM_OF_DUMPS];
-    int numFds = 0;
-
+    std::vector<std::string> paths;
+    std::vector<android::base::ScopeGuard<std::function<void()>>> remover;
     for (int i = 0; i < NUM_OF_DUMPS; i++) {
-        path[i] = kDumpstateBoardPath + kDumpstateBoardFiles[i];
-        MYLOGI("Calling IDumpstateDevice implementation using path %s\n", path[i].c_str());
-
-        fd[i] = android::base::unique_fd(
-            TEMP_FAILURE_RETRY(open(path[i].c_str(),
-            O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
-            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
-        if (fd[i] < 0) {
-            MYLOGE("Could not open file %s: %s\n", path[i].c_str(), strerror(errno));
-            return;
-        } else {
-            numFds++;
-        }
+        paths.emplace_back(kDumpstateBoardPath + kDumpstateBoardFiles[i]);
+        remover.emplace_back(android::base::make_scope_guard(std::bind(
+            [](std::string path) {
+                if (remove(path.c_str()) != 0 && errno != ENOENT) {
+                    MYLOGE("Could not remove(%s): %s\n", path.c_str(), strerror(errno));
+                }
+            },
+            paths[i])));
     }
 
-    native_handle_t *handle = native_handle_create(numFds, 0);
+    sp<IDumpstateDevice> dumpstate_device(IDumpstateDevice::getService());
+    if (dumpstate_device == nullptr) {
+        MYLOGE("No IDumpstateDevice implementation\n");
+        return;
+    }
+
+    using ScopedNativeHandle =
+            std::unique_ptr<native_handle_t, std::function<void(native_handle_t*)>>;
+    ScopedNativeHandle handle(native_handle_create(static_cast<int>(paths.size()), 0),
+                              [](native_handle_t* handle) {
+                                  native_handle_close(handle);
+                                  native_handle_delete(handle);
+                              });
     if (handle == nullptr) {
         MYLOGE("Could not create native_handle\n");
         return;
     }
 
-    for (int i = 0; i < numFds; i++) {
-        handle->data[i] = fd[i].release();
-    }
+    for (size_t i = 0; i < paths.size(); i++) {
+        MYLOGI("Calling IDumpstateDevice implementation using path %s\n", paths[i].c_str());
 
-    // TODO: need a timeout mechanism so dumpstate does not hang on device implementation call.
-    android::hardware::Return<void> status = dumpstate_device->dumpstateBoard(handle);
-    if (!status.isOk()) {
-        MYLOGE("dumpstateBoard failed: %s\n", status.description().c_str());
-        native_handle_close(handle);
-        native_handle_delete(handle);
-        return;
-    }
-
-    for (int i = 0; i < numFds; i++) {
-        struct stat s;
-        if (fstat(handle->data[i], &s) == -1) {
-            MYLOGE("Failed to fstat %s: %d\n", kDumpstateBoardFiles[i].c_str(), errno);
-        } else if (s.st_size > 0) {
-            AddZipEntry(kDumpstateBoardFiles[i], path[i]);
-        } else {
-            MYLOGE("Ignoring empty %s\n", kDumpstateBoardFiles[i].c_str());
+        android::base::unique_fd fd(TEMP_FAILURE_RETRY(
+            open(paths[i].c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+                 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+        if (fd < 0) {
+            MYLOGE("Could not open file %s: %s\n", paths[i].c_str(), strerror(errno));
+            return;
         }
+        handle.get()->data[i] = fd.release();
+    }
+
+    // Given that bugreport is required to diagnose failures, it's better to
+    // set an arbitrary amount of timeout for IDumpstateDevice than to block the
+    // rest of bugreport. In the timeout case, we will kill dumpstate board HAL
+    // and grab whatever dumped
+    std::packaged_task<bool()>
+            dumpstate_task([paths, dumpstate_device, &handle]() -> bool {
+            android::hardware::Return<void> status = dumpstate_device->dumpstateBoard(handle.get());
+            if (!status.isOk()) {
+                MYLOGE("dumpstateBoard failed: %s\n", status.description().c_str());
+                return false;
+            }
+            return true;
+        });
+
+    auto result = dumpstate_task.get_future();
+    std::thread(std::move(dumpstate_task)).detach();
+
+    constexpr size_t timeout_sec = 30;
+    if (result.wait_for(std::chrono::seconds(timeout_sec)) != std::future_status::ready) {
+        MYLOGE("dumpstateBoard timed out after %zus, killing dumpstate vendor HAL\n", timeout_sec);
+        if (!android::base::SetProperty("ctl.interface_restart",
+                                        android::base::StringPrintf("%s/default",
+                                                                    IDumpstateDevice::descriptor))) {
+            MYLOGE("Couldn't restart dumpstate HAL\n");
+        }
+    }
+    // Wait some time for init to kill dumpstate vendor HAL
+    constexpr size_t killing_timeout_sec = 10;
+    if (result.wait_for(std::chrono::seconds(killing_timeout_sec)) != std::future_status::ready) {
+        MYLOGE("killing dumpstateBoard timed out after %zus, continue and "
+               "there might be racing in content\n", killing_timeout_sec);
+    }
+
+    auto file_sizes = std::make_unique<ssize_t[]>(paths.size());
+    for (size_t i = 0; i < paths.size(); i++) {
+        struct stat s;
+        if (fstat(handle.get()->data[i], &s) == -1) {
+            MYLOGE("Failed to fstat %s: %s\n", kDumpstateBoardFiles[i].c_str(),
+                   strerror(errno));
+            file_sizes[i] = -1;
+            continue;
+        }
+        file_sizes[i] = s.st_size;
+    }
+
+    for (size_t i = 0; i < paths.size(); i++) {
+        if (file_sizes[i] == -1) {
+            continue;
+        }
+        if (file_sizes[i] == 0) {
+            MYLOGE("Ignoring empty %s\n", kDumpstateBoardFiles[i].c_str());
+            continue;
+        }
+        AddZipEntry(kDumpstateBoardFiles[i], paths[i]);
     }
 
     printf("*** See dumpstate-board.txt entry ***\n");
-
-    native_handle_close(handle);
-    native_handle_delete(handle);
-
-    for (int i = 0; i < numFds; i++) {
-        if (remove(path[i].c_str()) != 0) {
-            MYLOGE("Could not remove(%s): %s\n", path[i].c_str(), strerror(errno));
-        }
-    }
 }
 
 static void ShowUsageAndExit(int exitCode = 1) {
@@ -1359,7 +1693,7 @@
             "  -p: capture screenshot to filename.png (requires -o)\n"
             "  -z: generate zipped file (requires -o)\n"
             "  -s: write output to control socket (for init)\n"
-            "  -S: write file location to control socket (for init; requires -o and -z)"
+            "  -S: write file location to control socket (for init; requires -o and -z)\n"
             "  -q: disable vibrate\n"
             "  -B: send broadcast when finished (requires -o)\n"
             "  -P: send broadcast when started and update system properties on "
@@ -1375,20 +1709,8 @@
     ShowUsageAndExit();
 }
 
-static void sig_handler(int) {
-    _exit(EXIT_FAILURE);
-}
-
 static void register_sig_handler() {
-    struct sigaction sa;
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = 0;
-    sa.sa_handler = sig_handler;
-    sigaction(SIGPIPE, &sa, NULL); // broken pipe
-    sigaction(SIGSEGV, &sa, NULL); // segment fault
-    sigaction(SIGINT, &sa, NULL); // ctrl-c
-    sigaction(SIGTERM, &sa, NULL); // killed
-    sigaction(SIGQUIT, &sa, NULL); // quit
+    signal(SIGPIPE, SIG_IGN);
 }
 
 bool Dumpstate::FinishZipFile() {
@@ -1438,7 +1760,7 @@
     return true;
 }
 
-static std::string SHA256_file_hash(std::string filepath) {
+static std::string SHA256_file_hash(const std::string& filepath) {
     android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY | O_NONBLOCK
             | O_CLOEXEC | O_NOFOLLOW)));
     if (fd == -1) {
@@ -1500,7 +1822,8 @@
     // clang-format on
 }
 
-int main(int argc, char *argv[]) {
+/** Main entry point for dumpstate. */
+int run_main(int argc, char* argv[]) {
     int do_add_date = 0;
     int do_zip_file = 0;
     int do_vibrate = 1;
@@ -1513,6 +1836,9 @@
     bool show_header_only = false;
     bool do_start_service = false;
     bool telephony_only = false;
+    bool wifi_only = false;
+    int dup_stdout_fd;
+    int dup_stderr_fd;
 
     /* set as high priority, and protect from OOM killer */
     setpriority(PRIO_PROCESS, 0, -20);
@@ -1579,9 +1905,14 @@
             is_remote_mode = 1;
             do_fb = 0;
         } else if (ds.extra_options_ == "bugreportwear") {
+            do_start_service = true;
             ds.update_progress_ = true;
+            do_zip_file = 1;
         } else if (ds.extra_options_ == "bugreporttelephony") {
             telephony_only = true;
+        } else if (ds.extra_options_ == "bugreportwifi") {
+            wifi_only = true;
+            do_zip_file = 1;
         } else {
             MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str());
         }
@@ -1700,6 +2031,8 @@
 
         if (telephony_only) {
             ds.base_name_ += "-telephony";
+        } else if (wifi_only) {
+            ds.base_name_ += "-wifi";
         }
 
         if (do_fb) {
@@ -1781,11 +2114,13 @@
     }
 
     if (is_redirecting) {
+        TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
         redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
         if (chown(ds.log_path_.c_str(), AID_SHELL, AID_SHELL)) {
             MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n",
                    ds.log_path_.c_str(), strerror(errno));
         }
+        TEMP_FAILURE_RETRY(dup_stdout_fd = dup(fileno(stdout)));
         /* TODO: rather than generating a text file now and zipping it later,
            it would be more efficient to redirect stdout to the zip entry
            directly, but the libziparchive doesn't support that option yet. */
@@ -1807,6 +2142,8 @@
     if (telephony_only) {
         DumpstateTelephonyOnly();
         ds.DumpstateBoard();
+    } else if (wifi_only) {
+        DumpstateWifiOnly();
     } else {
         // Dumps systrace right away, otherwise it will be filled with unnecessary events.
         // First try to dump anrd trace if the daemon is running. Otherwise, dump
@@ -1817,10 +2154,7 @@
 
         // Invoking the following dumpsys calls before dump_traces() to try and
         // keep the system stats as close to its initial state as possible.
-        RunDumpsys("DUMPSYS MEMINFO", {"meminfo", "-a"},
-                   CommandOptions::WithTimeout(90).DropRoot().Build());
-        RunDumpsys("DUMPSYS CPUINFO", {"cpuinfo", "-a"},
-                   CommandOptions::WithTimeout(10).DropRoot().Build());
+        RunDumpsysCritical();
 
         // TODO: Drop root user and move into dumpstate() once b/28633932 is fixed.
         dump_raft();
@@ -1829,11 +2163,12 @@
         dump_traces_path = dump_traces();
 
         /* Run some operations that require root. */
-        tombstone_data.reset(GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX, !ds.IsZipping()));
-        anr_data.reset(GetDumpFds(ANR_DIR, ANR_FILE_PREFIX, !ds.IsZipping()));
+        ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX, !ds.IsZipping());
+        ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX, !ds.IsZipping());
 
         ds.AddDir(RECOVERY_DIR, true);
         ds.AddDir(RECOVERY_DATA_DIR, true);
+        ds.AddDir(UPDATE_ENGINE_LOG_DIR, true);
         ds.AddDir(LOGPERSIST_DATA_DIR, false);
         if (!PropertiesHelper::IsUserBuild()) {
             ds.AddDir(PROFILE_DATA_DIR_CUR, true);
@@ -1850,6 +2185,9 @@
         RunCommand("DETAILED SOCKET STATE", {"ss", "-eionptu"},
                    CommandOptions::WithTimeout(10).Build());
 
+        // Run iotop as root to show top 100 IO threads
+        RunCommand("IOTOP", {"iotop", "-n", "1", "-m", "100"});
+
         if (!DropRootUser()) {
             return -1;
         }
@@ -1859,7 +2197,7 @@
 
     /* close output if needed */
     if (is_redirecting) {
-        fclose(stdout);
+        TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout)));
     }
 
     /* rename or zip the (now complete) .tmp file to its final location */
@@ -1935,9 +2273,11 @@
     }
 
     /* vibrate a few but shortly times to let user know it's finished */
-    for (int i = 0; i < 3; i++) {
-        Vibrate(75);
-        usleep((75 + 50) * 1000);
+    if (do_vibrate) {
+        for (int i = 0; i < 3; i++) {
+            Vibrate(75);
+            usleep((75 + 50) * 1000);
+        }
     }
 
     /* tell activity manager we're done */
@@ -1990,7 +2330,7 @@
     MYLOGI("done (id %d)\n", ds.id_);
 
     if (is_redirecting) {
-        fclose(stderr);
+        TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
     }
 
     if (use_control_socket && ds.control_socket_fd_ != -1) {
@@ -1998,5 +2338,8 @@
         close(ds.control_socket_fd_);
     }
 
+    ds.tombstone_data_.clear();
+    ds.anr_data_.clear();
+
     return 0;
 }
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 7757c1e..b220013 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include <android-base/macros.h>
+#include <android-base/unique_fd.h>
 #include <android/os/IDumpstateListener.h>
 #include <utils/StrongPointer.h>
 #include <ziparchive/zip_writer.h>
@@ -145,13 +146,13 @@
  *
  * See bugreport-format.md for more info.
  */
-static std::string VERSION_CURRENT = "1.0";
+static std::string VERSION_CURRENT = "2.0";
 
 /*
  * Temporary version that adds a anr-traces.txt entry. Once tools support it, the current version
- * will be bumped to 2.0-dev-1.
+ * will be bumped to 3.0.
  */
-static std::string VERSION_SPLIT_ANR = "2.0-dev-1";
+static std::string VERSION_SPLIT_ANR = "3.0-dev-split-anr";
 
 /*
  * "Alias" for the current version.
@@ -159,6 +160,20 @@
 static std::string VERSION_DEFAULT = "default";
 
 /*
+ * Structure that contains the information of an open dump file.
+ */
+struct DumpData {
+    // Path of the file.
+    std::string name;
+
+    // Open file descriptor for the file.
+    android::base::unique_fd fd;
+
+    // Modification time of the file.
+    time_t mtime;
+};
+
+/*
  * Main class driving a bugreport generation.
  *
  * Currently, it only contains variables that are accessed externally, but gradually the functions
@@ -190,19 +205,19 @@
 
     /*
      * Runs `dumpsys` with the given arguments, automatically setting its timeout
-     * (`-t` argument)
+     * (`-T` argument)
      * according to the command options.
      *
      * |title| description of the command printed on `stdout` (or empty to skip
      * description).
      * |dumpsys_args| `dumpsys` arguments (except `-t`).
      * |options| optional argument defining the command's behavior.
-     * |dumpsys_timeout| when > 0, defines the value passed to `dumpsys -t` (otherwise it uses the
+     * |dumpsys_timeout| when > 0, defines the value passed to `dumpsys -T` (otherwise it uses the
      * timeout from `options`)
      */
     void RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsys_args,
                     const android::os::dumpstate::CommandOptions& options = DEFAULT_DUMPSYS,
-                    long dumpsys_timeout = 0);
+                    long dumpsys_timeout_ms = 0);
 
     /*
      * Prints the contents of a file.
@@ -220,8 +235,14 @@
 
     /*
      * Adds a new entry to the existing zip file.
+     *
+     * |entry_name| destination path of the new entry.
+     * |fd| file descriptor to read from.
+     * |timeout| timeout to terminate the read if not completed. Set
+     * value of 0s (default) to disable timeout.
      */
-    bool AddZipEntryFromFd(const std::string& entry_name, int fd);
+    android::status_t AddZipEntryFromFd(const std::string& entry_name, int fd,
+                                        std::chrono::milliseconds timeout);
 
     /*
      * Adds a text entry entry to the existing zip file.
@@ -266,6 +287,9 @@
     /* Gets the path of a bugreport file with the given suffix. */
     std::string GetPath(const std::string& suffix) const;
 
+    /* Returns true if the current version supports priority dump feature. */
+    bool CurrentVersionSupportsPriorityDumps() const;
+
     // TODO: initialize fields on constructor
 
     // dumpstate id - unique after each device reboot.
@@ -332,14 +356,21 @@
     // Pointer to the zip structure.
     std::unique_ptr<ZipWriter> zip_writer_;
 
-    // Binder object listing to progress.
+    // Binder object listening to progress.
     android::sp<android::os::IDumpstateListener> listener_;
     std::string listener_name_;
+    bool report_section_;
 
     // Notification title and description
     std::string notification_title;
     std::string notification_description;
 
+    // List of open tombstone dump files.
+    std::vector<DumpData> tombstone_data_;
+
+    // List of open ANR dump files.
+    std::vector<DumpData> anr_data_;
+
   private:
     // Used by GetInstance() only.
     Dumpstate(const std::string& version = VERSION_CURRENT);
@@ -424,6 +455,9 @@
 /** Gets command-line arguments. */
 void format_args(int argc, const char *argv[], std::string *args);
 
+/** Main entry point for dumpstate. */
+int run_main(int argc, char* argv[]);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/vulkan/libvulkan/vulkan_loader_data.cpp b/cmds/dumpstate/main.cpp
similarity index 71%
rename from vulkan/libvulkan/vulkan_loader_data.cpp
rename to cmds/dumpstate/main.cpp
index 0eda0af..78aad11 100644
--- a/vulkan/libvulkan/vulkan_loader_data.cpp
+++ b/cmds/dumpstate/main.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-#include <vulkan/vulkan_loader_data.h>
+#include "dumpstate.h"
 
-using namespace vulkan;
-
-LoaderData& LoaderData::GetInstance() {
-    static LoaderData loader_data = {};
-    return loader_data;
+int main(int argc, char* argv[]) {
+    return run_main(argc, argv);
 }
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
new file mode 100644
index 0000000..61a5ef5
--- /dev/null
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2018 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <libgen.h>
+
+#include <android-base/file.h>
+#include <cutils/properties.h>
+#include <ziparchive/zip_archive.h>
+
+#include "dumpstate.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+using ::testing::Test;
+using ::std::literals::chrono_literals::operator""s;
+
+struct SectionInfo {
+    std::string name;
+    status_t status;
+    int32_t size_bytes;
+    int32_t duration_ms;
+};
+
+/**
+ * Listens to bugreport progress and updates the user by writing the progress to STDOUT. All the
+ * section details generated by dumpstate are added to a vector to be used by Tests later.
+ */
+class DumpstateListener : public IDumpstateListener {
+  public:
+    int outFd_, max_progress_;
+    std::shared_ptr<std::vector<SectionInfo>> sections_;
+    DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections)
+        : outFd_(fd), max_progress_(5000), sections_(sections) {
+    }
+    binder::Status onProgressUpdated(int32_t progress) override {
+        dprintf(outFd_, "\rIn progress %d/%d", progress, max_progress_);
+        return binder::Status::ok();
+    }
+    binder::Status onMaxProgressUpdated(int32_t max_progress) override {
+        max_progress_ = max_progress;
+        return binder::Status::ok();
+    }
+    binder::Status onSectionComplete(const ::std::string& name, int32_t status, int32_t size_bytes,
+                                     int32_t duration_ms) override {
+        sections_->push_back({name, status, size_bytes, duration_ms});
+        return binder::Status::ok();
+    }
+    IBinder* onAsBinder() override {
+        return nullptr;
+    }
+};
+
+/**
+ * Generates bug report and provide access to the bug report file and other info for other tests.
+ * Since bug report generation is slow, the bugreport is only generated once.
+ */
+class ZippedBugreportGenerationTest : public Test {
+  public:
+    static std::shared_ptr<std::vector<SectionInfo>> sections;
+    static Dumpstate& ds;
+    static std::chrono::milliseconds duration;
+    static void SetUpTestCase() {
+        property_set("dumpstate.options", "bugreportplus");
+        // clang-format off
+        char* argv[] = {
+            (char*)"dumpstate",
+            (char*)"-d",
+            (char*)"-z",
+            (char*)"-B",
+            (char*)"-o",
+            (char*)dirname(android::base::GetExecutablePath().c_str())
+        };
+        // clang-format on
+        sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
+        ds.listener_ = listener;
+        ds.listener_name_ = "Smokey";
+        ds.report_section_ = true;
+        auto start = std::chrono::steady_clock::now();
+        run_main(ARRAY_SIZE(argv), argv);
+        auto end = std::chrono::steady_clock::now();
+        duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+    }
+
+    static const char* getZipFilePath() {
+        return ds.GetPath(".zip").c_str();
+    }
+};
+std::shared_ptr<std::vector<SectionInfo>> ZippedBugreportGenerationTest::sections =
+    std::make_shared<std::vector<SectionInfo>>();
+Dumpstate& ZippedBugreportGenerationTest::ds = Dumpstate::GetInstance();
+std::chrono::milliseconds ZippedBugreportGenerationTest::duration = 0s;
+
+TEST_F(ZippedBugreportGenerationTest, IsGeneratedWithoutErrors) {
+    EXPECT_EQ(access(getZipFilePath(), F_OK), 0);
+}
+
+TEST_F(ZippedBugreportGenerationTest, Is3MBto30MBinSize) {
+    struct stat st;
+    EXPECT_EQ(stat(getZipFilePath(), &st), 0);
+    EXPECT_GE(st.st_size, 3000000 /* 3MB */);
+    EXPECT_LE(st.st_size, 30000000 /* 30MB */);
+}
+
+TEST_F(ZippedBugreportGenerationTest, TakesBetween30And150Seconds) {
+    EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time "
+                             << duration.count() << " s.";
+    EXPECT_LE(duration, 150s) << "Expected completion in less than 150s. Actual time "
+                              << duration.count() << " s.";
+}
+
+/**
+ * Run tests on contents of zipped bug report.
+ */
+class ZippedBugReportContentsTest : public Test {
+  public:
+    ZipArchiveHandle handle;
+    void SetUp() {
+        ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath(), &handle), 0);
+    }
+    void TearDown() {
+        CloseArchive(handle);
+    }
+
+    void FileExists(const char* filename, uint32_t minsize, uint32_t maxsize) {
+        ZipEntry entry;
+        EXPECT_EQ(FindEntry(handle, ZipString(filename), &entry), 0);
+        EXPECT_GT(entry.uncompressed_length, minsize);
+        EXPECT_LT(entry.uncompressed_length, maxsize);
+    }
+};
+
+TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) {
+    ZipEntry mainEntryLoc;
+    // contains main entry name file
+    EXPECT_EQ(FindEntry(handle, ZipString("main_entry.txt"), &mainEntryLoc), 0);
+
+    char* buf = new char[mainEntryLoc.uncompressed_length];
+    ExtractToMemory(handle, &mainEntryLoc, (uint8_t*)buf, mainEntryLoc.uncompressed_length);
+    delete[] buf;
+
+    // contains main entry file
+    FileExists(buf, 1000000U, 50000000U);
+}
+
+TEST_F(ZippedBugReportContentsTest, ContainsVersion) {
+    ZipEntry entry;
+    // contains main entry name file
+    EXPECT_EQ(FindEntry(handle, ZipString("version.txt"), &entry), 0);
+
+    char* buf = new char[entry.uncompressed_length + 1];
+    ExtractToMemory(handle, &entry, (uint8_t*)buf, entry.uncompressed_length);
+    buf[entry.uncompressed_length] = 0;
+    EXPECT_STREQ(buf, ZippedBugreportGenerationTest::ds.version_.c_str());
+    delete[] buf;
+}
+
+TEST_F(ZippedBugReportContentsTest, ContainsBoardSpecificFiles) {
+    FileExists("dumpstate_board.bin", 1000000U, 80000000U);
+    FileExists("dumpstate_board.txt", 100000U, 1000000U);
+}
+
+// Spot check on some files pulled from the file system
+TEST_F(ZippedBugReportContentsTest, ContainsSomeFileSystemFiles) {
+    // FS/proc/*/mountinfo size > 0
+    FileExists("FS/proc/1/mountinfo", 0U, 100000U);
+
+    // FS/data/misc/profiles/cur/0/*/primary.prof size > 0
+    FileExists("FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", 0U, 100000U);
+}
+
+/**
+ * Runs tests on section data generated by dumpstate and captured by DumpstateListener.
+ */
+class BugreportSectionTest : public Test {
+  public:
+    int numMatches(const std::string& substring) {
+        int matches = 0;
+        for (auto const& section : *ZippedBugreportGenerationTest::sections) {
+            if (section.name.find(substring) != std::string::npos) {
+                matches++;
+            }
+        }
+        return matches;
+    }
+    void SectionExists(const std::string& sectionName, int minsize) {
+        for (auto const& section : *ZippedBugreportGenerationTest::sections) {
+            if (sectionName == section.name) {
+                EXPECT_GE(section.size_bytes, minsize);
+                return;
+            }
+        }
+        FAIL() << sectionName << " not found.";
+    }
+};
+
+// Test all sections are generated without timeouts or errors
+TEST_F(BugreportSectionTest, GeneratedWithoutErrors) {
+    for (auto const& section : *ZippedBugreportGenerationTest::sections) {
+        EXPECT_EQ(section.status, 0) << section.name << " failed with status " << section.status;
+    }
+}
+
+TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
+    int numSections = numMatches("DUMPSYS CRITICAL");
+    EXPECT_GE(numSections, 3);
+}
+
+TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) {
+    int numSections = numMatches("DUMPSYS HIGH");
+    EXPECT_GE(numSections, 2);
+}
+
+TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) {
+    int allSections = numMatches("DUMPSYS");
+    int criticalSections = numMatches("DUMPSYS CRITICAL");
+    int highSections = numMatches("DUMPSYS HIGH");
+    int normalSections = allSections - criticalSections - highSections;
+
+    EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections
+                                  << "High:" << highSections << "Normal:" << normalSections << ")";
+}
+
+TEST_F(BugreportSectionTest, Atleast1ProtoDumpsysSectionGenerated) {
+    int numSections = numMatches("proto/");
+    EXPECT_GE(numSections, 1);
+}
+
+// Test if some critical sections are being generated.
+TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) {
+    SectionExists("DUMPSYS CRITICAL - SurfaceFlinger", /* bytes= */ 10000);
+}
+
+TEST_F(BugreportSectionTest, ActivitySectionsGenerated) {
+    SectionExists("DUMPSYS CRITICAL - activity", /* bytes= */ 5000);
+    SectionExists("DUMPSYS - activity", /* bytes= */ 10000);
+}
+
+TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) {
+    SectionExists("DUMPSYS CRITICAL - cpuinfo", /* bytes= */ 1000);
+}
+
+TEST_F(BugreportSectionTest, WindowSectionGenerated) {
+    SectionExists("DUMPSYS CRITICAL - window", /* bytes= */ 20000);
+}
+
+TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) {
+    SectionExists("DUMPSYS HIGH - connectivity", /* bytes= */ 5000);
+    SectionExists("DUMPSYS - connectivity", /* bytes= */ 5000);
+}
+
+TEST_F(BugreportSectionTest, MeminfoSectionGenerated) {
+    SectionExists("DUMPSYS HIGH - meminfo", /* bytes= */ 100000);
+}
+
+TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) {
+    SectionExists("DUMPSYS - batterystats", /* bytes= */ 1000);
+}
+
+TEST_F(BugreportSectionTest, WifiSectionGenerated) {
+    SectionExists("DUMPSYS - wifi", /* bytes= */ 100000);
+}
+
+}  // namespace dumpstate
+}  // namespace os
+}  // namespace android
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index a94cf99..838b385 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -58,6 +58,8 @@
   public:
     MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress));
     MOCK_METHOD1(onMaxProgressUpdated, binder::Status(int32_t max_progress));
+    MOCK_METHOD4(onSectionComplete, binder::Status(const ::std::string& name, int32_t status,
+                                                   int32_t size, int32_t durationMs));
 
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
@@ -477,6 +479,48 @@
     EXPECT_THAT(err, StrEq("stderr\n"));
 }
 
+TEST_F(DumpstateTest, RunCommandAsRootIfAvailableOnUserBuild) {
+    if (!IsStandalone()) {
+        // TODO: temporarily disabled because it might cause other tests to fail after dropping
+        // to Shell - need to refactor tests to avoid this problem)
+        MYLOGE("Skipping DumpstateTest.RunCommandAsRootIfAvailableOnUserBuild() on test suite\n")
+        return;
+    }
+    if (!PropertiesHelper::IsUserBuild()) {
+        // Emulates user build if necessarily.
+        SetBuildType("user");
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).AsRootIfAvailable().Build()));
+
+    EXPECT_THAT(out, StrEq("2000\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandAsRootIfAvailableOnDebugBuild) {
+    if (!IsStandalone()) {
+        // TODO: temporarily disabled because it might cause other tests to fail after dropping
+        // to Shell - need to refactor tests to avoid this problem)
+        MYLOGE("Skipping DumpstateTest.RunCommandAsRootIfAvailableOnDebugBuild() on test suite\n")
+        return;
+    }
+    if (PropertiesHelper::IsUserBuild()) {
+        ALOGI("Skipping RunCommandAsRootNonUserBuild on user builds\n");
+        return;
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).AsRootIfAvailable().Build()));
+
+    EXPECT_THAT(out, StrEq("0\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
 TEST_F(DumpstateTest, DumpFileNotFoundNoTitle) {
     EXPECT_EQ(-1, DumpFile("", "/I/cant/believe/I/exist"));
     EXPECT_THAT(out,
@@ -559,27 +603,43 @@
 TEST_F(DumpstateServiceTest, SetListenerNoName) {
     sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("", listener, &token).isOk());
+    EXPECT_TRUE(dss.setListener("", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
 }
 
 TEST_F(DumpstateServiceTest, SetListenerNoPointer) {
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("whatever", nullptr, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatever", nullptr, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
 }
 
 TEST_F(DumpstateServiceTest, SetListenerTwice) {
     sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("whatever", listener, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatever", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, NotNull());
     EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_FALSE(Dumpstate::GetInstance().report_section_);
 
     token.clear();
-    EXPECT_TRUE(dss.setListener("whatsoever", listener, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatsoever", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
     EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_FALSE(Dumpstate::GetInstance().report_section_);
+}
+
+TEST_F(DumpstateServiceTest, SetListenerWithSectionDetails) {
+    sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+    sp<IDumpstateToken> token;
+    Dumpstate::GetInstance().listener_ = nullptr;
+    EXPECT_TRUE(
+        dss.setListener("whatever", listener, /* getSectionDetails = */ true, &token).isOk());
+    ASSERT_THAT(token, NotNull());
+    EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_TRUE(Dumpstate::GetInstance().report_section_);
 }
 
 class ProgressTest : public DumpstateBaseTest {
@@ -959,7 +1019,7 @@
         err, StartsWith("stderr\n*** command '" + kSimpleCommand + " --crash' failed: exit code"));
 }
 
-TEST_F(DumpstateUtilTest, RunCommandTimesout) {
+TEST_F(DumpstateUtilTest, RunCommandTimesoutWithSec) {
     CreateFd("RunCommandTimesout.txt");
     EXPECT_EQ(-1, RunCommand("", {kSimpleCommand, "--sleep", "2"},
                              CommandOptions::WithTimeout(1).Build()));
@@ -969,6 +1029,17 @@
                                 " --sleep 2' timed out after 1"));
 }
 
+TEST_F(DumpstateUtilTest, RunCommandTimesoutWithMsec) {
+    CreateFd("RunCommandTimesout.txt");
+    EXPECT_EQ(-1, RunCommand("", {kSimpleCommand, "--sleep", "2"},
+                             CommandOptions::WithTimeoutInMs(1000).Build()));
+    EXPECT_THAT(out, StartsWith("stdout line1\n*** command '" + kSimpleCommand +
+                                " --sleep 2' timed out after 1"));
+    EXPECT_THAT(err, StartsWith("sleeping for 2s\n*** command '" + kSimpleCommand +
+                                " --sleep 2' timed out after 1"));
+}
+
+
 TEST_F(DumpstateUtilTest, RunCommandIsKilled) {
     CreateFd("RunCommandIsKilled.txt");
     CaptureStderr();
@@ -1053,6 +1124,51 @@
     EXPECT_THAT(err, StrEq("stderr\n"));
 }
 
+
+TEST_F(DumpstateUtilTest, RunCommandAsRootIfAvailableOnUserBuild) {
+    if (!IsStandalone()) {
+        // TODO: temporarily disabled because it might cause other tests to fail after dropping
+        // to Shell - need to refactor tests to avoid this problem)
+        MYLOGE("Skipping DumpstateUtilTest.RunCommandAsRootIfAvailableOnUserBuild() on test suite\n")
+        return;
+    }
+    CreateFd("RunCommandAsRootIfAvailableOnUserBuild.txt");
+    if (!PropertiesHelper::IsUserBuild()) {
+        // Emulates user build if necessarily.
+        SetBuildType("user");
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).AsRootIfAvailable().Build()));
+
+    EXPECT_THAT(out, StrEq("2000\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateUtilTest, RunCommandAsRootIfAvailableOnDebugBuild) {
+    if (!IsStandalone()) {
+        // TODO: temporarily disabled because it might cause other tests to fail after dropping
+        // to Shell - need to refactor tests to avoid this problem)
+        MYLOGE("Skipping DumpstateUtilTest.RunCommandAsRootIfAvailableOnDebugBuild() on test suite\n")
+        return;
+    }
+    CreateFd("RunCommandAsRootIfAvailableOnDebugBuild.txt");
+    if (PropertiesHelper::IsUserBuild()) {
+        ALOGI("Skipping RunCommandAsRootNonUserBuild on user builds\n");
+        return;
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {kSimpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).AsRootIfAvailable().Build()));
+
+    EXPECT_THAT(out, StrEq("0\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
 TEST_F(DumpstateUtilTest, RunCommandDropRoot) {
     if (!IsStandalone()) {
         // TODO: temporarily disabled because it might cause other tests to fail after dropping
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index 93f4c22..022f4fc 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -48,10 +48,10 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <android/hidl/manager/1.0/IServiceManager.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
 #include <debuggerd/client.h>
+#include <dumputils/dump_utils.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 
@@ -76,34 +76,6 @@
     return ds.RunCommand(title, full_command, options);
 }
 
-/* list of native processes to include in the native dumps */
-// This matches the /proc/pid/exe link instead of /proc/pid/cmdline.
-static const char* native_processes_to_dump[] = {
-        "/system/bin/audioserver",
-        "/system/bin/cameraserver",
-        "/system/bin/drmserver",
-        "/system/bin/mediadrmserver",
-        "/system/bin/mediaextractor", // media.extractor
-        "/system/bin/mediaserver",
-        "/system/bin/sdcard",
-        "/system/bin/surfaceflinger",
-        "/system/bin/vehicle_network_service",
-        "/vendor/bin/hw/android.hardware.media.omx@1.0-service", // media.codec
-        NULL,
-};
-
-/* list of hal interface to dump containing process during native dumps */
-static const char* hal_interfaces_to_dump[] {
-        "android.hardware.audio@2.0::IDevicesFactory",
-        "android.hardware.bluetooth@1.0::IBluetoothHci",
-        "android.hardware.camera.provider@2.4::ICameraProvider",
-        "android.hardware.graphics.composer@2.1::IComposer",
-        "android.hardware.media.omx@1.0::IOmx",
-        "android.hardware.sensors@1.0::ISensors",
-        "android.hardware.vr@1.0::IVr",
-        NULL,
-};
-
 // Reasonable value for max stats.
 static const int STATS_MAX_N_RUNS = 1000;
 static const long STATS_MAX_AVERAGE = 100000;
@@ -215,10 +187,10 @@
     return progress_;
 }
 
-bool Progress::Inc(int32_t delta) {
+bool Progress::Inc(int32_t delta_sec) {
     bool changed = false;
-    if (delta >= 0) {
-        progress_ += delta;
+    if (delta_sec >= 0) {
+        progress_ += delta_sec;
         if (progress_ > max_) {
             int32_t old_max = max_;
             max_ = floor((float)progress_ * growth_factor_);
@@ -628,7 +600,7 @@
     struct dirent *d;
     char *newpath = NULL;
     const char *slash = "/";
-    int fd, retval = 0;
+    int retval = 0;
 
     if (!title.empty()) {
         printf("------ %s (%s) ------\n", title.c_str(), dir);
@@ -670,13 +642,13 @@
             }
             continue;
         }
-        fd = TEMP_FAILURE_RETRY(open(newpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
-        if (fd < 0) {
-            retval = fd;
+        android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(newpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+        if (fd.get() < 0) {
+            retval = -1;
             printf("*** %s: %s\n", newpath, strerror(errno));
             continue;
         }
-        (*dump_from_fd)(NULL, newpath, fd);
+        (*dump_from_fd)(NULL, newpath, fd.get());
     }
     closedir(dirp);
     if (!title.empty()) {
@@ -695,11 +667,9 @@
     int flags = fcntl(fd, F_GETFL);
     if (flags == -1) {
         printf("*** %s: failed to get flags on fd %d: %s\n", path, fd, strerror(errno));
-        close(fd);
         return -1;
     } else if (!(flags & O_NONBLOCK)) {
         printf("*** %s: fd must have O_NONBLOCK set.\n", path);
-        close(fd);
         return -1;
     }
     return DumpFileFromFdToFd(title, path, fd, STDOUT_FILENO, PropertiesHelper::IsDryRun());
@@ -721,9 +691,9 @@
 }
 
 void Dumpstate::RunDumpsys(const std::string& title, const std::vector<std::string>& dumpsys_args,
-                           const CommandOptions& options, long dumpsysTimeout) {
-    long timeout = dumpsysTimeout > 0 ? dumpsysTimeout : options.Timeout();
-    std::vector<std::string> dumpsys = {"/system/bin/dumpsys", "-t", std::to_string(timeout)};
+                           const CommandOptions& options, long dumpsysTimeoutMs) {
+    long timeout_ms = dumpsysTimeoutMs > 0 ? dumpsysTimeoutMs : options.TimeoutInMs();
+    std::vector<std::string> dumpsys = {"/system/bin/dumpsys", "-T", std::to_string(timeout_ms)};
     dumpsys.insert(dumpsys.end(), dumpsys_args.begin(), dumpsys_args.end());
     RunCommand(title, dumpsys, options);
 }
@@ -809,53 +779,6 @@
     _redirect_to_file(redirect, path, O_APPEND);
 }
 
-static bool should_dump_hal_interface(const char* interface) {
-    for (const char** i = hal_interfaces_to_dump; *i; i++) {
-        if (!strcmp(*i, interface)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-static bool should_dump_native_traces(const char* path) {
-    for (const char** p = native_processes_to_dump; *p; p++) {
-        if (!strcmp(*p, path)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-std::set<int> get_interesting_hal_pids() {
-    using android::hidl::manager::V1_0::IServiceManager;
-    using android::sp;
-    using android::hardware::Return;
-
-    sp<IServiceManager> manager = IServiceManager::getService();
-    std::set<int> pids;
-
-    Return<void> ret = manager->debugDump([&](auto& hals) {
-        for (const auto &info : hals) {
-            if (info.pid == static_cast<int>(IServiceManager::PidConstant::NO_PID)) {
-                continue;
-            }
-
-            if (!should_dump_hal_interface(info.interfaceName.c_str())) {
-                continue;
-            }
-
-            pids.insert(info.pid);
-        }
-    });
-
-    if (!ret.isOk()) {
-        MYLOGE("Could not get list of HAL PIDs: %s\n", ret.description().c_str());
-    }
-
-    return pids; // whether it was okay or not
-}
-
 const char* DumpTraces(const std::string& traces_path);
 const char* DumpTracesTombstoned(const std::string& traces_dir);
 
@@ -876,18 +799,6 @@
     return nullptr;
 }
 
-static bool IsZygote(int pid) {
-    static const std::string kZygotePrefix = "zygote";
-
-    std::string cmdline;
-    if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid),
-                                         &cmdline)) {
-        return true;
-    }
-
-    return (cmdline.find(kZygotePrefix) == 0);
-}
-
 const char* DumpTracesTombstoned(const std::string& traces_dir) {
     const std::string temp_file_pattern = traces_dir + "/dumptrace_XXXXXX";
 
@@ -1163,14 +1074,14 @@
 }
 
 // TODO: make this function thread safe if sections are generated in parallel.
-void Dumpstate::UpdateProgress(int32_t delta) {
+void Dumpstate::UpdateProgress(int32_t delta_sec) {
     if (progress_ == nullptr) {
         MYLOGE("UpdateProgress: progress_ not set\n");
         return;
     }
 
     // Always update progess so stats can be tuned...
-    bool max_changed = progress_->Inc(delta);
+    bool max_changed = progress_->Inc(delta_sec);
 
     // ...but only notifiy listeners when necessary.
     if (!update_progress_) return;
diff --git a/cmds/dumpsys/Android.bp b/cmds/dumpsys/Android.bp
index 3476964..f68b862 100644
--- a/cmds/dumpsys/Android.bp
+++ b/cmds/dumpsys/Android.bp
@@ -17,6 +17,10 @@
         "libbinder",
     ],
 
+    static_libs: [
+        "libserviceutils",
+    ],
+
     clang: true,
 }
 
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index fa6f6df..5412d4d 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -25,6 +25,7 @@
 #include <binder/Parcel.h>
 #include <binder/ProcessState.h>
 #include <binder/TextOutput.h>
+#include <serviceutils/PriorityDumper.h>
 #include <utils/Log.h>
 #include <utils/Vector.h>
 
@@ -42,9 +43,11 @@
 #include "dumpsys.h"
 
 using namespace android;
-using android::base::StringPrintf;
-using android::base::unique_fd;
-using android::base::WriteFully;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::unique_fd;
+using ::android::base::WriteFully;
+using ::android::base::WriteStringToFd;
 
 static int sort_func(const String16* lhs, const String16* rhs)
 {
@@ -53,13 +56,19 @@
 
 static void usage() {
     fprintf(stderr,
-        "usage: dumpsys\n"
+            "usage: dumpsys\n"
             "         To dump all services.\n"
             "or:\n"
-            "       dumpsys [-t TIMEOUT] [--help | -l | --skip SERVICES | SERVICE [ARGS]]\n"
+            "       dumpsys [-t TIMEOUT] [--priority LEVEL] [--help | -l | --skip SERVICES | "
+            "SERVICE [ARGS]]\n"
             "         --help: shows this help\n"
             "         -l: only list services, do not dump them\n"
-            "         -t TIMEOUT: TIMEOUT to use in seconds instead of default 10 seconds\n"
+            "         -t TIMEOUT_SEC: TIMEOUT to use in seconds instead of default 10 seconds\n"
+            "         -T TIMEOUT_MS: TIMEOUT to use in milliseconds instead of default 10 seconds\n"
+            "         --proto: filter services that support dumping data in proto format. Dumps"
+            "               will be in proto format.\n"
+            "         --priority LEVEL: filter services based on specified priority\n"
+            "               LEVEL must be one of CRITICAL | HIGH | NORMAL\n"
             "         --skip SERVICES: dumps all services but SERVICES (comma-separated list)\n"
             "         SERVICE [ARGS]: dumps only service SERVICE, optionally passing ARGS to it\n");
 }
@@ -73,18 +82,51 @@
     return false;
 }
 
+static bool ConvertPriorityTypeToBitmask(const String16& type, int& bitmask) {
+    if (type == PriorityDumper::PRIORITY_ARG_CRITICAL) {
+        bitmask = IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL;
+        return true;
+    }
+    if (type == PriorityDumper::PRIORITY_ARG_HIGH) {
+        bitmask = IServiceManager::DUMP_FLAG_PRIORITY_HIGH;
+        return true;
+    }
+    if (type == PriorityDumper::PRIORITY_ARG_NORMAL) {
+        bitmask = IServiceManager::DUMP_FLAG_PRIORITY_NORMAL;
+        return true;
+    }
+    return false;
+}
+
+String16 ConvertBitmaskToPriorityType(int bitmask) {
+    if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL) {
+        return String16(PriorityDumper::PRIORITY_ARG_CRITICAL);
+    }
+    if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_HIGH) {
+        return String16(PriorityDumper::PRIORITY_ARG_HIGH);
+    }
+    if (bitmask == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL) {
+        return String16(PriorityDumper::PRIORITY_ARG_NORMAL);
+    }
+    return String16("");
+}
+
 int Dumpsys::main(int argc, char* const argv[]) {
     Vector<String16> services;
     Vector<String16> args;
+    String16 priorityType;
     Vector<String16> skippedServices;
+    Vector<String16> protoServices;
     bool showListOnly = false;
     bool skipServices = false;
-    int timeoutArg = 10;
-    static struct option longOptions[] = {
-        {"skip", no_argument, 0,  0 },
-        {"help", no_argument, 0,  0 },
-        {     0,           0, 0,  0 }
-    };
+    bool asProto = false;
+    int timeoutArgMs = 10000;
+    int priorityFlags = IServiceManager::DUMP_FLAG_PRIORITY_ALL;
+    static struct option longOptions[] = {{"priority", required_argument, 0, 0},
+                                          {"proto", no_argument, 0, 0},
+                                          {"skip", no_argument, 0, 0},
+                                          {"help", no_argument, 0, 0},
+                                          {0, 0, 0, 0}};
 
     // Must reset optind, otherwise subsequent calls will fail (wouldn't happen on main.cpp, but
     // happens on test cases).
@@ -93,7 +135,7 @@
         int c;
         int optionIndex = 0;
 
-        c = getopt_long(argc, argv, "+t:l", longOptions, &optionIndex);
+        c = getopt_long(argc, argv, "+t:T:l", longOptions, &optionIndex);
 
         if (c == -1) {
             break;
@@ -103,18 +145,39 @@
         case 0:
             if (!strcmp(longOptions[optionIndex].name, "skip")) {
                 skipServices = true;
+            } else if (!strcmp(longOptions[optionIndex].name, "proto")) {
+                asProto = true;
             } else if (!strcmp(longOptions[optionIndex].name, "help")) {
                 usage();
                 return 0;
+            } else if (!strcmp(longOptions[optionIndex].name, "priority")) {
+                priorityType = String16(String8(optarg));
+                if (!ConvertPriorityTypeToBitmask(priorityType, priorityFlags)) {
+                    fprintf(stderr, "\n");
+                    usage();
+                    return -1;
+                }
             }
             break;
 
         case 't':
             {
-                char *endptr;
-                timeoutArg = strtol(optarg, &endptr, 10);
-                if (*endptr != '\0' || timeoutArg <= 0) {
-                    fprintf(stderr, "Error: invalid timeout number: '%s'\n", optarg);
+                char* endptr;
+                timeoutArgMs = strtol(optarg, &endptr, 10);
+                timeoutArgMs = timeoutArgMs * 1000;
+                if (*endptr != '\0' || timeoutArgMs <= 0) {
+                    fprintf(stderr, "Error: invalid timeout(seconds) number: '%s'\n", optarg);
+                    return -1;
+                }
+            }
+            break;
+
+        case 'T':
+            {
+                char* endptr;
+                timeoutArgMs = strtol(optarg, &endptr, 10);
+                if (*endptr != '\0' || timeoutArgMs <= 0) {
+                    fprintf(stderr, "Error: invalid timeout(milliseconds) number: '%s'\n", optarg);
                     return -1;
                 }
             }
@@ -150,14 +213,11 @@
     }
 
     if (services.empty() || showListOnly) {
-        // gets all services
-        services = sm_->listServices();
-        services.sort(sort_func);
-        args.add(String16("-a"));
+        services = listServices(priorityFlags, asProto);
+        setServiceArgs(args, asProto, priorityFlags);
     }
 
     const size_t N = services.size();
-
     if (N > 1) {
         // first print a list of the current services
         aout << "Currently running services:" << endl;
@@ -177,125 +237,214 @@
     }
 
     for (size_t i = 0; i < N; i++) {
-        String16 service_name = std::move(services[i]);
-        if (IsSkipped(skippedServices, service_name)) continue;
+        const String16& serviceName = services[i];
+        if (IsSkipped(skippedServices, serviceName)) continue;
 
-        sp<IBinder> service = sm_->checkService(service_name);
-        if (service != nullptr) {
-            int sfd[2];
-
-            if (pipe(sfd) != 0) {
-                aerr << "Failed to create pipe to dump service info for " << service_name
-                     << ": " << strerror(errno) << endl;
-                continue;
+        if (startDumpThread(serviceName, args) == OK) {
+            bool addSeparator = (N > 1);
+            if (addSeparator) {
+                writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);
             }
+            std::chrono::duration<double> elapsedDuration;
+            size_t bytesWritten = 0;
+            status_t status =
+                writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),
+                          asProto, elapsedDuration, bytesWritten);
 
-            unique_fd local_end(sfd[0]);
-            unique_fd remote_end(sfd[1]);
-            sfd[0] = sfd[1] = -1;
-
-            if (N > 1) {
-                aout << "------------------------------------------------------------"
-                        "-------------------" << endl;
-                aout << "DUMP OF SERVICE " << service_name << ":" << endl;
-            }
-
-            // dump blocks until completion, so spawn a thread..
-            std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable {
-                int err = service->dump(remote_end.get(), args);
-
-                // It'd be nice to be able to close the remote end of the socketpair before the dump
-                // call returns, to terminate our reads if the other end closes their copy of the
-                // file descriptor, but then hangs for some reason. There doesn't seem to be a good
-                // way to do this, though.
-                remote_end.reset();
-
-                if (err != 0) {
-                    aerr << "Error dumping service info: (" << strerror(err) << ") " << service_name
-                         << endl;
-                }
-            });
-
-            auto timeout = std::chrono::seconds(timeoutArg);
-            auto start = std::chrono::steady_clock::now();
-            auto end = start + timeout;
-
-            struct pollfd pfd = {
-                .fd = local_end.get(),
-                .events = POLLIN
-            };
-
-            bool timed_out = false;
-            bool error = false;
-            while (true) {
-                // Wrap this in a lambda so that TEMP_FAILURE_RETRY recalculates the timeout.
-                auto time_left_ms = [end]() {
-                    auto now = std::chrono::steady_clock::now();
-                    auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
-                    return std::max(diff.count(), 0ll);
-                };
-
-                int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
-                if (rc < 0) {
-                    aerr << "Error in poll while dumping service " << service_name << " : "
-                         << strerror(errno) << endl;
-                    error = true;
-                    break;
-                } else if (rc == 0) {
-                    timed_out = true;
-                    break;
-                }
-
-                char buf[4096];
-                rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf)));
-                if (rc < 0) {
-                    aerr << "Failed to read while dumping service " << service_name << ": "
-                         << strerror(errno) << endl;
-                    error = true;
-                    break;
-                } else if (rc == 0) {
-                    // EOF.
-                    break;
-                }
-
-                if (!WriteFully(STDOUT_FILENO, buf, rc)) {
-                    aerr << "Failed to write while dumping service " << service_name << ": "
-                         << strerror(errno) << endl;
-                    error = true;
-                    break;
-                }
-            }
-
-            if (timed_out) {
+            if (status == TIMED_OUT) {
                 aout << endl
-                     << "*** SERVICE '" << service_name << "' DUMP TIMEOUT (" << timeoutArg
-                     << "s) EXPIRED ***" << endl
+                     << "*** SERVICE '" << serviceName << "' DUMP TIMEOUT (" << timeoutArgMs
+                     << "ms) EXPIRED ***" << endl
                      << endl;
             }
 
-            if (timed_out || error) {
-                dump_thread.detach();
-            } else {
-                dump_thread.join();
+            if (addSeparator) {
+                writeDumpFooter(STDOUT_FILENO, serviceName, elapsedDuration);
             }
-
-            if (N > 1) {
-              std::chrono::duration<double> elapsed_seconds =
-                  std::chrono::steady_clock::now() - start;
-              aout << StringPrintf("--------- %.3fs ", elapsed_seconds.count()).c_str()
-                   << "was the duration of dumpsys " << service_name;
-
-              using std::chrono::system_clock;
-              const auto finish = system_clock::to_time_t(system_clock::now());
-              std::tm finish_tm;
-              localtime_r(&finish, &finish_tm);
-              aout << ", ending at: " << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S")
-                   << endl;
-            }
-        } else {
-            aerr << "Can't find service: " << service_name << endl;
+            bool dumpComplete = (status == OK);
+            stopDumpThread(dumpComplete);
         }
     }
 
     return 0;
 }
+
+Vector<String16> Dumpsys::listServices(int priorityFilterFlags, bool filterByProto) const {
+    Vector<String16> services = sm_->listServices(priorityFilterFlags);
+    services.sort(sort_func);
+    if (filterByProto) {
+        Vector<String16> protoServices = sm_->listServices(IServiceManager::DUMP_FLAG_PROTO);
+        protoServices.sort(sort_func);
+        Vector<String16> intersection;
+        std::set_intersection(services.begin(), services.end(), protoServices.begin(),
+                              protoServices.end(), std::back_inserter(intersection));
+        services = std::move(intersection);
+    }
+    return services;
+}
+
+void Dumpsys::setServiceArgs(Vector<String16>& args, bool asProto, int priorityFlags) {
+    // Add proto flag if dumping service as proto.
+    if (asProto) {
+        args.insertAt(String16(PriorityDumper::PROTO_ARG), 0);
+    }
+
+    // Add -a (dump all) flag if dumping all services, dumping normal services or
+    // services not explicitly registered to a priority bucket (default services).
+    if ((priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL) ||
+        (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL) ||
+        (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT)) {
+        args.insertAt(String16("-a"), 0);
+    }
+
+    // Add priority flags when dumping services registered to a specific priority bucket.
+    if ((priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL) ||
+        (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_HIGH) ||
+        (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL)) {
+        String16 priorityType = ConvertBitmaskToPriorityType(priorityFlags);
+        args.insertAt(String16(PriorityDumper::PRIORITY_ARG), 0);
+        args.insertAt(priorityType, 1);
+    }
+}
+
+status_t Dumpsys::startDumpThread(const String16& serviceName, const Vector<String16>& args) {
+    sp<IBinder> service = sm_->checkService(serviceName);
+    if (service == nullptr) {
+        aerr << "Can't find service: " << serviceName << endl;
+        return NAME_NOT_FOUND;
+    }
+
+    int sfd[2];
+    if (pipe(sfd) != 0) {
+        aerr << "Failed to create pipe to dump service info for " << serviceName << ": "
+             << strerror(errno) << endl;
+        return -errno;
+    }
+
+    redirectFd_ = unique_fd(sfd[0]);
+    unique_fd remote_end(sfd[1]);
+    sfd[0] = sfd[1] = -1;
+
+    // dump blocks until completion, so spawn a thread..
+    activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {
+        int err = service->dump(remote_end.get(), args);
+
+        // It'd be nice to be able to close the remote end of the socketpair before the dump
+        // call returns, to terminate our reads if the other end closes their copy of the
+        // file descriptor, but then hangs for some reason. There doesn't seem to be a good
+        // way to do this, though.
+        remote_end.reset();
+
+        if (err != 0) {
+            aerr << "Error dumping service info: (" << strerror(err) << ") "
+                 << serviceName << endl;
+        }
+    });
+    return OK;
+}
+
+void Dumpsys::stopDumpThread(bool dumpComplete) {
+    if (dumpComplete) {
+        activeThread_.join();
+    } else {
+        activeThread_.detach();
+    }
+    /* close read end of the dump output redirection pipe */
+    redirectFd_.reset();
+}
+
+void Dumpsys::writeDumpHeader(int fd, const String16& serviceName, int priorityFlags) const {
+    std::string msg(
+        "----------------------------------------"
+        "---------------------------------------\n");
+    if (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL ||
+        priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL ||
+        priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
+        StringAppendF(&msg, "DUMP OF SERVICE %s:\n", String8(serviceName).c_str());
+    } else {
+        String16 priorityType = ConvertBitmaskToPriorityType(priorityFlags);
+        StringAppendF(&msg, "DUMP OF SERVICE %s %s:\n", String8(priorityType).c_str(),
+                      String8(serviceName).c_str());
+    }
+    WriteStringToFd(msg, fd);
+}
+
+status_t Dumpsys::writeDump(int fd, const String16& serviceName, std::chrono::milliseconds timeout,
+                            bool asProto, std::chrono::duration<double>& elapsedDuration,
+                            size_t& bytesWritten) const {
+    status_t status = OK;
+    size_t totalBytes = 0;
+    auto start = std::chrono::steady_clock::now();
+    auto end = start + timeout;
+
+    int serviceDumpFd = redirectFd_.get();
+    if (serviceDumpFd == -1) {
+        return INVALID_OPERATION;
+    }
+
+    struct pollfd pfd = {.fd = serviceDumpFd, .events = POLLIN};
+
+    while (true) {
+        // Wrap this in a lambda so that TEMP_FAILURE_RETRY recalculates the timeout.
+        auto time_left_ms = [end]() {
+            auto now = std::chrono::steady_clock::now();
+            auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - now);
+            return std::max(diff.count(), 0ll);
+        };
+
+        int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
+        if (rc < 0) {
+            aerr << "Error in poll while dumping service " << serviceName << " : "
+                 << strerror(errno) << endl;
+            status = -errno;
+            break;
+        } else if (rc == 0) {
+            status = TIMED_OUT;
+            break;
+        }
+
+        char buf[4096];
+        rc = TEMP_FAILURE_RETRY(read(redirectFd_.get(), buf, sizeof(buf)));
+        if (rc < 0) {
+            aerr << "Failed to read while dumping service " << serviceName << ": "
+                 << strerror(errno) << endl;
+            status = -errno;
+            break;
+        } else if (rc == 0) {
+            // EOF.
+            break;
+        }
+
+        if (!WriteFully(fd, buf, rc)) {
+            aerr << "Failed to write while dumping service " << serviceName << ": "
+                 << strerror(errno) << endl;
+            status = -errno;
+            break;
+        }
+        totalBytes += rc;
+    }
+
+    if ((status == TIMED_OUT) && (!asProto)) {
+        std::string msg = StringPrintf("\n*** SERVICE '%s' DUMP TIMEOUT (%llums) EXPIRED ***\n\n",
+                                       String8(serviceName).string(), timeout.count());
+        WriteStringToFd(msg, fd);
+    }
+
+    elapsedDuration = std::chrono::steady_clock::now() - start;
+    bytesWritten = totalBytes;
+    return status;
+}
+
+void Dumpsys::writeDumpFooter(int fd, const String16& serviceName,
+                              const std::chrono::duration<double>& elapsedDuration) const {
+    using std::chrono::system_clock;
+    const auto finish = system_clock::to_time_t(system_clock::now());
+    std::tm finish_tm;
+    localtime_r(&finish, &finish_tm);
+    std::stringstream oss;
+    oss << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S");
+    std::string msg =
+        StringPrintf("--------- %.3fs was the duration of dumpsys %s, ending at: %s\n",
+                     elapsedDuration.count(), String8(serviceName).string(), oss.str().c_str());
+    WriteStringToFd(msg, fd);
+}
diff --git a/cmds/dumpsys/dumpsys.h b/cmds/dumpsys/dumpsys.h
index 2534dde..84f3b02 100644
--- a/cmds/dumpsys/dumpsys.h
+++ b/cmds/dumpsys/dumpsys.h
@@ -17,6 +17,9 @@
 #ifndef FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
 #define FRAMEWORK_NATIVE_CMD_DUMPSYS_H_
 
+#include <thread>
+
+#include <android-base/unique_fd.h>
 #include <binder/IServiceManager.h>
 
 namespace android {
@@ -25,10 +28,97 @@
   public:
     Dumpsys(android::IServiceManager* sm) : sm_(sm) {
     }
+    /**
+     * Main entry point into dumpsys.
+     */
     int main(int argc, char* const argv[]);
 
+    /**
+     * Returns a list of services.
+     * @param priorityFlags filter services by specified priorities
+     * @param supportsProto filter services that support proto dumps
+     * @return list of services
+     */
+    Vector<String16> listServices(int priorityFlags, bool supportsProto) const;
+
+    /**
+     * Modifies @{code args} to add additional arguments  to indicate if the service
+     * must dump as proto or dump to a certian priority bucket.
+     * @param args initial list of arguments to pass to service dump method.
+     * @param asProto dump service as proto by passing an additional --proto arg
+     * @param priorityFlags indicates priority of dump by passing additional priority args
+     * to the service
+     */
+    static void setServiceArgs(Vector<String16>& args, bool asProto, int priorityFlags);
+
+    /**
+     * Starts a thread to connect to a service and get its dump output. The thread redirects
+     * the output to a pipe. Thread must be stopped by a subsequent callto {@code
+     * stopDumpThread}.
+     * @param serviceName
+     * @param args list of arguments to pass to service dump method.
+     * @return {@code OK} thread is started successfully.
+     *         {@code NAME_NOT_FOUND} service could not be found.
+     *         {@code != OK} error
+     */
+    status_t startDumpThread(const String16& serviceName, const Vector<String16>& args);
+
+    /**
+     * Writes a section header to a file descriptor.
+     * @param fd file descriptor to write data
+     * @param serviceName
+     * @param priorityFlags dump priority specified
+     */
+    void writeDumpHeader(int fd, const String16& serviceName, int priorityFlags) const;
+
+    /**
+     * Redirects service dump to a file descriptor. This requires
+     * {@code startDumpThread} to be called successfully otherwise the function will
+     * return {@code INVALID_OPERATION}.
+     * @param fd file descriptor to write data
+     * @param serviceName
+     * @param timeout timeout to terminate the dump if not completed
+     * @param asProto used to supresses additional output to the fd such as timeout
+     * error messages
+     * @param elapsedDuration returns elapsed time in seconds
+     * @param bytesWritten returns number of bytes written
+     * @return {@code OK} if successful
+     *         {@code TIMED_OUT} dump timed out
+     *         {@code INVALID_OPERATION} invalid state
+     *         {@code != OK} error
+     */
+    status_t writeDump(int fd, const String16& serviceName, std::chrono::milliseconds timeout,
+                       bool asProto, std::chrono::duration<double>& elapsedDuration,
+                       size_t& bytesWritten) const;
+
+    /**
+     * Writes a section footer to a file descriptor with duration info.
+     * @param fd file descriptor to write data
+     * @param serviceName
+     * @param elapsedDuration duration of dump
+     */
+    void writeDumpFooter(int fd, const String16& serviceName,
+                         const std::chrono::duration<double>& elapsedDuration) const;
+
+    /**
+     * Terminates dump thread.
+     * @param dumpComplete If {@code true}, indicates the dump was successfully completed and
+     * tries to join the thread. Otherwise thread is detached.
+     */
+    void stopDumpThread(bool dumpComplete);
+
+    /**
+     * Returns file descriptor of the pipe used to dump service data. This assumes
+     * {@code startDumpThread} was called successfully.
+     */
+    int getDumpFd() const {
+        return redirectFd_.get();
+    }
+
   private:
     android::IServiceManager* sm_;
+    std::thread activeThread_;
+    mutable android::base::unique_fd redirectFd_;
 };
 }
 
diff --git a/cmds/dumpsys/tests/Android.bp b/cmds/dumpsys/tests/Android.bp
index 127e0f3..e182b9d 100644
--- a/cmds/dumpsys/tests/Android.bp
+++ b/cmds/dumpsys/tests/Android.bp
@@ -4,6 +4,7 @@
     test_suites: ["device-tests"],
 
     srcs: ["dumpsys_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
 
     shared_libs: [
         "libbase",
@@ -14,6 +15,7 @@
     static_libs: [
         "libdumpsys",
         "libgmock",
+        "libserviceutils",
     ],
 
     clang: true,
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index cfd53e5..5029352 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -22,6 +22,7 @@
 #include <gtest/gtest.h>
 
 #include <android-base/file.h>
+#include <serviceutils/PriorityDumper.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
@@ -50,8 +51,8 @@
   public:
     MOCK_CONST_METHOD1(getService, sp<IBinder>(const String16&));
     MOCK_CONST_METHOD1(checkService, sp<IBinder>(const String16&));
-    MOCK_METHOD3(addService, status_t(const String16&, const sp<IBinder>&, bool));
-    MOCK_METHOD0(listServices, Vector<String16>());
+    MOCK_METHOD4(addService, status_t(const String16&, const sp<IBinder>&, bool, int));
+    MOCK_METHOD1(listServices, Vector<String16>(int));
 
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
@@ -96,7 +97,7 @@
     }
     int i = 0;
     std::ostringstream actual_stream, expected_stream;
-    for (String16 actual : arg) {
+    for (const String16& actual : arg) {
         std::string actual_str = String8(actual).c_str();
         std::string expected_str = expected[i];
         actual_stream << "'" << actual_str << "' ";
@@ -131,7 +132,16 @@
         for (auto& service : services) {
             services16.add(String16(service.c_str()));
         }
-        EXPECT_CALL(sm_, listServices()).WillRepeatedly(Return(services16));
+        EXPECT_CALL(sm_, listServices(IServiceManager::DUMP_FLAG_PRIORITY_ALL))
+            .WillRepeatedly(Return(services16));
+    }
+
+    void ExpectListServicesWithPriority(std::vector<std::string> services, int dumpFlags) {
+        Vector<String16> services16;
+        for (auto& service : services) {
+            services16.add(String16(service.c_str()));
+        }
+        EXPECT_CALL(sm_, listServices(dumpFlags)).WillRepeatedly(Return(services16));
     }
 
     sp<BinderMock> ExpectCheckService(const char* name, bool running = true) {
@@ -178,8 +188,27 @@
         EXPECT_THAT(status, Eq(0));
     }
 
+    void CallSingleService(const String16& serviceName, Vector<String16>& args, int priorityFlags,
+                           bool supportsProto, std::chrono::duration<double>& elapsedDuration,
+                           size_t& bytesWritten) {
+        CaptureStdout();
+        CaptureStderr();
+        dump_.setServiceArgs(args, supportsProto, priorityFlags);
+        status_t status = dump_.startDumpThread(serviceName, args);
+        EXPECT_THAT(status, Eq(0));
+        status = dump_.writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(500), false,
+                                 elapsedDuration, bytesWritten);
+        EXPECT_THAT(status, Eq(0));
+        dump_.stopDumpThread(/* dumpCompleted = */ true);
+        stdout_ = GetCapturedStdout();
+        stderr_ = GetCapturedStderr();
+    }
+
     void AssertRunningServices(const std::vector<std::string>& services) {
-        std::string expected("Currently running services:\n");
+        std::string expected;
+        if (services.size() > 1) {
+            expected.append("Currently running services:\n");
+        }
         for (const std::string& service : services) {
             expected.append("  ").append(service).append("\n");
         }
@@ -196,6 +225,15 @@
 
     void AssertDumped(const std::string& service, const std::string& dump) {
         EXPECT_THAT(stdout_, HasSubstr("DUMP OF SERVICE " + service + ":\n" + dump));
+        EXPECT_THAT(stdout_, HasSubstr("was the duration of dumpsys " + service + ", ending at: "));
+    }
+
+    void AssertDumpedWithPriority(const std::string& service, const std::string& dump,
+                                  const char16_t* priorityType) {
+        std::string priority = String8(priorityType).c_str();
+        EXPECT_THAT(stdout_,
+                    HasSubstr("DUMP OF SERVICE " + priority + " " + service + ":\n" + dump));
+        EXPECT_THAT(stdout_, HasSubstr("was the duration of dumpsys " + service + ", ending at: "));
     }
 
     void AssertNotDumped(const std::string& dump) {
@@ -236,6 +274,39 @@
     AssertNotDumped({"Valet"});
 }
 
+// Tests 'dumpsys -l --priority HIGH'
+TEST_F(DumpsysTest, ListAllServicesWithPriority) {
+    ExpectListServicesWithPriority({"Locksmith", "Valet"}, IServiceManager::DUMP_FLAG_PRIORITY_HIGH);
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+
+    CallMain({"-l", "--priority", "HIGH"});
+
+    AssertRunningServices({"Locksmith", "Valet"});
+}
+
+// Tests 'dumpsys -l --priority HIGH' with and empty list
+TEST_F(DumpsysTest, ListEmptyServicesWithPriority) {
+    ExpectListServicesWithPriority({}, IServiceManager::DUMP_FLAG_PRIORITY_HIGH);
+
+    CallMain({"-l", "--priority", "HIGH"});
+
+    AssertRunningServices({});
+}
+
+// Tests 'dumpsys -l --proto'
+TEST_F(DumpsysTest, ListAllServicesWithProto) {
+    ExpectListServicesWithPriority({"Locksmith", "Valet", "Car"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_ALL);
+    ExpectListServicesWithPriority({"Valet", "Car"}, IServiceManager::DUMP_FLAG_PROTO);
+    ExpectCheckService("Car");
+    ExpectCheckService("Valet");
+
+    CallMain({"-l", "--proto"});
+
+    AssertRunningServices({"Car", "Valet"});
+}
+
 // Tests 'dumpsys service_name' on a service is running
 TEST_F(DumpsysTest, DumpRunningService) {
     ExpectDump("Valet", "Here's your car");
@@ -246,12 +317,25 @@
 }
 
 // Tests 'dumpsys -t 1 service_name' on a service that times out after 2s
-TEST_F(DumpsysTest, DumpRunningServiceTimeout) {
+TEST_F(DumpsysTest, DumpRunningServiceTimeoutInSec) {
     sp<BinderMock> binder_mock = ExpectDumpAndHang("Valet", 2, "Here's your car");
 
     CallMain({"-t", "1", "Valet"});
 
-    AssertOutputContains("SERVICE 'Valet' DUMP TIMEOUT (1s) EXPIRED");
+    AssertOutputContains("SERVICE 'Valet' DUMP TIMEOUT (1000ms) EXPIRED");
+    AssertNotDumped("Here's your car");
+
+    // TODO(b/65056227): BinderMock is not destructed because thread is detached on dumpsys.cpp
+    Mock::AllowLeak(binder_mock.get());
+}
+
+// Tests 'dumpsys -T 500 service_name' on a service that times out after 2s
+TEST_F(DumpsysTest, DumpRunningServiceTimeoutInMs) {
+    sp<BinderMock> binder_mock = ExpectDumpAndHang("Valet", 2, "Here's your car");
+
+    CallMain({"-T", "500", "Valet"});
+
+    AssertOutputContains("SERVICE 'Valet' DUMP TIMEOUT (500ms) EXPIRED");
     AssertNotDumped("Here's your car");
 
     // TODO(b/65056227): BinderMock is not destructed because thread is detached on dumpsys.cpp
@@ -267,6 +351,65 @@
     AssertOutput("I DO!");
 }
 
+// Tests dumpsys passes the -a flag when called on all services
+TEST_F(DumpsysTest, PassAllFlagsToServices) {
+    ExpectListServices({"Locksmith", "Valet"});
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+    ExpectDumpWithArgs("Locksmith", {"-a"}, "dumped1");
+    ExpectDumpWithArgs("Valet", {"-a"}, "dumped2");
+
+    CallMain({"-T", "500"});
+
+    AssertDumped("Locksmith", "dumped1");
+    AssertDumped("Valet", "dumped2");
+}
+
+// Tests dumpsys passes the -a flag when called on NORMAL priority services
+TEST_F(DumpsysTest, PassAllFlagsToNormalServices) {
+    ExpectListServicesWithPriority({"Locksmith", "Valet"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_NORMAL);
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+    ExpectDumpWithArgs("Locksmith", {"-a", "--dump-priority", "NORMAL"}, "dump1");
+    ExpectDumpWithArgs("Valet", {"-a", "--dump-priority", "NORMAL"}, "dump2");
+
+    CallMain({"--priority", "NORMAL"});
+
+    AssertDumped("Locksmith", "dump1");
+    AssertDumped("Valet", "dump2");
+}
+
+// Tests dumpsys passes only priority flags when called on CRITICAL priority services
+TEST_F(DumpsysTest, PassPriorityFlagsToCriticalServices) {
+    ExpectListServicesWithPriority({"Locksmith", "Valet"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+    ExpectDumpWithArgs("Locksmith", {"--dump-priority", "CRITICAL"}, "dump1");
+    ExpectDumpWithArgs("Valet", {"--dump-priority", "CRITICAL"}, "dump2");
+
+    CallMain({"--priority", "CRITICAL"});
+
+    AssertDumpedWithPriority("Locksmith", "dump1", PriorityDumper::PRIORITY_ARG_CRITICAL);
+    AssertDumpedWithPriority("Valet", "dump2", PriorityDumper::PRIORITY_ARG_CRITICAL);
+}
+
+// Tests dumpsys passes only priority flags when called on HIGH priority services
+TEST_F(DumpsysTest, PassPriorityFlagsToHighServices) {
+    ExpectListServicesWithPriority({"Locksmith", "Valet"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_HIGH);
+    ExpectCheckService("Locksmith");
+    ExpectCheckService("Valet");
+    ExpectDumpWithArgs("Locksmith", {"--dump-priority", "HIGH"}, "dump1");
+    ExpectDumpWithArgs("Valet", {"--dump-priority", "HIGH"}, "dump2");
+
+    CallMain({"--priority", "HIGH"});
+
+    AssertDumpedWithPriority("Locksmith", "dump1", PriorityDumper::PRIORITY_ARG_HIGH);
+    AssertDumpedWithPriority("Valet", "dump2", PriorityDumper::PRIORITY_ARG_HIGH);
+}
+
 // Tests 'dumpsys' with no arguments
 TEST_F(DumpsysTest, DumpMultipleServices) {
     ExpectListServices({"running1", "stopped2", "running3"});
@@ -300,3 +443,124 @@
     AssertNotDumped("dump3");
     AssertNotDumped("dump5");
 }
+
+// Tests 'dumpsys --skip skipped3 skipped5 --priority CRITICAL', which should skip these services
+TEST_F(DumpsysTest, DumpWithSkipAndPriority) {
+    ExpectListServicesWithPriority({"running1", "stopped2", "skipped3", "running4", "skipped5"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
+    ExpectDump("running1", "dump1");
+    ExpectCheckService("stopped2", false);
+    ExpectDump("skipped3", "dump3");
+    ExpectDump("running4", "dump4");
+    ExpectDump("skipped5", "dump5");
+
+    CallMain({"--priority", "CRITICAL", "--skip", "skipped3", "skipped5"});
+
+    AssertRunningServices({"running1", "running4", "skipped3 (skipped)", "skipped5 (skipped)"});
+    AssertDumpedWithPriority("running1", "dump1", PriorityDumper::PRIORITY_ARG_CRITICAL);
+    AssertDumpedWithPriority("running4", "dump4", PriorityDumper::PRIORITY_ARG_CRITICAL);
+    AssertStopped("stopped2");
+    AssertNotDumped("dump3");
+    AssertNotDumped("dump5");
+}
+
+// Tests 'dumpsys --priority CRITICAL'
+TEST_F(DumpsysTest, DumpWithPriorityCritical) {
+    ExpectListServicesWithPriority({"runningcritical1", "runningcritical2"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL);
+    ExpectDump("runningcritical1", "dump1");
+    ExpectDump("runningcritical2", "dump2");
+
+    CallMain({"--priority", "CRITICAL"});
+
+    AssertRunningServices({"runningcritical1", "runningcritical2"});
+    AssertDumpedWithPriority("runningcritical1", "dump1", PriorityDumper::PRIORITY_ARG_CRITICAL);
+    AssertDumpedWithPriority("runningcritical2", "dump2", PriorityDumper::PRIORITY_ARG_CRITICAL);
+}
+
+// Tests 'dumpsys --priority HIGH'
+TEST_F(DumpsysTest, DumpWithPriorityHigh) {
+    ExpectListServicesWithPriority({"runninghigh1", "runninghigh2"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_HIGH);
+    ExpectDump("runninghigh1", "dump1");
+    ExpectDump("runninghigh2", "dump2");
+
+    CallMain({"--priority", "HIGH"});
+
+    AssertRunningServices({"runninghigh1", "runninghigh2"});
+    AssertDumpedWithPriority("runninghigh1", "dump1", PriorityDumper::PRIORITY_ARG_HIGH);
+    AssertDumpedWithPriority("runninghigh2", "dump2", PriorityDumper::PRIORITY_ARG_HIGH);
+}
+
+// Tests 'dumpsys --priority NORMAL'
+TEST_F(DumpsysTest, DumpWithPriorityNormal) {
+    ExpectListServicesWithPriority({"runningnormal1", "runningnormal2"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_NORMAL);
+    ExpectDump("runningnormal1", "dump1");
+    ExpectDump("runningnormal2", "dump2");
+
+    CallMain({"--priority", "NORMAL"});
+
+    AssertRunningServices({"runningnormal1", "runningnormal2"});
+    AssertDumped("runningnormal1", "dump1");
+    AssertDumped("runningnormal2", "dump2");
+}
+
+// Tests 'dumpsys --proto'
+TEST_F(DumpsysTest, DumpWithProto) {
+    ExpectListServicesWithPriority({"run8", "run1", "run2", "run5"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_ALL);
+    ExpectListServicesWithPriority({"run3", "run2", "run4", "run8"},
+                                   IServiceManager::DUMP_FLAG_PROTO);
+    ExpectDump("run2", "dump1");
+    ExpectDump("run8", "dump2");
+
+    CallMain({"--proto"});
+
+    AssertRunningServices({"run2", "run8"});
+    AssertDumped("run2", "dump1");
+    AssertDumped("run8", "dump2");
+}
+
+// Tests 'dumpsys --priority HIGH --proto'
+TEST_F(DumpsysTest, DumpWithPriorityHighAndProto) {
+    ExpectListServicesWithPriority({"runninghigh1", "runninghigh2"},
+                                   IServiceManager::DUMP_FLAG_PRIORITY_HIGH);
+    ExpectListServicesWithPriority({"runninghigh1", "runninghigh2", "runninghigh3"},
+                                   IServiceManager::DUMP_FLAG_PROTO);
+
+    ExpectDump("runninghigh1", "dump1");
+    ExpectDump("runninghigh2", "dump2");
+
+    CallMain({"--priority", "HIGH", "--proto"});
+
+    AssertRunningServices({"runninghigh1", "runninghigh2"});
+    AssertDumpedWithPriority("runninghigh1", "dump1", PriorityDumper::PRIORITY_ARG_HIGH);
+    AssertDumpedWithPriority("runninghigh2", "dump2", PriorityDumper::PRIORITY_ARG_HIGH);
+}
+
+TEST_F(DumpsysTest, GetBytesWritten) {
+    const char* serviceName = "service2";
+    const char* dumpContents = "dump1";
+    ExpectDump(serviceName, dumpContents);
+
+    String16 service(serviceName);
+    Vector<String16> args;
+    std::chrono::duration<double> elapsedDuration;
+    size_t bytesWritten;
+
+    CallSingleService(service, args, IServiceManager::DUMP_FLAG_PRIORITY_ALL,
+                      /* as_proto = */ false, elapsedDuration, bytesWritten);
+
+    AssertOutput(dumpContents);
+    EXPECT_THAT(bytesWritten, Eq(strlen(dumpContents)));
+}
+
+TEST_F(DumpsysTest, WriteDumpWithoutThreadStart) {
+    std::chrono::duration<double> elapsedDuration;
+    size_t bytesWritten;
+    status_t status =
+        dump_.writeDump(STDOUT_FILENO, String16("service"), std::chrono::milliseconds(500),
+                        /* as_proto = */ false, elapsedDuration, bytesWritten);
+    EXPECT_THAT(status, Eq(INVALID_OPERATION));
+}
\ No newline at end of file
diff --git a/cmds/flatland/Android.mk b/cmds/flatland/Android.mk
index c295167..7aa111c 100644
--- a/cmds/flatland/Android.mk
+++ b/cmds/flatland/Android.mk
@@ -8,6 +8,8 @@
     Renderers.cpp   \
     Main.cpp        \
 
+LOCAL_CFLAGS := -Wall -Werror
+
 LOCAL_MODULE:= flatland
 
 LOCAL_MODULE_TAGS := tests
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index dfc3e58..d5b3372 100644
--- a/cmds/flatland/GLHelper.cpp
+++ b/cmds/flatland/GLHelper.cpp
@@ -269,24 +269,10 @@
         return false;
     }
 
-    SurfaceComposerClient::openGlobalTransaction();
-    err = sc->setLayer(0x7FFFFFFF);
-    if (err != NO_ERROR) {
-        fprintf(stderr, "SurfaceComposer::setLayer error: %#x\n", err);
-        return false;
-    }
-    err = sc->setMatrix(scale, 0.0f, 0.0f, scale);
-    if (err != NO_ERROR) {
-        fprintf(stderr, "SurfaceComposer::setMatrix error: %#x\n", err);
-        return false;
-    }
-
-    err = sc->show();
-    if (err != NO_ERROR) {
-        fprintf(stderr, "SurfaceComposer::show error: %#x\n", err);
-        return false;
-    }
-    SurfaceComposerClient::closeGlobalTransaction();
+    SurfaceComposerClient::Transaction{}.setLayer(sc, 0x7FFFFFFF)
+            .setMatrix(sc, scale, 0.0f, 0.0f, scale)
+            .show(sc)
+            .apply();
 
     sp<ANativeWindow> anw = sc->getSurface();
     EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL);
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index ec1e543..3d7cac0 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -284,7 +284,6 @@
 public:
 
     Layer() :
-        mFirstFrame(true),
         mGLHelper(NULL),
         mSurface(EGL_NO_SURFACE) {
     }
@@ -358,8 +357,6 @@
     }
 
 private:
-    bool mFirstFrame;
-
     LayerDesc mDesc;
 
     GLHelper* mGLHelper;
@@ -389,7 +386,6 @@
         ATRACE_CALL();
 
         bool result;
-        EGLint resulte;
 
         float scaleFactor = float(mDesc.runHeights[mInstance]) /
             float(mDesc.height);
@@ -465,7 +461,6 @@
         ATRACE_CALL();
 
         bool result;
-        status_t err;
 
         resetColorGenerator();
 
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index 33db6db..94c3102 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -4,6 +4,11 @@
     cflags: [
         "-Wall",
         "-Werror",
+        "-Wextra",
+
+        "-Wunreachable-code",
+        "-Wunreachable-code-break",
+        "-Wunreachable-code-return",
     ],
     srcs: [
         "CacheItem.cpp",
@@ -12,11 +17,15 @@
         "dexopt.cpp",
         "globals.cpp",
         "utils.cpp",
-        "binder/android/os/IInstalld.aidl",
+        ":installd_aidl",
+    ],
+    header_libs: [
+        "dex2oat_headers",
     ],
     shared_libs: [
         "libbase",
         "libbinder",
+        "libcrypto",
         "libcutils",
         "liblog",
         "liblogwrap",
@@ -25,6 +34,17 @@
     ],
 
     clang: true,
+
+    tidy: true,
+    tidy_checks: [
+        "-*",
+        "clang-analyzer-security*",
+        "cert-*",
+        "-cert-err58-cpp",
+    ],
+    tidy_flags: [
+        "-warnings-as-errors=clang-analyzer-security*,cert-*"
+    ],
 }
 
 //
@@ -72,4 +92,35 @@
     ],
 }
 
+filegroup {
+    name: "installd_aidl",
+    srcs: [
+        "binder/android/os/IInstalld.aidl",
+    ],
+}
+
+//
+// Static library for otapreopt used in testing
+//
+cc_library_static {
+    name: "libotapreoptparameters",
+    cflags: [
+        "-Wall",
+        "-Werror"
+    ],
+    clang: true,
+
+    srcs: [
+        "otapreopt_parameters.cpp"],
+
+    export_include_dirs: ["."],
+
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libutils",
+    ],
+}
+
 subdirs = ["tests"]
diff --git a/cmds/installd/Android.mk b/cmds/installd/Android.mk
index 1d21b3c..a4f95da 100644
--- a/cmds/installd/Android.mk
+++ b/cmds/installd/Android.mk
@@ -23,10 +23,11 @@
 LOCAL_CFLAGS += -DART_BASE_ADDRESS_MIN_DELTA=$(LOCAL_LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA)
 LOCAL_CFLAGS += -DART_BASE_ADDRESS_MAX_DELTA=$(LOCAL_LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA)
 
-LOCAL_SRC_FILES := otapreopt.cpp globals.cpp utils.cpp dexopt.cpp
+LOCAL_SRC_FILES := otapreopt.cpp otapreopt_parameters.cpp globals.cpp utils.cpp dexopt.cpp
 LOCAL_HEADER_LIBRARIES := dex2oat_headers
 LOCAL_SHARED_LIBRARIES := \
     libbase \
+    libcrypto \
     libcutils \
     liblog \
     liblogwrap \
diff --git a/cmds/installd/CacheTracker.cpp b/cmds/installd/CacheTracker.cpp
index 3eb39b9..ea0cd9e 100644
--- a/cmds/installd/CacheTracker.cpp
+++ b/cmds/installd/CacheTracker.cpp
@@ -60,7 +60,7 @@
 
     ATRACE_BEGIN("loadStats tree");
     cacheUsed = 0;
-    for (auto path : mDataPaths) {
+    for (const auto& path : mDataPaths) {
         auto cachePath = read_path_inode(path, "cache", kXattrInodeCache);
         auto codeCachePath = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
         calculate_tree_size(cachePath, &cacheUsed);
@@ -170,7 +170,7 @@
     items.clear();
 
     ATRACE_BEGIN("loadItems");
-    for (auto path : mDataPaths) {
+    for (const auto& path : mDataPaths) {
         loadItemsFrom(read_path_inode(path, "cache", kXattrInodeCache));
         loadItemsFrom(read_path_inode(path, "code_cache", kXattrInodeCodeCache));
     }
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 8a79ee1..860a68b 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -18,17 +18,21 @@
 
 #define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
 
+#include <algorithm>
 #include <errno.h>
-#include <inttypes.h>
 #include <fstream>
 #include <fts.h>
+#include <functional>
+#include <inttypes.h>
 #include <regex>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/capability.h>
 #include <sys/file.h>
-#include <sys/resource.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/quota.h>
+#include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/statvfs.h>
 #include <sys/types.h>
@@ -37,9 +41,11 @@
 #include <unistd.h>
 
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <cutils/ashmem.h>
 #include <cutils/fs.h>
 #include <cutils/properties.h>
 #include <cutils/sched_policy.h>
@@ -71,6 +77,7 @@
 
 static constexpr const char* kCpPath = "/system/bin/cp";
 static constexpr const char* kXattrDefault = "user.default";
+static constexpr const char* kPropHasReserved = "vold.has_reserved";
 
 static constexpr const int MIN_RESTRICTED_HOME_SDK_VERSION = 24; // > M
 
@@ -82,6 +89,12 @@
 static constexpr const char* IDMAP_PREFIX = "/data/resource-cache/";
 static constexpr const char* IDMAP_SUFFIX = "@idmap";
 
+// fsverity assumes the page size is always 4096. If not, the feature can not be
+// enabled.
+static constexpr int kVerityPageSize = 4096;
+static constexpr size_t kSha256Size = 32;
+static constexpr const char* kPropApkVerityMode = "ro.apk_verity.mode";
+
 // NOTE: keep in sync with Installer
 static constexpr int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
 static constexpr int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
@@ -100,6 +113,7 @@
 }
 
 static binder::Status exception(uint32_t code, const std::string& msg) {
+    LOG(ERROR) << msg << " (" << code << ")";
     return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
 }
 
@@ -158,6 +172,35 @@
     }
 }
 
+binder::Status checkArgumentPath(const std::string& path) {
+    if (path.empty()) {
+        return exception(binder::Status::EX_ILLEGAL_ARGUMENT, "Missing path");
+    }
+    if (path[0] != '/') {
+        return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+                StringPrintf("Path %s is relative", path.c_str()));
+    }
+    if ((path + '/').find("/../") != std::string::npos) {
+        return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+                StringPrintf("Path %s is shady", path.c_str()));
+    }
+    for (const char& c : path) {
+        if (c == '\0' || c == '\n') {
+            return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+                    StringPrintf("Path %s is malformed", path.c_str()));
+        }
+    }
+    return ok();
+}
+
+binder::Status checkArgumentPath(const std::unique_ptr<std::string>& path) {
+    if (path) {
+        return checkArgumentPath(*path);
+    } else {
+        return ok();
+    }
+}
+
 #define ENFORCE_UID(uid) {                                  \
     binder::Status status = checkUid((uid));                \
     if (!status.isOk()) {                                   \
@@ -180,6 +223,19 @@
     }                                                       \
 }
 
+#define CHECK_ARGUMENT_PATH(path) {                         \
+    binder::Status status = checkArgumentPath((path));      \
+    if (!status.isOk()) {                                   \
+        return status;                                      \
+    }                                                       \
+}
+
+#define ASSERT_PAGE_SIZE_4K() {                             \
+    if (getpagesize() != kVerityPageSize) {                 \
+        return error("FSVerity only supports 4K pages");     \
+    }                                                       \
+}
+
 }  // namespace
 
 status_t InstalldNativeService::start() {
@@ -300,8 +356,11 @@
  * Ensure that we have a hard-limit quota to protect against abusive apps;
  * they should never use more than 90% of blocks or 50% of inodes.
  */
-static int prepare_app_quota(const std::unique_ptr<std::string>& uuid, const std::string& device,
-        uid_t uid) {
+static int prepare_app_quota(const std::unique_ptr<std::string>& uuid ATTRIBUTE_UNUSED,
+        const std::string& device, uid_t uid) {
+    // Skip when reserved blocks are protecting us against abusive apps
+    if (android::base::GetBoolProperty(kPropHasReserved, false)) return 0;
+    // Skip when device no quotas present
     if (device.empty()) return 0;
 
     struct dqblk dq;
@@ -342,6 +401,49 @@
 #endif
 }
 
+static bool prepare_app_profile_dir(const std::string& packageName, int32_t appId, int32_t userId) {
+    if (!property_get_bool("dalvik.vm.usejitprofiles", false)) {
+        return true;
+    }
+
+    int32_t uid = multiuser_get_uid(userId, appId);
+    int shared_app_gid = multiuser_get_shared_gid(userId, appId);
+    if (shared_app_gid == -1) {
+        // TODO(calin): this should no longer be possible but do not continue if we don't get
+        // a valid shared gid.
+        PLOG(WARNING) << "Invalid shared_app_gid for " << packageName;
+        return true;
+    }
+
+    const std::string profile_dir =
+            create_primary_current_profile_package_dir_path(userId, packageName);
+    // read-write-execute only for the app user.
+    if (fs_prepare_dir_strict(profile_dir.c_str(), 0700, uid, uid) != 0) {
+        PLOG(ERROR) << "Failed to prepare " << profile_dir;
+        return false;
+    }
+
+    const std::string ref_profile_path =
+            create_primary_reference_profile_package_dir_path(packageName);
+
+    // Prepare the reference profile directory. Note that we use the non strict version of
+    // fs_prepare_dir. This will fix the permission and the ownership to the correct values.
+    // This is particularly important given that in O there were some fixes for how the
+    // shared_app_gid is computed.
+    //
+    // Note that by the time we get here we know that we are using a correct uid (otherwise
+    // prepare_app_dir and the above fs_prepare_file_strict which check the uid). So we
+    // are sure that the gid being used belongs to the owning app and not someone else.
+    //
+    // dex2oat/profman runs under the shared app gid and it needs to read/write reference profiles.
+    if (fs_prepare_dir(ref_profile_path.c_str(), 0770, AID_SYSTEM, shared_app_gid) != 0) {
+        PLOG(ERROR) << "Failed to prepare " << ref_profile_path;
+        return false;
+    }
+
+    return true;
+}
+
 binder::Status InstalldNativeService::createAppData(const std::unique_ptr<std::string>& uuid,
         const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
         const std::string& seInfo, int32_t targetSdkVersion, int64_t* _aidl_return) {
@@ -417,28 +519,8 @@
             return error("Failed to set hard quota " + path);
         }
 
-        if (property_get_bool("dalvik.vm.usejitprofiles", false)) {
-            const std::string profile_dir =
-                    create_primary_current_profile_package_dir_path(userId, pkgname);
-            // read-write-execute only for the app user.
-            if (fs_prepare_dir_strict(profile_dir.c_str(), 0700, uid, uid) != 0) {
-                return error("Failed to prepare " + profile_dir);
-            }
-            const std::string profile_file = create_current_profile_path(userId, pkgname,
-                    /*is_secondary_dex*/false);
-            // read-write only for the app user.
-            if (fs_prepare_file_strict(profile_file.c_str(), 0600, uid, uid) != 0) {
-                return error("Failed to prepare " + profile_file);
-            }
-            const std::string ref_profile_path =
-                    create_primary_reference_profile_package_dir_path(pkgname);
-            // dex2oat/profman runs under the shared app gid and it needs to read/write reference
-            // profiles.
-            int shared_app_gid = multiuser_get_shared_gid(0, appId);
-            if ((shared_app_gid != -1) && fs_prepare_dir_strict(
-                    ref_profile_path.c_str(), 0700, shared_app_gid, shared_app_gid) != 0) {
-                return error("Failed to prepare " + ref_profile_path);
-            }
+        if (!prepare_app_profile_dir(packageName, appId, userId)) {
+            return error("Failed to prepare profiles for " + packageName);
         }
     }
     return ok();
@@ -489,16 +571,17 @@
 }
 
 
-binder::Status InstalldNativeService::clearAppProfiles(const std::string& packageName) {
+binder::Status InstalldNativeService::clearAppProfiles(const std::string& packageName,
+        const std::string& profileName) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     binder::Status res = ok();
-    if (!clear_primary_reference_profile(packageName)) {
+    if (!clear_primary_reference_profile(packageName, profileName)) {
         res = error("Failed to clear reference profile for " + packageName);
     }
-    if (!clear_primary_current_profiles(packageName)) {
+    if (!clear_primary_current_profiles(packageName, profileName)) {
         res = error("Failed to clear current profiles for " + packageName);
     }
     return res;
@@ -525,6 +608,9 @@
         if (access(path.c_str(), F_OK) == 0) {
             if (delete_dir_contents(path) != 0) {
                 res = error("Failed to delete contents of " + path);
+            } else if ((flags & (FLAG_CLEAR_CACHE_ONLY | FLAG_CLEAR_CODE_CACHE_ONLY)) == 0) {
+                remove_path_xattr(path, kXattrInodeCache);
+                remove_path_xattr(path, kXattrInodeCodeCache);
             }
         }
     }
@@ -545,11 +631,6 @@
                 res = error("Failed to delete contents of " + path);
             }
         }
-        if (!only_cache) {
-            if (!clear_primary_current_profile(packageName, userId)) {
-                res = error("Failed to clear current profile for " + packageName);
-            }
-        }
     }
     return res;
 }
@@ -711,6 +792,9 @@
                     // Ignore all other GID transitions, since they're kinda shady
                     LOG(WARNING) << "Ignoring " << p->fts_path << " with unexpected GID " << actual
                             << " instead of " << expected;
+                    if (!(flags & FLAG_FORCE)) {
+                        fts_set(fts, p, FTS_SKIP);
+                    }
                 }
             }
         }
@@ -1093,6 +1177,7 @@
 binder::Status InstalldNativeService::rmdex(const std::string& codePath,
         const std::string& instructionSet) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(codePath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     char dex_path[PKG_PATH_MAX];
@@ -1349,9 +1434,12 @@
         const std::vector<std::string>& codePaths, std::vector<int64_t>* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(uuid);
-    for (auto packageName : packageNames) {
+    for (const auto& packageName : packageNames) {
         CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     }
+    for (const auto& codePath : codePaths) {
+        CHECK_ARGUMENT_PATH(codePath);
+    }
     // NOTE: Locking is relaxed on this method, since it's limited to
     // read-only measurements without mutation.
 
@@ -1388,7 +1476,7 @@
     }
 
     ATRACE_BEGIN("obb");
-    for (auto packageName : packageNames) {
+    for (const auto& packageName : packageNames) {
         auto obbCodePath = create_data_media_obb_path(uuid_, packageName.c_str());
         calculate_tree_size(obbCodePath, &extStats.codeSize);
     }
@@ -1396,7 +1484,7 @@
 
     if (flags & FLAG_USE_QUOTA && appId >= AID_APP_START) {
         ATRACE_BEGIN("code");
-        for (auto codePath : codePaths) {
+        for (const auto& codePath : codePaths) {
             calculate_tree_size(codePath, &stats.codeSize, -1,
                     multiuser_get_shared_gid(0, appId));
         }
@@ -1407,7 +1495,7 @@
         ATRACE_END();
     } else {
         ATRACE_BEGIN("code");
-        for (auto codePath : codePaths) {
+        for (const auto& codePath : codePaths) {
             calculate_tree_size(codePath, &stats.codeSize);
         }
         ATRACE_END();
@@ -1794,64 +1882,101 @@
 // Dumps the contents of a profile file, using pkgname's dex files for pretty
 // printing the result.
 binder::Status InstalldNativeService::dumpProfiles(int32_t uid, const std::string& packageName,
-        const std::string& codePaths, bool* _aidl_return) {
+        const std::string& profileName, const std::string& codePath, bool* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    CHECK_ARGUMENT_PATH(codePath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
-    const char* pkgname = packageName.c_str();
-    const char* code_paths = codePaths.c_str();
-
-    *_aidl_return = dump_profiles(uid, pkgname, code_paths);
+    *_aidl_return = dump_profiles(uid, packageName, profileName, codePath);
     return ok();
 }
 
 // Copy the contents of a system profile over the data profile.
 binder::Status InstalldNativeService::copySystemProfile(const std::string& systemProfile,
-        int32_t packageUid, const std::string& packageName, bool* _aidl_return) {
+        int32_t packageUid, const std::string& packageName, const std::string& profileName,
+        bool* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     std::lock_guard<std::recursive_mutex> lock(mLock);
-    *_aidl_return = copy_system_profile(systemProfile, packageUid, packageName);
+    *_aidl_return = copy_system_profile(systemProfile, packageUid, packageName, profileName);
     return ok();
 }
 
 // TODO: Consider returning error codes.
 binder::Status InstalldNativeService::mergeProfiles(int32_t uid, const std::string& packageName,
-        bool* _aidl_return) {
+        const std::string& profileName, bool* _aidl_return) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
-    *_aidl_return = analyze_primary_profiles(uid, packageName);
+    *_aidl_return = analyze_primary_profiles(uid, packageName, profileName);
     return ok();
 }
 
+binder::Status InstalldNativeService::createProfileSnapshot(int32_t appId,
+        const std::string& packageName, const std::string& profileName,
+        const std::string& classpath, bool* _aidl_return) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
+    *_aidl_return = create_profile_snapshot(appId, packageName, profileName, classpath);
+    return ok();
+}
+
+binder::Status InstalldNativeService::destroyProfileSnapshot(const std::string& packageName,
+        const std::string& profileName) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
+    std::string snapshot = create_snapshot_profile_path(packageName, profileName);
+    if ((unlink(snapshot.c_str()) != 0) && (errno != ENOENT)) {
+        return error("Failed to destroy profile snapshot for " + packageName + ":" + profileName);
+    }
+    return ok();
+}
+
+static const char* getCStr(const std::unique_ptr<std::string>& data,
+        const char* default_value = nullptr) {
+    return data == nullptr ? default_value : data->c_str();
+}
 binder::Status InstalldNativeService::dexopt(const std::string& apkPath, int32_t uid,
         const std::unique_ptr<std::string>& packageName, const std::string& instructionSet,
         int32_t dexoptNeeded, const std::unique_ptr<std::string>& outputPath, int32_t dexFlags,
         const std::string& compilerFilter, const std::unique_ptr<std::string>& uuid,
         const std::unique_ptr<std::string>& classLoaderContext,
-        const std::unique_ptr<std::string>& seInfo, bool downgrade) {
+        const std::unique_ptr<std::string>& seInfo, bool downgrade, int32_t targetSdkVersion,
+        const std::unique_ptr<std::string>& profileName,
+        const std::unique_ptr<std::string>& dexMetadataPath,
+        const std::unique_ptr<std::string>& compilationReason) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(uuid);
+    CHECK_ARGUMENT_PATH(apkPath);
     if (packageName && *packageName != "*") {
         CHECK_ARGUMENT_PACKAGE_NAME(*packageName);
     }
+    CHECK_ARGUMENT_PATH(outputPath);
+    CHECK_ARGUMENT_PATH(dexMetadataPath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* apk_path = apkPath.c_str();
-    const char* pkgname = packageName ? packageName->c_str() : "*";
+    const char* pkgname = getCStr(packageName, "*");
     const char* instruction_set = instructionSet.c_str();
-    const char* oat_dir = outputPath ? outputPath->c_str() : nullptr;
+    const char* oat_dir = getCStr(outputPath);
     const char* compiler_filter = compilerFilter.c_str();
-    const char* volume_uuid = uuid ? uuid->c_str() : nullptr;
-    const char* class_loader_context = classLoaderContext ? classLoaderContext->c_str() : nullptr;
-    const char* se_info = seInfo ? seInfo->c_str() : nullptr;
+    const char* volume_uuid = getCStr(uuid);
+    const char* class_loader_context = getCStr(classLoaderContext);
+    const char* se_info = getCStr(seInfo);
+    const char* profile_name = getCStr(profileName);
+    const char* dm_path = getCStr(dexMetadataPath);
+    const char* compilation_reason = getCStr(compilationReason);
+    std::string error_msg;
     int res = android::installd::dexopt(apk_path, uid, pkgname, instruction_set, dexoptNeeded,
             oat_dir, dexFlags, compiler_filter, volume_uuid, class_loader_context, se_info,
-            downgrade);
-    return res ? error(res, "Failed to dexopt") : ok();
+            downgrade, targetSdkVersion, profile_name, dm_path, compilation_reason, &error_msg);
+    return res ? error(res, error_msg) : ok();
 }
 
 binder::Status InstalldNativeService::markBootComplete(const std::string& instructionSet) {
@@ -1863,7 +1988,7 @@
     char boot_marker_path[PKG_PATH_MAX];
     sprintf(boot_marker_path,
           "%s/%s/%s/.booting",
-          android_data_dir.path,
+          android_data_dir.c_str(),
           DALVIK_CACHE,
           instruction_set);
 
@@ -1874,33 +1999,13 @@
     return ok();
 }
 
-void mkinnerdirs(char* path, int basepos, mode_t mode, int uid, int gid,
-        struct stat* statbuf)
-{
-    while (path[basepos] != 0) {
-        if (path[basepos] == '/') {
-            path[basepos] = 0;
-            if (lstat(path, statbuf) < 0) {
-                ALOGV("Making directory: %s\n", path);
-                if (mkdir(path, mode) == 0) {
-                    chown(path, uid, gid);
-                } else {
-                    ALOGW("Unable to make directory %s: %s\n", path, strerror(errno));
-                }
-            }
-            path[basepos] = '/';
-            basepos++;
-        }
-        basepos++;
-    }
-}
-
 binder::Status InstalldNativeService::linkNativeLibraryDirectory(
         const std::unique_ptr<std::string>& uuid, const std::string& packageName,
         const std::string& nativeLibPath32, int32_t userId) {
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(uuid);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    CHECK_ARGUMENT_PATH(nativeLibPath32);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* uuid_ = uuid ? uuid->c_str() : nullptr;
@@ -2068,6 +2173,8 @@
 binder::Status InstalldNativeService::idmap(const std::string& targetApkPath,
         const std::string& overlayApkPath, int32_t uid) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(targetApkPath);
+    CHECK_ARGUMENT_PATH(overlayApkPath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* target_apk = targetApkPath.c_str();
@@ -2153,6 +2260,10 @@
 }
 
 binder::Status InstalldNativeService::removeIdmap(const std::string& overlayApkPath) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(overlayApkPath);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
     const char* overlay_apk = overlayApkPath.c_str();
     char idmap_path[PATH_MAX];
 
@@ -2203,6 +2314,7 @@
 binder::Status InstalldNativeService::createOatDir(const std::string& oatDir,
         const std::string& instructionSet) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(oatDir);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* oat_dir = oatDir.c_str();
@@ -2227,6 +2339,7 @@
 
 binder::Status InstalldNativeService::rmPackageDir(const std::string& packageDir) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(packageDir);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     if (validate_apk_path(packageDir.c_str())) {
@@ -2241,6 +2354,8 @@
 binder::Status InstalldNativeService::linkFile(const std::string& relativePath,
         const std::string& fromBase, const std::string& toBase) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(fromBase);
+    CHECK_ARGUMENT_PATH(toBase);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* relative_path = relativePath.c_str();
@@ -2269,6 +2384,8 @@
 binder::Status InstalldNativeService::moveAb(const std::string& apkPath,
         const std::string& instructionSet, const std::string& outputPath) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(apkPath);
+    CHECK_ARGUMENT_PATH(outputPath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* apk_path = apkPath.c_str();
@@ -2282,6 +2399,8 @@
 binder::Status InstalldNativeService::deleteOdex(const std::string& apkPath,
         const std::string& instructionSet, const std::unique_ptr<std::string>& outputPath) {
     ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(apkPath);
+    CHECK_ARGUMENT_PATH(outputPath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
 
     const char* apk_path = apkPath.c_str();
@@ -2292,6 +2411,142 @@
     return res ? ok() : error();
 }
 
+// This kernel feature is experimental.
+// TODO: remove local definition once upstreamed
+#ifndef FS_IOC_ENABLE_VERITY
+
+#define FS_IOC_ENABLE_VERITY           _IO('f', 133)
+#define FS_IOC_SET_VERITY_MEASUREMENT  _IOW('f', 134, struct fsverity_measurement)
+
+#define FS_VERITY_ALG_SHA256           1
+
+struct fsverity_measurement {
+    __u16 digest_algorithm;
+    __u16 digest_size;
+    __u32 reserved1;
+    __u64 reserved2[3];
+    __u8 digest[];
+};
+
+#endif
+
+binder::Status InstalldNativeService::installApkVerity(const std::string& filePath,
+        const ::android::base::unique_fd& verityInputAshmem, int32_t contentSize) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(filePath);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
+    if (!android::base::GetBoolProperty(kPropApkVerityMode, false)) {
+        return ok();
+    }
+#ifndef NDEBUG
+    ASSERT_PAGE_SIZE_4K();
+#endif
+    // TODO: also check fsverity support in the current file system if compiled with DEBUG.
+    // TODO: change ashmem to some temporary file to support huge apk.
+    if (!ashmem_valid(verityInputAshmem.get())) {
+        return error("FD is not an ashmem");
+    }
+
+    // 1. Seek to the next page boundary beyond the end of the file.
+    ::android::base::unique_fd wfd(open(filePath.c_str(), O_WRONLY));
+    if (wfd.get() < 0) {
+        return error("Failed to open " + filePath);
+    }
+    struct stat st;
+    if (fstat(wfd.get(), &st) < 0) {
+        return error("Failed to stat " + filePath);
+    }
+    // fsverity starts from the block boundary.
+    off_t padding = kVerityPageSize - st.st_size % kVerityPageSize;
+    if (padding == kVerityPageSize) {
+        padding = 0;
+    }
+    if (lseek(wfd.get(), st.st_size + padding, SEEK_SET) < 0) {
+        return error("Failed to lseek " + filePath);
+    }
+
+    // 2. Write everything in the ashmem to the file.  Note that allocated
+    //    ashmem size is multiple of page size, which is different from the
+    //    actual content size.
+    int shmSize = ashmem_get_size_region(verityInputAshmem.get());
+    if (shmSize < 0) {
+        return error("Failed to get ashmem size: " + std::to_string(shmSize));
+    }
+    if (contentSize < 0) {
+        return error("Invalid content size: " + std::to_string(contentSize));
+    }
+    if (contentSize > shmSize) {
+        return error("Content size overflow: " + std::to_string(contentSize) + " > " +
+                     std::to_string(shmSize));
+    }
+    auto data = std::unique_ptr<void, std::function<void (void *)>>(
+        mmap(NULL, contentSize, PROT_READ, MAP_SHARED, verityInputAshmem.get(), 0),
+        [contentSize] (void* ptr) {
+          if (ptr != MAP_FAILED) {
+            munmap(ptr, contentSize);
+          }
+        });
+
+    if (data.get() == MAP_FAILED) {
+        return error("Failed to mmap the ashmem");
+    }
+    char* cursor = reinterpret_cast<char*>(data.get());
+    int remaining = contentSize;
+    while (remaining > 0) {
+        int ret = TEMP_FAILURE_RETRY(write(wfd.get(), cursor, remaining));
+        if (ret < 0) {
+            return error("Failed to write to " + filePath + " (" + std::to_string(remaining) +
+                         + "/" + std::to_string(contentSize) + ")");
+        }
+        cursor += ret;
+        remaining -= ret;
+    }
+    wfd.reset();
+
+    // 3. Enable fsverity (needs readonly fd. Once it's done, the file becomes immutable.
+    ::android::base::unique_fd rfd(open(filePath.c_str(), O_RDONLY));
+    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) {
+        return error("Failed to enable fsverity on " + filePath);
+    }
+    return ok();
+}
+
+binder::Status InstalldNativeService::assertFsverityRootHashMatches(const std::string& filePath,
+        const std::vector<uint8_t>& expectedHash) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PATH(filePath);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
+    if (!android::base::GetBoolProperty(kPropApkVerityMode, false)) {
+        return ok();
+    }
+    // TODO: also check fsverity support in the current file system if compiled with DEBUG.
+    if (expectedHash.size() != kSha256Size) {
+        return error("verity hash size should be " + std::to_string(kSha256Size) + " but is " +
+                     std::to_string(expectedHash.size()));
+    }
+
+    ::android::base::unique_fd fd(open(filePath.c_str(), O_RDONLY));
+    if (fd.get() < 0) {
+        return error("Failed to open " + filePath + ": " + strerror(errno));
+    }
+
+    unsigned int buffer_size = sizeof(fsverity_measurement) + kSha256Size;
+    std::vector<char> buffer(buffer_size, 0);
+
+    fsverity_measurement* config = reinterpret_cast<fsverity_measurement*>(buffer.data());
+    config->digest_algorithm = FS_VERITY_ALG_SHA256;
+    config->digest_size = kSha256Size;
+    memcpy(config->digest, expectedHash.data(), kSha256Size);
+    if (ioctl(fd.get(), FS_IOC_SET_VERITY_MEASUREMENT, config) < 0) {
+        // This includes an expected failure case with no FSVerity setup. It normally happens when
+        // the apk does not contains the Merkle tree root hash.
+        return error("Failed to measure fsverity on " + filePath + ": " + strerror(errno));
+    }
+    return ok();  // hashes match
+}
+
 binder::Status InstalldNativeService::reconcileSecondaryDexFile(
         const std::string& dexPath, const std::string& packageName, int32_t uid,
         const std::vector<std::string>& isas, const std::unique_ptr<std::string>& volumeUuid,
@@ -2299,13 +2554,31 @@
     ENFORCE_UID(AID_SYSTEM);
     CHECK_ARGUMENT_UUID(volumeUuid);
     CHECK_ARGUMENT_PACKAGE_NAME(packageName);
-
+    CHECK_ARGUMENT_PATH(dexPath);
     std::lock_guard<std::recursive_mutex> lock(mLock);
+
     bool result = android::installd::reconcile_secondary_dex_file(
             dexPath, packageName, uid, isas, volumeUuid, storage_flag, _aidl_return);
     return result ? ok() : error();
 }
 
+binder::Status InstalldNativeService::hashSecondaryDexFile(
+        const std::string& dexPath, const std::string& packageName, int32_t uid,
+        const std::unique_ptr<std::string>& volumeUuid, int32_t storageFlag,
+        std::vector<uint8_t>* _aidl_return) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_UUID(volumeUuid);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    CHECK_ARGUMENT_PATH(dexPath);
+
+    // mLock is not taken here since we will never modify the file system.
+    // If a file is modified just as we are reading it this may result in an
+    // anomalous hash, but that's ok.
+    bool result = android::installd::hash_secondary_dex_file(
+        dexPath, packageName, uid, volumeUuid, storageFlag, _aidl_return);
+    return result ? ok() : error();
+}
+
 binder::Status InstalldNativeService::invalidateMounts() {
     ENFORCE_UID(AID_SYSTEM);
     std::lock_guard<std::recursive_mutex> lock(mMountsLock);
@@ -2342,14 +2615,18 @@
                 mQuotaReverseMounts[target] = source;
 
                 // ext4 only enables DQUOT_USAGE_ENABLED by default, so we
-                // need to kick it again to enable DQUOT_LIMITS_ENABLED.
-                if (quotactl(QCMD(Q_QUOTAON, USRQUOTA), source.c_str(), QFMT_VFS_V1, nullptr) != 0
-                        && errno != EBUSY) {
-                    PLOG(ERROR) << "Failed to enable USRQUOTA on " << source;
-                }
-                if (quotactl(QCMD(Q_QUOTAON, GRPQUOTA), source.c_str(), QFMT_VFS_V1, nullptr) != 0
-                        && errno != EBUSY) {
-                    PLOG(ERROR) << "Failed to enable GRPQUOTA on " << source;
+                // need to kick it again to enable DQUOT_LIMITS_ENABLED. We
+                // only need hard limits enabled when we're not being protected
+                // by reserved blocks.
+                if (!android::base::GetBoolProperty(kPropHasReserved, false)) {
+                    if (quotactl(QCMD(Q_QUOTAON, USRQUOTA), source.c_str(), QFMT_VFS_V1,
+                            nullptr) != 0 && errno != EBUSY) {
+                        PLOG(ERROR) << "Failed to enable USRQUOTA on " << source;
+                    }
+                    if (quotactl(QCMD(Q_QUOTAON, GRPQUOTA), source.c_str(), QFMT_VFS_V1,
+                            nullptr) != 0 && errno != EBUSY) {
+                        PLOG(ERROR) << "Failed to enable GRPQUOTA on " << source;
+                    }
                 }
             }
         }
@@ -2384,5 +2661,18 @@
     return ok();
 }
 
+binder::Status InstalldNativeService::prepareAppProfile(const std::string& packageName,
+        int32_t userId, int32_t appId, const std::string& profileName, const std::string& codePath,
+        const std::unique_ptr<std::string>& dexMetadata, bool* _aidl_return) {
+    ENFORCE_UID(AID_SYSTEM);
+    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+    CHECK_ARGUMENT_PATH(codePath);
+    std::lock_guard<std::recursive_mutex> lock(mLock);
+
+    *_aidl_return = prepare_app_profile(packageName, userId, appId, profileName, codePath,
+        dexMetadata);
+    return ok();
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index c8db3df..cebd3f9 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -84,18 +84,28 @@
             int32_t dexoptNeeded, const std::unique_ptr<std::string>& outputPath, int32_t dexFlags,
             const std::string& compilerFilter, const std::unique_ptr<std::string>& uuid,
             const std::unique_ptr<std::string>& classLoaderContext,
-            const std::unique_ptr<std::string>& seInfo, bool downgrade);
+            const std::unique_ptr<std::string>& seInfo, bool downgrade,
+            int32_t targetSdkVersion, const std::unique_ptr<std::string>& profileName,
+            const std::unique_ptr<std::string>& dexMetadataPath,
+            const std::unique_ptr<std::string>& compilationReason);
 
     binder::Status rmdex(const std::string& codePath, const std::string& instructionSet);
 
-    binder::Status mergeProfiles(int32_t uid, const std::string& packageName, bool* _aidl_return);
+    binder::Status mergeProfiles(int32_t uid, const std::string& packageName,
+            const std::string& profileName, bool* _aidl_return);
     binder::Status dumpProfiles(int32_t uid, const std::string& packageName,
-            const std::string& codePaths, bool* _aidl_return);
+            const std::string& profileName, const std::string& codePath, bool* _aidl_return);
     binder::Status copySystemProfile(const std::string& systemProfile,
-            int32_t uid, const std::string& packageName, bool* _aidl_return);
-    binder::Status clearAppProfiles(const std::string& packageName);
+            int32_t uid, const std::string& packageName, const std::string& profileName,
+            bool* _aidl_return);
+    binder::Status clearAppProfiles(const std::string& packageName, const std::string& profileName);
     binder::Status destroyAppProfiles(const std::string& packageName);
 
+    binder::Status createProfileSnapshot(int32_t appId, const std::string& packageName,
+            const std::string& profileName, const std::string& classpath, bool* _aidl_return);
+    binder::Status destroyProfileSnapshot(const std::string& packageName,
+            const std::string& profileName);
+
     binder::Status idmap(const std::string& targetApkPath, const std::string& overlayApkPath,
             int32_t uid);
     binder::Status removeIdmap(const std::string& overlayApkPath);
@@ -112,14 +122,26 @@
             const std::string& outputPath);
     binder::Status deleteOdex(const std::string& apkPath, const std::string& instructionSet,
             const std::unique_ptr<std::string>& outputPath);
+    binder::Status installApkVerity(const std::string& filePath,
+            const ::android::base::unique_fd& verityInput, int32_t contentSize);
+    binder::Status assertFsverityRootHashMatches(const std::string& filePath,
+            const std::vector<uint8_t>& expectedHash);
     binder::Status reconcileSecondaryDexFile(const std::string& dexPath,
         const std::string& packageName, int32_t uid, const std::vector<std::string>& isa,
         const std::unique_ptr<std::string>& volumeUuid, int32_t storage_flag, bool* _aidl_return);
+    binder::Status hashSecondaryDexFile(const std::string& dexPath,
+        const std::string& packageName, int32_t uid, const std::unique_ptr<std::string>& volumeUuid,
+        int32_t storageFlag, std::vector<uint8_t>* _aidl_return);
 
     binder::Status invalidateMounts();
     binder::Status isQuotaSupported(const std::unique_ptr<std::string>& volumeUuid,
             bool* _aidl_return);
 
+    binder::Status prepareAppProfile(const std::string& packageName,
+            int32_t userId, int32_t appId, const std::string& profileName,
+            const std::string& codePath, const std::unique_ptr<std::string>& dexMetadata,
+            bool* _aidl_return);
+
 private:
     std::recursive_mutex mLock;
 
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 452a2b1..91e20b7 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -51,17 +51,25 @@
             @nullable @utf8InCpp String outputPath, int dexFlags,
             @utf8InCpp String compilerFilter, @nullable @utf8InCpp String uuid,
             @nullable @utf8InCpp String sharedLibraries,
-            @nullable @utf8InCpp String seInfo, boolean downgrade);
+            @nullable @utf8InCpp String seInfo, boolean downgrade, int targetSdkVersion,
+            @nullable @utf8InCpp String profileName,
+            @nullable @utf8InCpp String dexMetadataPath,
+            @nullable @utf8InCpp String compilationReason);
 
     void rmdex(@utf8InCpp String codePath, @utf8InCpp String instructionSet);
 
-    boolean mergeProfiles(int uid, @utf8InCpp String packageName);
-    boolean dumpProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String codePaths);
+    boolean mergeProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String profileName);
+    boolean dumpProfiles(int uid, @utf8InCpp String packageName, @utf8InCpp String  profileName,
+            @utf8InCpp String codePath);
     boolean copySystemProfile(@utf8InCpp String systemProfile, int uid,
-            @utf8InCpp String packageName);
-    void clearAppProfiles(@utf8InCpp String packageName);
+            @utf8InCpp String packageName, @utf8InCpp String profileName);
+    void clearAppProfiles(@utf8InCpp String packageName, @utf8InCpp String profileName);
     void destroyAppProfiles(@utf8InCpp String packageName);
 
+    boolean createProfileSnapshot(int appId, @utf8InCpp String packageName,
+            @utf8InCpp String profileName, @utf8InCpp String classpath);
+    void destroyProfileSnapshot(@utf8InCpp String packageName, @utf8InCpp String profileName);
+
     void idmap(@utf8InCpp String targetApkPath, @utf8InCpp String overlayApkPath, int uid);
     void removeIdmap(@utf8InCpp String overlayApkPath);
     void rmPackageDir(@utf8InCpp String packageDir);
@@ -77,11 +85,21 @@
             @utf8InCpp String outputPath);
     void deleteOdex(@utf8InCpp String apkPath, @utf8InCpp String instructionSet,
             @nullable @utf8InCpp String outputPath);
+    void installApkVerity(@utf8InCpp String filePath, in FileDescriptor verityInput,
+            int contentSize);
+    void assertFsverityRootHashMatches(@utf8InCpp String filePath, in byte[] expectedHash);
 
     boolean reconcileSecondaryDexFile(@utf8InCpp String dexPath, @utf8InCpp String pkgName,
         int uid, in @utf8InCpp String[] isas, @nullable @utf8InCpp String volume_uuid,
         int storage_flag);
 
+    byte[] hashSecondaryDexFile(@utf8InCpp String dexPath, @utf8InCpp String pkgName,
+        int uid, @nullable @utf8InCpp String volumeUuid, int storageFlag);
+
     void invalidateMounts();
     boolean isQuotaSupported(@nullable @utf8InCpp String uuid);
+
+    boolean prepareAppProfile(@utf8InCpp String packageName,
+        int userId, int appId, @utf8InCpp String profileName, @utf8InCpp String codePath,
+        @nullable @utf8InCpp String dexMetadata);
 }
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index f29da17..9615a75 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -15,6 +15,7 @@
  */
 #define LOG_TAG "installed"
 
+#include <array>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
@@ -27,30 +28,51 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <iomanip>
+
+#include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <cutils/fs.h>
 #include <cutils/properties.h>
 #include <cutils/sched_policy.h>
+#include <dex2oat_return_codes.h>
 #include <log/log.h>               // TODO: Move everything to base/logging.
+#include <openssl/sha.h>
 #include <private/android_filesystem_config.h>
 #include <selinux/android.h>
 #include <system/thread_defs.h>
 
 #include "dexopt.h"
+#include "dexopt_return_codes.h"
+#include "globals.h"
 #include "installd_deps.h"
 #include "otapreopt_utils.h"
 #include "utils.h"
 
-using android::base::StringPrintf;
 using android::base::EndsWith;
+using android::base::ReadFully;
+using android::base::StringPrintf;
+using android::base::WriteFully;
 using android::base::unique_fd;
 
 namespace android {
 namespace installd {
 
+// Should minidebug info be included in compiled artifacts? Even if this value is
+// "true," usage might still be conditional to other constraints, e.g., system
+// property overrides.
+static constexpr bool kEnableMinidebugInfo = true;
+
+static constexpr const char* kMinidebugInfoSystemProperty = "dalvik.vm.dex2oat-minidebuginfo";
+static constexpr bool kMinidebugInfoSystemPropertyDefault = false;
+static constexpr const char* kMinidebugDex2oatFlag = "--generate-mini-debug-info";
+static constexpr const char* kDisableCompactDexFlag = "--compact-dex-level=none";
+
+
 // Deleter using free() for use with std::unique_ptr<>. See also UniqueCPtr<> below.
 struct FreeDelete {
   // NOTE: Deleting a const object is valid but free() takes a non-const pointer.
@@ -67,6 +89,14 @@
     return unique_fd(-1);
 }
 
+static bool is_debug_runtime() {
+    return android::base::GetProperty("persist.sys.dalvik.vm.lib.2", "") == "libartd.so";
+}
+
+static bool is_debuggable_build() {
+    return android::base::GetBoolProperty("ro.debuggable", false);
+}
+
 static bool clear_profile(const std::string& profile) {
     unique_fd ufd(open(profile.c_str(), O_WRONLY | O_NOFOLLOW | O_CLOEXEC));
     if (ufd.get() < 0) {
@@ -112,37 +142,43 @@
 }
 
 // Clear the reference profile for the given location.
-// The location is the package name for primary apks or the dex path for secondary dex files.
-static bool clear_reference_profile(const std::string& location, bool is_secondary_dex) {
-    return clear_profile(create_reference_profile_path(location, is_secondary_dex));
+// The location is the profile name for primary apks or the dex path for secondary dex files.
+static bool clear_reference_profile(const std::string& package_name, const std::string& location,
+        bool is_secondary_dex) {
+    return clear_profile(create_reference_profile_path(package_name, location, is_secondary_dex));
 }
 
 // Clear the reference profile for the given location.
-// The location is the package name for primary apks or the dex path for secondary dex files.
-static bool clear_current_profile(const std::string& pkgname, userid_t user,
-        bool is_secondary_dex) {
-    return clear_profile(create_current_profile_path(user, pkgname, is_secondary_dex));
+// The location is the profile name for primary apks or the dex path for secondary dex files.
+static bool clear_current_profile(const std::string& package_name, const std::string& location,
+        userid_t user, bool is_secondary_dex) {
+    return clear_profile(create_current_profile_path(user, package_name, location,
+            is_secondary_dex));
 }
 
 // Clear the reference profile for the primary apk of the given package.
-bool clear_primary_reference_profile(const std::string& pkgname) {
-    return clear_reference_profile(pkgname, /*is_secondary_dex*/false);
+// The location is the profile name for primary apks or the dex path for secondary dex files.
+bool clear_primary_reference_profile(const std::string& package_name,
+        const std::string& location) {
+    return clear_reference_profile(package_name, location, /*is_secondary_dex*/false);
 }
 
 // Clear all current profile for the primary apk of the given package.
-bool clear_primary_current_profiles(const std::string& pkgname) {
+// The location is the profile name for primary apks or the dex path for secondary dex files.
+bool clear_primary_current_profiles(const std::string& package_name, const std::string& location) {
     bool success = true;
     // For secondary dex files, we don't really need the user but we use it for sanity checks.
     std::vector<userid_t> users = get_known_users(/*volume_uuid*/ nullptr);
     for (auto user : users) {
-        success &= clear_current_profile(pkgname, user, /*is_secondary_dex*/false);
+        success &= clear_current_profile(package_name, location, user, /*is_secondary_dex*/false);
     }
     return success;
 }
 
 // Clear the current profile for the primary apk of the given package and user.
-bool clear_primary_current_profile(const std::string& pkgname, userid_t user) {
-    return clear_current_profile(pkgname, user, /*is_secondary_dex*/false);
+bool clear_primary_current_profile(const std::string& package_name, const std::string& location,
+        userid_t user) {
+    return clear_current_profile(package_name, location, user, /*is_secondary_dex*/false);
 }
 
 static int split_count(const char *str)
@@ -151,7 +187,7 @@
   int count = 0;
   char buf[kPropertyValueMax];
 
-  strncpy(buf, str, sizeof(buf));
+  strlcpy(buf, str, sizeof(buf));
   char *pBuf = buf;
 
   while(strtok_r(pBuf, " ", &ctx) != NULL) {
@@ -188,16 +224,19 @@
     }
 }
 
+[[ noreturn ]]
 static void run_dex2oat(int zip_fd, int oat_fd, int input_vdex_fd, int output_vdex_fd, int image_fd,
         const char* input_file_name, const char* output_file_name, int swap_fd,
         const char* instruction_set, const char* compiler_filter,
-        bool debuggable, bool post_bootcomplete, int profile_fd, const char* class_loader_context) {
+        bool debuggable, bool post_bootcomplete, bool background_job_compile, int profile_fd,
+        const char* class_loader_context, int target_sdk_version, bool enable_hidden_api_checks,
+        bool generate_compact_dex, int dex_metadata_fd, const char* compilation_reason) {
     static const unsigned int MAX_INSTRUCTION_SET_LEN = 7;
 
     if (strlen(instruction_set) >= MAX_INSTRUCTION_SET_LEN) {
-        ALOGE("Instruction set %s longer than max length of %d",
-              instruction_set, MAX_INSTRUCTION_SET_LEN);
-        return;
+        LOG(ERROR) << "Instruction set '" << instruction_set << "' longer than max length of "
+                   << MAX_INSTRUCTION_SET_LEN;
+        exit(DexoptReturnCodes::kInstructionSetLength);
     }
 
     // Get the relative path to the input file.
@@ -267,7 +306,20 @@
                 dex2oat_large_app_threshold);
     }
 
-    static const char* DEX2OAT_BIN = "/system/bin/dex2oat";
+    // If the runtime was requested to use libartd.so, we'll run dex2oatd, otherwise dex2oat.
+    const char* dex2oat_bin = "/system/bin/dex2oat";
+    constexpr const char* kDex2oatDebugPath = "/system/bin/dex2oatd";
+    // Do not use dex2oatd for release candidates (give dex2oat more soak time).
+    bool is_release = android::base::GetProperty("ro.build.version.codename", "") == "REL";
+    if (is_debug_runtime() || (background_job_compile && is_debuggable_build() && !is_release)) {
+        if (access(kDex2oatDebugPath, X_OK) == 0) {
+            dex2oat_bin = kDex2oatDebugPath;
+        }
+    }
+
+    bool generate_minidebug_info = kEnableMinidebugInfo &&
+            android::base::GetBoolProperty(kMinidebugInfoSystemProperty,
+                                           kMinidebugInfoSystemPropertyDefault);
 
     static const char* RUNTIME_ARG = "--runtime-arg";
 
@@ -292,6 +344,7 @@
     bool have_dex2oat_image_fd = false;
     char dex2oat_image_fd[arraysize("--app-image-fd=") + MAX_INT_LEN];
     size_t class_loader_context_size = arraysize("--class-loader-context=") + PKG_PATH_MAX;
+    char target_sdk_version_arg[arraysize("-Xtarget-sdk-version:") + MAX_INT_LEN];
     char class_loader_context_arg[class_loader_context_size];
     if (class_loader_context != nullptr) {
         snprintf(class_loader_context_arg, class_loader_context_size, "--class-loader-context=%s",
@@ -322,12 +375,14 @@
     if (have_dex2oat_Xmx_flag) {
         sprintf(dex2oat_Xmx_arg, "-Xmx%s", dex2oat_Xmx_flag);
     }
+    sprintf(target_sdk_version_arg, "-Xtarget-sdk-version:%d", target_sdk_version);
 
     // Compute compiler filter.
 
     bool have_dex2oat_compiler_filter_flag = false;
     if (skip_compilation) {
-        strcpy(dex2oat_compiler_filter_arg, "--compiler-filter=extract");
+        strlcpy(dex2oat_compiler_filter_arg, "--compiler-filter=extract",
+                sizeof(dex2oat_compiler_filter_arg));
         have_dex2oat_compiler_filter_flag = true;
         have_dex2oat_relocation_skip_flag = true;
     } else if (compiler_filter != nullptr) {
@@ -375,8 +430,17 @@
         sprintf(base_dir, "--classpath-dir=%s", apk_dir.c_str());
     }
 
+    std::string dex_metadata_fd_arg = "--dm-fd=" + std::to_string(dex_metadata_fd);
 
-    ALOGV("Running %s in=%s out=%s\n", DEX2OAT_BIN, relative_input_file_name, output_file_name);
+    std::string compilation_reason_arg = compilation_reason == nullptr
+            ? ""
+            : std::string("--compilation-reason=") + compilation_reason;
+
+    ALOGV("Running %s in=%s out=%s\n", dex2oat_bin, relative_input_file_name, output_file_name);
+
+    // Disable cdex if update input vdex is true since this combination of options is not
+    // supported.
+    const bool disable_cdex = !generate_compact_dex || (input_vdex_fd == output_vdex_fd);
 
     const char* argv[9  // program name, mandatory arguments and the final NULL
                      + (have_dex2oat_isa_variant ? 1 : 0)
@@ -395,9 +459,15 @@
                      + (profile_fd == -1 ? 0 : 1)
                      + (class_loader_context != nullptr ? 1 : 0)
                      + (has_base_dir ? 1 : 0)
-                     + (have_dex2oat_large_app_threshold ? 1 : 0)];
+                     + (have_dex2oat_large_app_threshold ? 1 : 0)
+                     + (disable_cdex ? 1 : 0)
+                     + (generate_minidebug_info ? 1 : 0)
+                     + (target_sdk_version != 0 ? 2 : 0)
+                     + (enable_hidden_api_checks ? 2 : 0)
+                     + (dex_metadata_fd > -1 ? 1 : 0)
+                     + (compilation_reason != nullptr ? 1 : 0)];
     int i = 0;
-    argv[i++] = DEX2OAT_BIN;
+    argv[i++] = dex2oat_bin;
     argv[i++] = zip_fd_arg;
     argv[i++] = zip_location_arg;
     argv[i++] = input_vdex_fd_arg;
@@ -459,12 +529,34 @@
     if (class_loader_context != nullptr) {
         argv[i++] = class_loader_context_arg;
     }
+    if (generate_minidebug_info) {
+        argv[i++] = kMinidebugDex2oatFlag;
+    }
+    if (disable_cdex) {
+        argv[i++] = kDisableCompactDexFlag;
+    }
+    if (target_sdk_version != 0) {
+        argv[i++] = RUNTIME_ARG;
+        argv[i++] = target_sdk_version_arg;
+    }
+    if (enable_hidden_api_checks) {
+        argv[i++] = RUNTIME_ARG;
+        argv[i++] = "-Xhidden-api-checks";
+    }
 
+    if (dex_metadata_fd > -1) {
+        argv[i++] = dex_metadata_fd_arg.c_str();
+    }
+
+    if(compilation_reason != nullptr) {
+        argv[i++] = compilation_reason_arg.c_str();
+    }
     // Do not add after dex2oat_flags, they should override others for debugging.
     argv[i] = NULL;
 
-    execv(DEX2OAT_BIN, (char * const *)argv);
-    ALOGE("execv(%s) failed: %s\n", DEX2OAT_BIN, strerror(errno));
+    execv(dex2oat_bin, (char * const *)argv);
+    PLOG(ERROR) << "execv(" << dex2oat_bin << ") failed";
+    exit(DexoptReturnCodes::kDex2oatExec);
 }
 
 /*
@@ -516,24 +608,22 @@
 static void SetDex2OatScheduling(bool set_to_bg) {
     if (set_to_bg) {
         if (set_sched_policy(0, SP_BACKGROUND) < 0) {
-            ALOGE("set_sched_policy failed: %s\n", strerror(errno));
-            exit(70);
+            PLOG(ERROR) << "set_sched_policy failed";
+            exit(DexoptReturnCodes::kSetSchedPolicy);
         }
         if (setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_BACKGROUND) < 0) {
-            ALOGE("setpriority failed: %s\n", strerror(errno));
-            exit(71);
+            PLOG(ERROR) << "setpriority failed";
+            exit(DexoptReturnCodes::kSetPriority);
         }
     }
 }
 
-static bool create_profile(int uid, const std::string& profile) {
-    unique_fd fd(TEMP_FAILURE_RETRY(open(profile.c_str(), O_CREAT | O_NOFOLLOW, 0600)));
+static unique_fd create_profile(uid_t uid, const std::string& profile, int32_t flags) {
+    unique_fd fd(TEMP_FAILURE_RETRY(open(profile.c_str(), flags, 0600)));
     if (fd.get() < 0) {
-        if (errno == EEXIST) {
-            return true;
-        } else {
+        if (errno != EEXIST) {
             PLOG(ERROR) << "Failed to create profile " << profile;
-            return false;
+            return invalid_unique_fd();
         }
     }
     // Profiles should belong to the app; make sure of that by giving ownership to
@@ -541,27 +631,26 @@
     // since dex2oat/profman will fail with SElinux denials.
     if (fchown(fd.get(), uid, uid) < 0) {
         PLOG(ERROR) << "Could not chwon profile " << profile;
-        return false;
+        return invalid_unique_fd();
     }
-    return true;
+    return fd;
 }
 
-static unique_fd open_profile(int uid, const std::string& profile, bool read_write) {
-    // Check if we need to open the profile for a read-write operation. If so, we
-    // might need to create the profile since the file might not be there. Reference
-    // profiles are created on the fly so they might not exist beforehand.
-    if (read_write) {
-        if (!create_profile(uid, profile)) {
-            return invalid_unique_fd();
-        }
-    }
-    int flags = read_write ? O_RDWR : O_RDONLY;
+static unique_fd open_profile(uid_t uid, const std::string& profile, int32_t flags) {
     // Do not follow symlinks when opening a profile:
     //   - primary profiles should not contain symlinks in their paths
     //   - secondary dex paths should have been already resolved and validated
     flags |= O_NOFOLLOW;
 
-    unique_fd fd(TEMP_FAILURE_RETRY(open(profile.c_str(), flags)));
+    // Check if we need to create the profile
+    // Reference profiles and snapshots are created on the fly; so they might not exist beforehand.
+    unique_fd fd;
+    if ((flags & O_CREAT) != 0) {
+        fd = create_profile(uid, profile, flags);
+    } else {
+        fd.reset(TEMP_FAILURE_RETRY(open(profile.c_str(), flags)));
+    }
+
     if (fd.get() < 0) {
         if (errno != ENOENT) {
             // Profiles might be missing for various reasons. For example, in a
@@ -578,23 +667,31 @@
     return fd;
 }
 
-static unique_fd open_current_profile(uid_t uid, userid_t user, const std::string& location,
-        bool is_secondary_dex) {
-    std::string profile = create_current_profile_path(user, location, is_secondary_dex);
-    return open_profile(uid, profile, /*read_write*/false);
+static unique_fd open_current_profile(uid_t uid, userid_t user, const std::string& package_name,
+        const std::string& location, bool is_secondary_dex) {
+    std::string profile = create_current_profile_path(user, package_name, location,
+            is_secondary_dex);
+    return open_profile(uid, profile, O_RDONLY);
 }
 
-static unique_fd open_reference_profile(uid_t uid, const std::string& location, bool read_write,
-        bool is_secondary_dex) {
-    std::string profile = create_reference_profile_path(location, is_secondary_dex);
-    return open_profile(uid, profile, read_write);
+static unique_fd open_reference_profile(uid_t uid, const std::string& package_name,
+        const std::string& location, bool read_write, bool is_secondary_dex) {
+    std::string profile = create_reference_profile_path(package_name, location, is_secondary_dex);
+    return open_profile(uid, profile, read_write ? (O_CREAT | O_RDWR) : O_RDONLY);
 }
 
-static void open_profile_files(uid_t uid, const std::string& location, bool is_secondary_dex,
+static unique_fd open_spnashot_profile(uid_t uid, const std::string& package_name,
+        const std::string& location) {
+    std::string profile = create_snapshot_profile_path(package_name, location);
+    return open_profile(uid, profile, O_CREAT | O_RDWR | O_TRUNC);
+}
+
+static void open_profile_files(uid_t uid, const std::string& package_name,
+            const std::string& location, bool is_secondary_dex,
             /*out*/ std::vector<unique_fd>* profiles_fd, /*out*/ unique_fd* reference_profile_fd) {
     // Open the reference profile in read-write mode as profman might need to save the merge.
-    *reference_profile_fd = open_reference_profile(uid, location, /*read_write*/ true,
-            is_secondary_dex);
+    *reference_profile_fd = open_reference_profile(uid, package_name, location,
+            /*read_write*/ true, is_secondary_dex);
 
     // For secondary dex files, we don't really need the user but we use it for sanity checks.
     // Note: the user owning the dex file should be the current user.
@@ -605,7 +702,8 @@
         users = get_known_users(/*volume_uuid*/ nullptr);
     }
     for (auto user : users) {
-        unique_fd profile_fd = open_current_profile(uid, user, location, is_secondary_dex);
+        unique_fd profile_fd = open_current_profile(uid, user, package_name, location,
+                is_secondary_dex);
         // Add to the lists only if both fds are valid.
         if (profile_fd.get() >= 0) {
             profiles_fd->push_back(std::move(profile_fd));
@@ -615,12 +713,12 @@
 
 static void drop_capabilities(uid_t uid) {
     if (setgid(uid) != 0) {
-        ALOGE("setgid(%d) failed in installd during dexopt\n", uid);
-        exit(64);
+        PLOG(ERROR) << "setgid(" << uid << ") failed in installd during dexopt";
+        exit(DexoptReturnCodes::kSetGid);
     }
     if (setuid(uid) != 0) {
-        ALOGE("setuid(%d) failed in installd during dexopt\n", uid);
-        exit(65);
+        PLOG(ERROR) << "setuid(" << uid << ") failed in installd during dexopt";
+        exit(DexoptReturnCodes::kSetUid);
     }
     // drop capabilities
     struct __user_cap_header_struct capheader;
@@ -629,8 +727,8 @@
     memset(&capdata, 0, sizeof(capdata));
     capheader.version = _LINUX_CAPABILITY_VERSION_3;
     if (capset(&capheader, &capdata[0]) < 0) {
-        ALOGE("capset failed: %s\n", strerror(errno));
-        exit(66);
+        PLOG(ERROR) << "capset failed";
+        exit(DexoptReturnCodes::kCapSet);
     }
 }
 
@@ -640,34 +738,89 @@
 static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_IO = 3;
 static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_LOCKING = 4;
 
-static void run_profman_merge(const std::vector<unique_fd>& profiles_fd,
-        const unique_fd& reference_profile_fd) {
-    static const size_t MAX_INT_LEN = 32;
-    static const char* PROFMAN_BIN = "/system/bin/profman";
+[[ noreturn ]]
+static void run_profman(const std::vector<unique_fd>& profile_fds,
+                        const unique_fd& reference_profile_fd,
+                        const std::vector<unique_fd>* apk_fds,
+                        const std::vector<std::string>* dex_locations,
+                        bool copy_and_update) {
+    const char* profman_bin = is_debug_runtime() ? "/system/bin/profmand" : "/system/bin/profman";
 
-    std::vector<std::string> profile_args(profiles_fd.size());
-    char profile_buf[strlen("--profile-file-fd=") + MAX_INT_LEN];
-    for (size_t k = 0; k < profiles_fd.size(); k++) {
-        sprintf(profile_buf, "--profile-file-fd=%d", profiles_fd[k].get());
-        profile_args[k].assign(profile_buf);
+    if (copy_and_update) {
+        CHECK_EQ(1u, profile_fds.size());
+        CHECK(apk_fds != nullptr);
+        CHECK_EQ(1u, apk_fds->size());
     }
-    char reference_profile_arg[strlen("--reference-profile-file-fd=") + MAX_INT_LEN];
-    sprintf(reference_profile_arg, "--reference-profile-file-fd=%d", reference_profile_fd.get());
+    std::vector<std::string> profile_args(profile_fds.size());
+    for (size_t k = 0; k < profile_fds.size(); k++) {
+        profile_args[k] = "--profile-file-fd=" + std::to_string(profile_fds[k].get());
+    }
+    std::string reference_profile_arg = "--reference-profile-file-fd="
+            + std::to_string(reference_profile_fd.get());
+
+    std::vector<std::string> apk_args;
+    if (apk_fds != nullptr) {
+        for (size_t k = 0; k < apk_fds->size(); k++) {
+            apk_args.push_back("--apk-fd=" + std::to_string((*apk_fds)[k].get()));
+        }
+    }
+
+    std::vector<std::string> dex_location_args;
+    if (dex_locations != nullptr) {
+        for (size_t k = 0; k < dex_locations->size(); k++) {
+            dex_location_args.push_back("--dex-location=" + (*dex_locations)[k]);
+        }
+    }
 
     // program name, reference profile fd, the final NULL and the profile fds
-    const char* argv[3 + profiles_fd.size()];
+    const char* argv[3 + profile_args.size() + apk_args.size() + (copy_and_update ? 1 : 0)];
     int i = 0;
-    argv[i++] = PROFMAN_BIN;
-    argv[i++] = reference_profile_arg;
+    argv[i++] = profman_bin;
+    argv[i++] = reference_profile_arg.c_str();
     for (size_t k = 0; k < profile_args.size(); k++) {
         argv[i++] = profile_args[k].c_str();
     }
+    for (size_t k = 0; k < apk_args.size(); k++) {
+        argv[i++] = apk_args[k].c_str();
+    }
+    for (size_t k = 0; k < dex_location_args.size(); k++) {
+        argv[i++] = dex_location_args[k].c_str();
+    }
+    if (copy_and_update) {
+        argv[i++] = "--copy-and-update-profile-key";
+    }
+
     // Do not add after dex2oat_flags, they should override others for debugging.
     argv[i] = NULL;
 
-    execv(PROFMAN_BIN, (char * const *)argv);
-    ALOGE("execv(%s) failed: %s\n", PROFMAN_BIN, strerror(errno));
-    exit(68);   /* only get here on exec failure */
+    execv(profman_bin, (char * const *)argv);
+    PLOG(ERROR) << "execv(" << profman_bin << ") failed";
+    exit(DexoptReturnCodes::kProfmanExec);   /* only get here on exec failure */
+}
+
+[[ noreturn ]]
+static void run_profman_merge(const std::vector<unique_fd>& profiles_fd,
+                              const unique_fd& reference_profile_fd,
+                              const std::vector<unique_fd>* apk_fds = nullptr,
+                              const std::vector<std::string>* dex_locations = nullptr) {
+    run_profman(profiles_fd, reference_profile_fd, apk_fds, dex_locations,
+            /*copy_and_update*/false);
+}
+
+[[ noreturn ]]
+static void run_profman_copy_and_update(unique_fd&& profile_fd,
+                                        unique_fd&& reference_profile_fd,
+                                        unique_fd&& apk_fd,
+                                        const std::string& dex_location) {
+    std::vector<unique_fd> profiles_fd;
+    profiles_fd.push_back(std::move(profile_fd));
+    std::vector<unique_fd> apk_fds;
+    apk_fds.push_back(std::move(apk_fd));
+    std::vector<std::string> dex_locations;
+    dex_locations.push_back(dex_location);
+
+    run_profman(profiles_fd, reference_profile_fd, &apk_fds, &dex_locations,
+            /*copy_and_update*/true);
 }
 
 // Decides if profile guided compilation is needed or not based on existing profiles.
@@ -676,10 +829,12 @@
 // worth to recompile the given location.
 // If the return value is true all the current profiles would have been merged into
 // the reference profiles accessible with open_reference_profile().
-static bool analyze_profiles(uid_t uid, const std::string& location, bool is_secondary_dex) {
+static bool analyze_profiles(uid_t uid, const std::string& package_name,
+        const std::string& location, bool is_secondary_dex) {
     std::vector<unique_fd> profiles_fd;
     unique_fd reference_profile_fd;
-    open_profile_files(uid, location, is_secondary_dex, &profiles_fd, &reference_profile_fd);
+    open_profile_files(uid, package_name, location, is_secondary_dex,
+        &profiles_fd, &reference_profile_fd);
     if (profiles_fd.empty() || (reference_profile_fd.get() < 0)) {
         // Skip profile guided compilation because no profiles were found.
         // Or if the reference profile info couldn't be opened.
@@ -691,7 +846,6 @@
         /* child -- drop privileges before continuing */
         drop_capabilities(uid);
         run_profman_merge(profiles_fd, reference_profile_fd);
-        exit(68);   /* only get here on exec failure */
     }
     /* parent */
     int return_code = wait_child(pid);
@@ -741,13 +895,14 @@
     if (should_clear_current_profiles) {
         if (is_secondary_dex) {
             // For secondary dex files, the owning user is the current user.
-            clear_current_profile(location, multiuser_get_user_id(uid), is_secondary_dex);
+            clear_current_profile(package_name, location, multiuser_get_user_id(uid),
+                    is_secondary_dex);
         } else  {
-            clear_primary_current_profiles(location);
+            clear_primary_current_profiles(package_name, location);
         }
     }
     if (should_clear_reference_profile) {
-        clear_reference_profile(location, is_secondary_dex);
+        clear_reference_profile(package_name, location, is_secondary_dex);
     }
     return need_to_compile;
 }
@@ -758,10 +913,12 @@
 // worth to recompile the package.
 // If the return value is true all the current profiles would have been merged into
 // the reference profiles accessible with open_reference_profile().
-bool analyze_primary_profiles(uid_t uid, const std::string& pkgname) {
-    return analyze_profiles(uid, pkgname, /*is_secondary_dex*/false);
+bool analyze_primary_profiles(uid_t uid, const std::string& package_name,
+        const std::string& profile_name) {
+    return analyze_profiles(uid, package_name, profile_name, /*is_secondary_dex*/false);
 }
 
+[[ noreturn ]]
 static void run_profman_dump(const std::vector<unique_fd>& profile_fds,
                              const unique_fd& reference_profile_fd,
                              const std::vector<std::string>& dex_locations,
@@ -793,16 +950,18 @@
     argv[i] = NULL;
 
     execv(PROFMAN_BIN, (char * const *)argv);
-    ALOGE("execv(%s) failed: %s\n", PROFMAN_BIN, strerror(errno));
-    exit(68);   /* only get here on exec failure */
+    PLOG(ERROR) << "execv(" << PROFMAN_BIN << ") failed";
+    exit(DexoptReturnCodes::kProfmanExec);   /* only get here on exec failure */
 }
 
-bool dump_profiles(int32_t uid, const std::string& pkgname, const char* code_paths) {
+bool dump_profiles(int32_t uid, const std::string& pkgname, const std::string& profile_name,
+        const std::string& code_path) {
     std::vector<unique_fd> profile_fds;
     unique_fd reference_profile_fd;
-    std::string out_file_name = StringPrintf("/data/misc/profman/%s.txt", pkgname.c_str());
+    std::string out_file_name = StringPrintf("/data/misc/profman/%s-%s.txt",
+        pkgname.c_str(), profile_name.c_str());
 
-    open_profile_files(uid, pkgname, /*is_secondary_dex*/false,
+    open_profile_files(uid, pkgname, profile_name, /*is_secondary_dex*/false,
             &profile_fds, &reference_profile_fd);
 
     const bool has_reference_profile = (reference_profile_fd.get() != -1);
@@ -816,22 +975,20 @@
     unique_fd output_fd(open(out_file_name.c_str(),
             O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0644));
     if (fchmod(output_fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0) {
-        ALOGE("installd cannot chmod '%s' dump_profile\n", out_file_name.c_str());
+        LOG(ERROR) << "installd cannot chmod file for dump_profile" << out_file_name;
         return false;
     }
-    std::vector<std::string> code_full_paths = base::Split(code_paths, ";");
+
     std::vector<std::string> dex_locations;
     std::vector<unique_fd> apk_fds;
-    for (const std::string& code_full_path : code_full_paths) {
-        const char* full_path = code_full_path.c_str();
-        unique_fd apk_fd(open(full_path, O_RDONLY | O_NOFOLLOW));
-        if (apk_fd == -1) {
-            ALOGE("installd cannot open '%s'\n", full_path);
-            return false;
-        }
-        dex_locations.push_back(get_location_from_path(full_path));
-        apk_fds.push_back(std::move(apk_fd));
+    unique_fd apk_fd(open(code_path.c_str(), O_RDONLY | O_NOFOLLOW));
+    if (apk_fd == -1) {
+        PLOG(ERROR) << "installd cannot open " << code_path.c_str();
+        return false;
     }
+    dex_locations.push_back(get_location_from_path(code_path.c_str()));
+    apk_fds.push_back(std::move(apk_fd));
+
 
     pid_t pid = fork();
     if (pid == 0) {
@@ -839,7 +996,6 @@
         drop_capabilities(uid);
         run_profman_dump(profile_fds, reference_profile_fd, dex_locations,
                          apk_fds, output_fd);
-        exit(68);   /* only get here on exec failure */
     }
     /* parent */
     int return_code = wait_child(pid);
@@ -852,10 +1008,11 @@
 }
 
 bool copy_system_profile(const std::string& system_profile,
-        uid_t packageUid, const std::string& data_profile_location) {
+        uid_t packageUid, const std::string& package_name, const std::string& profile_name) {
     unique_fd in_fd(open(system_profile.c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
     unique_fd out_fd(open_reference_profile(packageUid,
-                     data_profile_location,
+                     package_name,
+                     profile_name,
                      /*read_write*/ true,
                      /*secondary*/ false));
     if (in_fd.get() < 0) {
@@ -863,7 +1020,7 @@
         return false;
     }
     if (out_fd.get() < 0) {
-        PLOG(WARNING) << "Could not open profile " << data_profile_location;
+        PLOG(WARNING) << "Could not open profile " << package_name;
         return false;
     }
 
@@ -876,7 +1033,7 @@
 
         if (flock(out_fd.get(), LOCK_EX | LOCK_NB) != 0) {
             if (errno != EWOULDBLOCK) {
-                PLOG(WARNING) << "Error locking profile " << data_profile_location;
+                PLOG(WARNING) << "Error locking profile " << package_name;
             }
             // This implies that the app owning this profile is running
             // (and has acquired the lock).
@@ -884,13 +1041,13 @@
             // The app never acquires the lock for the reference profiles of primary apks.
             // Only dex2oat from installd will do that. Since installd is single threaded
             // we should not see this case. Nevertheless be prepared for it.
-            PLOG(WARNING) << "Failed to flock " << data_profile_location;
+            PLOG(WARNING) << "Failed to flock " << package_name;
             return false;
         }
 
         bool truncated = ftruncate(out_fd.get(), 0) == 0;
         if (!truncated) {
-            PLOG(WARNING) << "Could not truncate " << data_profile_location;
+            PLOG(WARNING) << "Could not truncate " << package_name;
         }
 
         // Copy over data.
@@ -904,7 +1061,7 @@
             write(out_fd.get(), buffer, bytes);
         }
         if (flock(out_fd.get(), LOCK_UN) != 0) {
-            PLOG(WARNING) << "Error unlocking profile " << data_profile_location;
+            PLOG(WARNING) << "Error unlocking profile " << package_name;
         }
         // Use _exit since we don't want to run the global destructors in the child.
         // b/62597429
@@ -920,7 +1077,7 @@
   if (EndsWith(oat_path, ".dex")) {
     std::string new_path = oat_path;
     new_path.replace(new_path.length() - strlen(".dex"), strlen(".dex"), new_ext);
-    CHECK(EndsWith(new_path, new_ext.c_str()));
+    CHECK(EndsWith(new_path, new_ext));
     return new_path;
   }
 
@@ -949,14 +1106,6 @@
     return replace_file_extension(oat_path, ".vdex");
 }
 
-static bool add_extension_to_file_name(char* file_name, const char* extension) {
-    if (strlen(file_name) + strlen(extension) + 1 > PKG_PATH_MAX) {
-        return false;
-    }
-    strcat(file_name, extension);
-    return true;
-}
-
 static int open_output_file(const char* file_name, bool recreate, int permissions) {
     int flags = O_RDWR | O_CREAT;
     if (recreate) {
@@ -1135,8 +1284,8 @@
 };
 
 // (re)Creates the app image if needed.
-Dex2oatFileWrapper maybe_open_app_image(const char* out_oat_path, bool profile_guided,
-        bool is_public, int uid, bool is_secondary_dex) {
+Dex2oatFileWrapper maybe_open_app_image(const char* out_oat_path,
+        bool generate_app_image, bool is_public, int uid, bool is_secondary_dex) {
 
     // We don't create an image for secondary dex files.
     if (is_secondary_dex) {
@@ -1149,11 +1298,11 @@
         return Dex2oatFileWrapper();
     }
 
-    // Use app images only if it is enabled (by a set image format) and we are compiling
-    // profile-guided (so the app image doesn't conservatively contain all classes).
-    if (!profile_guided) {
-        // In case there is a stale image, remove it now. Ignore any error.
-        unlink(image_path.c_str());
+    // In case there is a stale image, remove it now. Ignore any error.
+    unlink(image_path.c_str());
+
+    // Not enabled, exit.
+    if (!generate_app_image) {
         return Dex2oatFileWrapper();
     }
     char app_image_format[kPropertyValueMax];
@@ -1192,21 +1341,16 @@
     if (!ShouldUseSwapFileForDexopt()) {
         return invalid_unique_fd();
     }
-    // Make sure there really is enough space.
-    char swap_file_name[PKG_PATH_MAX];
-    strcpy(swap_file_name, out_oat_path);
-    if (!add_extension_to_file_name(swap_file_name, ".swap")) {
-        return invalid_unique_fd();
-    }
+    auto swap_file_name = std::string(out_oat_path) + ".swap";
     unique_fd swap_fd(open_output_file(
-            swap_file_name, /*recreate*/true, /*permissions*/0600));
+            swap_file_name.c_str(), /*recreate*/true, /*permissions*/0600));
     if (swap_fd.get() < 0) {
         // Could not create swap file. Optimistically go on and hope that we can compile
         // without it.
-        ALOGE("installd could not create '%s' for swap during dexopt\n", swap_file_name);
+        ALOGE("installd could not create '%s' for swap during dexopt\n", swap_file_name.c_str());
     } else {
         // Immediately unlink. We don't really want to hit flash.
-        if (unlink(swap_file_name) < 0) {
+        if (unlink(swap_file_name.c_str()) < 0) {
             PLOG(ERROR) << "Couldn't unlink swap file " << swap_file_name;
         }
     }
@@ -1216,26 +1360,49 @@
 // Opens the reference profiles if needed.
 // Note that the reference profile might not exist so it's OK if the fd will be -1.
 Dex2oatFileWrapper maybe_open_reference_profile(const std::string& pkgname,
-        const std::string& dex_path, bool profile_guided, bool is_public, int uid,
-        bool is_secondary_dex) {
-    // Public apps should not be compiled with profile information ever. Same goes for the special
-    // package '*' used for the system server.
-    if (!profile_guided || is_public || (pkgname[0] == '*')) {
+        const std::string& dex_path, const char* profile_name, bool profile_guided,
+        bool is_public, int uid, bool is_secondary_dex) {
+    // If we are not profile guided compilation, or we are compiling system server
+    // do not bother to open the profiles; we won't be using them.
+    if (!profile_guided || (pkgname[0] == '*')) {
+        return Dex2oatFileWrapper();
+    }
+
+    // If this is a secondary dex path which is public do not open the profile.
+    // We cannot compile public secondary dex paths with profiles. That's because
+    // it will expose how the dex files are used by their owner.
+    //
+    // Note that the PackageManager is responsible to set the is_public flag for
+    // primary apks and we do not check it here. In some cases, e.g. when
+    // compiling with a public profile from the .dm file the PackageManager will
+    // set is_public toghether with the profile guided compilation.
+    if (is_secondary_dex && is_public) {
         return Dex2oatFileWrapper();
     }
 
     // Open reference profile in read only mode as dex2oat does not get write permissions.
-    const std::string location = is_secondary_dex ? dex_path : pkgname;
-    unique_fd ufd = open_reference_profile(uid, location, /*read_write*/false, is_secondary_dex);
-    const auto& cleanup = [location, is_secondary_dex]() {
-        clear_reference_profile(location.c_str(), is_secondary_dex);
+    std::string location;
+    if (is_secondary_dex) {
+        location = dex_path;
+    } else {
+        if (profile_name == nullptr) {
+            // This path is taken for system server re-compilation lunched from ZygoteInit.
+            return Dex2oatFileWrapper();
+        } else {
+            location = profile_name;
+        }
+    }
+    unique_fd ufd = open_reference_profile(uid, pkgname, location, /*read_write*/false,
+            is_secondary_dex);
+    const auto& cleanup = [pkgname, location, is_secondary_dex]() {
+        clear_reference_profile(pkgname, location, is_secondary_dex);
     };
     return Dex2oatFileWrapper(ufd.release(), cleanup);
 }
 
 // Opens the vdex files and assigns the input fd to in_vdex_wrapper_fd and the output fd to
 // out_vdex_wrapper_fd. Returns true for success or false in case of errors.
-bool open_vdex_files(const char* apk_path, const char* out_oat_path, int dexopt_needed,
+bool open_vdex_files_for_dex2oat(const char* apk_path, const char* out_oat_path, int dexopt_needed,
         const char* instruction_set, bool is_public, int uid, bool is_secondary_dex,
         bool profile_guided, Dex2oatFileWrapper* in_vdex_wrapper_fd,
         Dex2oatFileWrapper* out_vdex_wrapper_fd) {
@@ -1348,6 +1515,39 @@
     return wrapper_fd;
 }
 
+// Creates RDONLY fds for oat and vdex files, if exist.
+// Returns false if it fails to create oat out path for the given apk path.
+// Note that the method returns true even if the files could not be opened.
+bool maybe_open_oat_and_vdex_file(const std::string& apk_path,
+                                  const std::string& oat_dir,
+                                  const std::string& instruction_set,
+                                  bool is_secondary_dex,
+                                  unique_fd* oat_file_fd,
+                                  unique_fd* vdex_file_fd) {
+    char oat_path[PKG_PATH_MAX];
+    if (!create_oat_out_path(apk_path.c_str(),
+                             instruction_set.c_str(),
+                             oat_dir.c_str(),
+                             is_secondary_dex,
+                             oat_path)) {
+        LOG(ERROR) << "Could not create oat out path for "
+                << apk_path << " with oat dir " << oat_dir;
+        return false;
+    }
+    oat_file_fd->reset(open(oat_path, O_RDONLY));
+    if (oat_file_fd->get() < 0) {
+        PLOG(INFO) << "installd cannot open oat file during dexopt" <<  oat_path;
+    }
+
+    std::string vdex_filename = create_vdex_filename(oat_path);
+    vdex_file_fd->reset(open(vdex_filename.c_str(), O_RDONLY));
+    if (vdex_file_fd->get() < 0) {
+        PLOG(INFO) << "installd cannot open vdex file during dexopt" <<  vdex_filename;
+    }
+
+    return true;
+}
+
 // Updates the access times of out_oat_path based on those from apk_path.
 void update_out_oat_access_times(const char* apk_path, const char* out_oat_path) {
     struct stat input_stat;
@@ -1369,9 +1569,15 @@
 // The analyzer will check if the dex_file needs to be (re)compiled to match the compiler_filter.
 // If this is for a profile guided compilation, profile_was_updated will tell whether or not
 // the profile has changed.
-static void exec_dexoptanalyzer(const std::string& dex_file, const std::string& instruction_set,
-        const std::string& compiler_filter, bool profile_was_updated, bool downgrade) {
-    static const char* DEXOPTANALYZER_BIN = "/system/bin/dexoptanalyzer";
+static void exec_dexoptanalyzer(const std::string& dex_file, int vdex_fd, int oat_fd,
+        int zip_fd, const std::string& instruction_set, const std::string& compiler_filter,
+        bool profile_was_updated, bool downgrade,
+        const char* class_loader_context) {
+    CHECK_GE(zip_fd, 0);
+    const char* dexoptanalyzer_bin =
+            is_debug_runtime()
+                    ? "/system/bin/dexoptanalyzerd"
+                    : "/system/bin/dexoptanalyzer";
     static const unsigned int MAX_INSTRUCTION_SET_LEN = 7;
 
     if (instruction_set.size() >= MAX_INSTRUCTION_SET_LEN) {
@@ -1381,36 +1587,56 @@
     }
 
     std::string dex_file_arg = "--dex-file=" + dex_file;
+    std::string oat_fd_arg = "--oat-fd=" + std::to_string(oat_fd);
+    std::string vdex_fd_arg = "--vdex-fd=" + std::to_string(vdex_fd);
+    std::string zip_fd_arg = "--zip-fd=" + std::to_string(zip_fd);
     std::string isa_arg = "--isa=" + instruction_set;
     std::string compiler_filter_arg = "--compiler-filter=" + compiler_filter;
     const char* assume_profile_changed = "--assume-profile-changed";
     const char* downgrade_flag = "--downgrade";
+    std::string class_loader_context_arg = "--class-loader-context=";
+    if (class_loader_context != nullptr) {
+        class_loader_context_arg += class_loader_context;
+    }
 
     // program name, dex file, isa, filter, the final NULL
-    const int argc = 5 +
+    const int argc = 6 +
         (profile_was_updated ? 1 : 0) +
-        (downgrade ? 1 : 0);
+        (vdex_fd >= 0 ? 1 : 0) +
+        (oat_fd >= 0 ? 1 : 0) +
+        (downgrade ? 1 : 0) +
+        (class_loader_context != nullptr ? 1 : 0);
     const char* argv[argc];
     int i = 0;
-    argv[i++] = DEXOPTANALYZER_BIN;
+    argv[i++] = dexoptanalyzer_bin;
     argv[i++] = dex_file_arg.c_str();
     argv[i++] = isa_arg.c_str();
     argv[i++] = compiler_filter_arg.c_str();
+    if (oat_fd >= 0) {
+        argv[i++] = oat_fd_arg.c_str();
+    }
+    if (vdex_fd >= 0) {
+        argv[i++] = vdex_fd_arg.c_str();
+    }
+    argv[i++] = zip_fd_arg.c_str();
     if (profile_was_updated) {
         argv[i++] = assume_profile_changed;
     }
     if (downgrade) {
         argv[i++] = downgrade_flag;
     }
+    if (class_loader_context != nullptr) {
+        argv[i++] = class_loader_context_arg.c_str();
+    }
     argv[i] = NULL;
 
-    execv(DEXOPTANALYZER_BIN, (char * const *)argv);
-    ALOGE("execv(%s) failed: %s\n", DEXOPTANALYZER_BIN, strerror(errno));
+    execv(dexoptanalyzer_bin, (char * const *)argv);
+    ALOGE("execv(%s) failed: %s\n", dexoptanalyzer_bin, strerror(errno));
 }
 
 // Prepares the oat dir for the secondary dex files.
 static bool prepare_secondary_dex_oat_dir(const std::string& dex_path, int uid,
-        const char* instruction_set, std::string* oat_dir_out) {
+        const char* instruction_set) {
     unsigned long dirIndex = dex_path.rfind('/');
     if (dirIndex == std::string::npos) {
         LOG(ERROR ) << "Unexpected dir structure for secondary dex " << dex_path;
@@ -1427,10 +1653,8 @@
 
     char oat_dir[PKG_PATH_MAX];
     snprintf(oat_dir, PKG_PATH_MAX, "%s/oat", dex_dir.c_str());
-    oat_dir_out->assign(oat_dir);
 
-    // Create oat/isa output directory.
-    if (prepare_app_cache_dir(*oat_dir_out, instruction_set, oat_dir_mode, uid, uid) != 0) {
+    if (prepare_app_cache_dir(oat_dir, instruction_set, oat_dir_mode, uid, uid) != 0) {
         LOG(ERROR) << "Could not prepare oat/isa dir for secondary dex: " << dex_path;
         return false;
     }
@@ -1438,37 +1662,156 @@
     return true;
 }
 
-static int constexpr DEXOPTANALYZER_BIN_EXEC_ERROR = 200;
+// Return codes for identifying the reason why dexoptanalyzer was not invoked when processing
+// secondary dex files. This return codes are returned by the child process created for
+// analyzing secondary dex files in process_secondary_dex_dexopt.
 
-// Verifies the result of dexoptanalyzer executed for the apk_path.
+enum DexoptAnalyzerSkipCodes {
+  // The dexoptanalyzer was not invoked because of validation or IO errors.
+  // Specific errors are encoded in the name.
+  kSecondaryDexDexoptAnalyzerSkippedValidatePath = 200,
+  kSecondaryDexDexoptAnalyzerSkippedOpenZip = 201,
+  kSecondaryDexDexoptAnalyzerSkippedPrepareDir = 202,
+  kSecondaryDexDexoptAnalyzerSkippedOpenOutput = 203,
+  kSecondaryDexDexoptAnalyzerSkippedFailExec = 204,
+  // The dexoptanalyzer was not invoked because the dex file does not exist anymore.
+  kSecondaryDexDexoptAnalyzerSkippedNoFile = 205,
+};
+
+// Verifies the result of analyzing secondary dex files from process_secondary_dex_dexopt.
 // If the result is valid returns true and sets dexopt_needed_out to a valid value.
 // Returns false for errors or unexpected result values.
-static bool process_dexoptanalyzer_result(const std::string& dex_path, int result,
-            int* dexopt_needed_out) {
+// The result is expected to be either one of SECONDARY_DEX_* codes or a valid exit code
+// of dexoptanalyzer.
+static bool process_secondary_dexoptanalyzer_result(const std::string& dex_path, int result,
+            int* dexopt_needed_out, std::string* error_msg) {
     // The result values are defined in dexoptanalyzer.
     switch (result) {
-        case 0:  // no_dexopt_needed
+        case 0:  // dexoptanalyzer: no_dexopt_needed
             *dexopt_needed_out = NO_DEXOPT_NEEDED; return true;
-        case 1:  // dex2oat_from_scratch
+        case 1:  // dexoptanalyzer: dex2oat_from_scratch
             *dexopt_needed_out = DEX2OAT_FROM_SCRATCH; return true;
-        case 5:  // dex2oat_for_bootimage_odex
+        case 5:  // dexoptanalyzer: dex2oat_for_bootimage_odex
             *dexopt_needed_out = -DEX2OAT_FOR_BOOT_IMAGE; return true;
-        case 6:  // dex2oat_for_filter_odex
+        case 6:  // dexoptanalyzer: dex2oat_for_filter_odex
             *dexopt_needed_out = -DEX2OAT_FOR_FILTER; return true;
-        case 7:  // dex2oat_for_relocation_odex
+        case 7:  // dexoptanalyzer: dex2oat_for_relocation_odex
             *dexopt_needed_out = -DEX2OAT_FOR_RELOCATION; return true;
-        case 2:  // dex2oat_for_bootimage_oat
-        case 3:  // dex2oat_for_filter_oat
-        case 4:  // dex2oat_for_relocation_oat
-            LOG(ERROR) << "Dexoptnalyzer return the status of an oat file."
-                    << " Expected odex file status for secondary dex " << dex_path
-                    << " : dexoptanalyzer result=" << result;
-            return false;
-        default:
-            LOG(ERROR) << "Unexpected result for dexoptanalyzer " << dex_path
-                    << " exec_dexoptanalyzer result=" << result;
+        case 2:  // dexoptanalyzer: dex2oat_for_bootimage_oat
+        case 3:  // dexoptanalyzer: dex2oat_for_filter_oat
+        case 4:  // dexoptanalyzer: dex2oat_for_relocation_oat
+            *error_msg = StringPrintf("Dexoptanalyzer return the status of an oat file."
+                                      " Expected odex file status for secondary dex %s"
+                                      " : dexoptanalyzer result=%d",
+                                      dex_path.c_str(),
+                                      result);
             return false;
     }
+
+    // Use a second switch for enum switch-case analysis.
+    switch (static_cast<DexoptAnalyzerSkipCodes>(result)) {
+        case kSecondaryDexDexoptAnalyzerSkippedNoFile:
+            // If the file does not exist there's no need for dexopt.
+            *dexopt_needed_out = NO_DEXOPT_NEEDED;
+            return true;
+
+        case kSecondaryDexDexoptAnalyzerSkippedValidatePath:
+            *error_msg = "Dexoptanalyzer path validation failed";
+            return false;
+        case kSecondaryDexDexoptAnalyzerSkippedOpenZip:
+            *error_msg = "Dexoptanalyzer open zip failed";
+            return false;
+        case kSecondaryDexDexoptAnalyzerSkippedPrepareDir:
+            *error_msg = "Dexoptanalyzer dir preparation failed";
+            return false;
+        case kSecondaryDexDexoptAnalyzerSkippedOpenOutput:
+            *error_msg = "Dexoptanalyzer open output failed";
+            return false;
+        case kSecondaryDexDexoptAnalyzerSkippedFailExec:
+            *error_msg = "Dexoptanalyzer failed to execute";
+            return false;
+    }
+
+    *error_msg = StringPrintf("Unexpected result from analyzing secondary dex %s result=%d",
+                              dex_path.c_str(),
+                              result);
+    return false;
+}
+
+enum SecondaryDexAccess {
+    kSecondaryDexAccessReadOk = 0,
+    kSecondaryDexAccessDoesNotExist = 1,
+    kSecondaryDexAccessPermissionError = 2,
+    kSecondaryDexAccessIOError = 3
+};
+
+static SecondaryDexAccess check_secondary_dex_access(const std::string& dex_path) {
+    // Check if the path exists and can be read. If not, there's nothing to do.
+    if (access(dex_path.c_str(), R_OK) == 0) {
+        return kSecondaryDexAccessReadOk;
+    } else {
+        if (errno == ENOENT) {
+            LOG(INFO) << "Secondary dex does not exist: " <<  dex_path;
+            return kSecondaryDexAccessDoesNotExist;
+        } else {
+            PLOG(ERROR) << "Could not access secondary dex " << dex_path;
+            return errno == EACCES
+                ? kSecondaryDexAccessPermissionError
+                : kSecondaryDexAccessIOError;
+        }
+    }
+}
+
+static bool is_file_public(const std::string& filename) {
+    struct stat file_stat;
+    if (stat(filename.c_str(), &file_stat) == 0) {
+        return (file_stat.st_mode & S_IROTH) != 0;
+    }
+    return false;
+}
+
+// Create the oat file structure for the secondary dex 'dex_path' and assign
+// the individual path component to the 'out_' parameters.
+static bool create_secondary_dex_oat_layout(const std::string& dex_path, const std::string& isa,
+        char* out_oat_dir, char* out_oat_isa_dir, char* out_oat_path, std::string* error_msg) {
+    size_t dirIndex = dex_path.rfind('/');
+    if (dirIndex == std::string::npos) {
+        *error_msg = std::string("Unexpected dir structure for dex file ").append(dex_path);
+        return false;
+    }
+    // TODO(calin): we have similar computations in at lest 3 other places
+    // (InstalldNativeService, otapropt and dexopt). Unify them and get rid of snprintf by
+    // using string append.
+    std::string apk_dir = dex_path.substr(0, dirIndex);
+    snprintf(out_oat_dir, PKG_PATH_MAX, "%s/oat", apk_dir.c_str());
+    snprintf(out_oat_isa_dir, PKG_PATH_MAX, "%s/%s", out_oat_dir, isa.c_str());
+
+    if (!create_oat_out_path(dex_path.c_str(), isa.c_str(), out_oat_dir,
+            /*is_secondary_dex*/true, out_oat_path)) {
+        *error_msg = std::string("Could not create oat path for secondary dex ").append(dex_path);
+        return false;
+    }
+    return true;
+}
+
+// Validate that the dexopt_flags contain a valid storage flag and convert that to an installd
+// recognized storage flags (FLAG_STORAGE_CE or FLAG_STORAGE_DE).
+static bool validate_dexopt_storage_flags(int dexopt_flags,
+                                          int* out_storage_flag,
+                                          std::string* error_msg) {
+    if ((dexopt_flags & DEXOPT_STORAGE_CE) != 0) {
+        *out_storage_flag = FLAG_STORAGE_CE;
+        if ((dexopt_flags & DEXOPT_STORAGE_DE) != 0) {
+            *error_msg = "Ambiguous secondary dex storage flag. Both, CE and DE, flags are set";
+            return false;
+        }
+    } else if ((dexopt_flags & DEXOPT_STORAGE_DE) != 0) {
+        *out_storage_flag = FLAG_STORAGE_DE;
+    } else {
+        *error_msg = "Secondary dex storage flag must be set";
+        return false;
+    }
+    return true;
 }
 
 // Processes the dex_path as a secondary dex files and return true if the path dex file should
@@ -1478,121 +1821,153 @@
 //   - is_public_out: whether or not the oat file should not be made public
 //   - dexopt_needed_out: valid OatFileAsssitant::DexOptNeeded
 //   - oat_dir_out: the oat dir path where the oat file should be stored
-//   - dex_path_out: the real path of the dex file
-static bool process_secondary_dex_dexopt(const char* original_dex_path, const char* pkgname,
+static bool process_secondary_dex_dexopt(const std::string& dex_path, const char* pkgname,
         int dexopt_flags, const char* volume_uuid, int uid, const char* instruction_set,
         const char* compiler_filter, bool* is_public_out, int* dexopt_needed_out,
-        std::string* oat_dir_out, std::string* dex_path_out, bool downgrade) {
+        std::string* oat_dir_out, bool downgrade, const char* class_loader_context,
+        /* out */ std::string* error_msg) {
+    LOG(DEBUG) << "Processing secondary dex path " << dex_path;
     int storage_flag;
-
-    if ((dexopt_flags & DEXOPT_STORAGE_CE) != 0) {
-        storage_flag = FLAG_STORAGE_CE;
-        if ((dexopt_flags & DEXOPT_STORAGE_DE) != 0) {
-            LOG(ERROR) << "Ambiguous secondary dex storage flag. Both, CE and DE, flags are set";
-            return false;
-        }
-    } else if ((dexopt_flags & DEXOPT_STORAGE_DE) != 0) {
-        storage_flag = FLAG_STORAGE_DE;
-    } else {
-        LOG(ERROR) << "Secondary dex storage flag must be set";
+    if (!validate_dexopt_storage_flags(dexopt_flags, &storage_flag, error_msg)) {
+        LOG(ERROR) << *error_msg;
         return false;
     }
-
-    {
-        // As opposed to the primary apk, secondary dex files might contain symlinks.
-        // Resolve the path before passing it to the validate method to
-        // make sure the verification is done on the real location.
-        UniqueCPtr<char> dex_real_path_cstr(realpath(original_dex_path, nullptr));
-        if (dex_real_path_cstr == nullptr) {
-            PLOG(ERROR) << "Could not get the real path of the secondary dex file "
-                    << original_dex_path;
-            return false;
-        } else {
-            dex_path_out->assign(dex_real_path_cstr.get());
-        }
-    }
-    const std::string& dex_path = *dex_path_out;
-    if (!validate_dex_path_size(dex_path)) {
+    // Compute the oat dir as it's not easy to extract it from the child computation.
+    char oat_path[PKG_PATH_MAX];
+    char oat_dir[PKG_PATH_MAX];
+    char oat_isa_dir[PKG_PATH_MAX];
+    if (!create_secondary_dex_oat_layout(
+            dex_path, instruction_set, oat_dir, oat_isa_dir, oat_path, error_msg)) {
+        LOG(ERROR) << "Could not create secondary odex layout: " << *error_msg;
         return false;
     }
-    if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid, uid, storage_flag)) {
-        LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
-        return false;
-    }
-
-    // Check if the path exist. If not, there's nothing to do.
-    struct stat dex_path_stat;
-    if (stat(dex_path.c_str(), &dex_path_stat) != 0) {
-        if (errno == ENOENT) {
-            // Secondary dex files might be deleted any time by the app.
-            // Nothing to do if that's the case
-            ALOGV("Secondary dex does not exist %s", dex_path.c_str());
-            return NO_DEXOPT_NEEDED;
-        } else {
-            PLOG(ERROR) << "Could not access secondary dex " << dex_path;
-        }
-    }
-
-    // Check if we should make the oat file public.
-    // Note that if the dex file is not public the compiled code cannot be made public.
-    *is_public_out = ((dexopt_flags & DEXOPT_PUBLIC) != 0) &&
-            ((dex_path_stat.st_mode & S_IROTH) != 0);
-
-    // Prepare the oat directories.
-    if (!prepare_secondary_dex_oat_dir(dex_path, uid, instruction_set, oat_dir_out)) {
-        return false;
-    }
-
-    // Analyze profiles.
-    bool profile_was_updated = analyze_profiles(uid, dex_path, /*is_secondary_dex*/true);
+    oat_dir_out->assign(oat_dir);
 
     pid_t pid = fork();
     if (pid == 0) {
         // child -- drop privileges before continuing.
         drop_capabilities(uid);
-        // Run dexoptanalyzer to get dexopt_needed code.
-        exec_dexoptanalyzer(dex_path, instruction_set, compiler_filter, profile_was_updated,
-                            downgrade);
-        exit(DEXOPTANALYZER_BIN_EXEC_ERROR);
+
+        // Validate the path structure.
+        if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid, uid, storage_flag)) {
+            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            _exit(kSecondaryDexDexoptAnalyzerSkippedValidatePath);
+        }
+
+        // Open the dex file.
+        unique_fd zip_fd;
+        zip_fd.reset(open(dex_path.c_str(), O_RDONLY));
+        if (zip_fd.get() < 0) {
+            if (errno == ENOENT) {
+                _exit(kSecondaryDexDexoptAnalyzerSkippedNoFile);
+            } else {
+                _exit(kSecondaryDexDexoptAnalyzerSkippedOpenZip);
+            }
+        }
+
+        // Prepare the oat directories.
+        if (!prepare_secondary_dex_oat_dir(dex_path, uid, instruction_set)) {
+            _exit(kSecondaryDexDexoptAnalyzerSkippedPrepareDir);
+        }
+
+        // Open the vdex/oat files if any.
+        unique_fd oat_file_fd;
+        unique_fd vdex_file_fd;
+        if (!maybe_open_oat_and_vdex_file(dex_path,
+                                          *oat_dir_out,
+                                          instruction_set,
+                                          true /* is_secondary_dex */,
+                                          &oat_file_fd,
+                                          &vdex_file_fd)) {
+            _exit(kSecondaryDexDexoptAnalyzerSkippedOpenOutput);
+        }
+
+        // Analyze profiles.
+        bool profile_was_updated = analyze_profiles(uid, pkgname, dex_path,
+                /*is_secondary_dex*/true);
+
+        // Run dexoptanalyzer to get dexopt_needed code. This is not expected to return.
+        exec_dexoptanalyzer(dex_path,
+                            vdex_file_fd.get(),
+                            oat_file_fd.get(),
+                            zip_fd.get(),
+                            instruction_set,
+                            compiler_filter, profile_was_updated,
+                            downgrade,
+                            class_loader_context);
+        PLOG(ERROR) << "Failed to exec dexoptanalyzer";
+        _exit(kSecondaryDexDexoptAnalyzerSkippedFailExec);
     }
 
     /* parent */
-
     int result = wait_child(pid);
     if (!WIFEXITED(result)) {
-        LOG(ERROR) << "dexoptanalyzer failed for path " << dex_path << ": " << result;
+        *error_msg = StringPrintf("dexoptanalyzer failed for path %s: 0x%04x",
+                                  dex_path.c_str(),
+                                  result);
+        LOG(ERROR) << *error_msg;
         return false;
     }
     result = WEXITSTATUS(result);
-    bool success = process_dexoptanalyzer_result(dex_path, result, dexopt_needed_out);
+    // Check that we successfully executed dexoptanalyzer.
+    bool success = process_secondary_dexoptanalyzer_result(dex_path,
+                                                           result,
+                                                           dexopt_needed_out,
+                                                           error_msg);
+    if (!success) {
+        LOG(ERROR) << *error_msg;
+    }
+
+    LOG(DEBUG) << "Processed secondary dex file " << dex_path << " result=" << result;
+
     // Run dexopt only if needed or forced.
-    // Note that dexoptanalyzer is executed even if force compilation is enabled.
-    // We ignore its valid dexopNeeded result, but still check (in process_dexoptanalyzer_result)
-    // that we only get results for odex files (apk_dir/oat/isa/code.odex) and not
-    // for oat files from dalvik-cache.
-    if (success && ((dexopt_flags & DEXOPT_FORCE) != 0)) {
+    // Note that dexoptanalyzer is executed even if force compilation is enabled (because it
+    // makes the code simpler; force compilation is only needed during tests).
+    if (success &&
+        (result != kSecondaryDexDexoptAnalyzerSkippedNoFile) &&
+        ((dexopt_flags & DEXOPT_FORCE) != 0)) {
         *dexopt_needed_out = DEX2OAT_FROM_SCRATCH;
     }
 
+    // Check if we should make the oat file public.
+    // Note that if the dex file is not public the compiled code cannot be made public.
+    // It is ok to check this flag outside in the parent process.
+    *is_public_out = ((dexopt_flags & DEXOPT_PUBLIC) != 0) && is_file_public(dex_path);
+
     return success;
 }
 
+static std::string format_dexopt_error(int status, const char* dex_path) {
+  if (WIFEXITED(status)) {
+    int int_code = WEXITSTATUS(status);
+    const char* code_name = get_return_code_name(static_cast<DexoptReturnCodes>(int_code));
+    if (code_name != nullptr) {
+      return StringPrintf("Dex2oat invocation for %s failed: %s", dex_path, code_name);
+    }
+  }
+  return StringPrintf("Dex2oat invocation for %s failed with 0x%04x", dex_path, status);
+}
+
 int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* instruction_set,
         int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
         const char* volume_uuid, const char* class_loader_context, const char* se_info,
-        bool downgrade) {
+        bool downgrade, int target_sdk_version, const char* profile_name,
+        const char* dex_metadata_path, const char* compilation_reason, std::string* error_msg) {
     CHECK(pkgname != nullptr);
     CHECK(pkgname[0] != 0);
-    if ((dexopt_flags & ~DEXOPT_MASK) != 0) {
-        LOG_FATAL("dexopt flags contains unknown fields\n");
-    }
+    CHECK(error_msg != nullptr);
+    CHECK_EQ(dexopt_flags & ~DEXOPT_MASK, 0)
+        << "dexopt flags contains unknown fields: " << dexopt_flags;
 
     if (!validate_dex_path_size(dex_path)) {
+        *error_msg = StringPrintf("Failed to validate %s", dex_path);
         return -1;
     }
 
     if (class_loader_context != nullptr && strlen(class_loader_context) > PKG_PATH_MAX) {
-        LOG(ERROR) << "Class loader context exceeds the allowed size: " << class_loader_context;
+        *error_msg = StringPrintf("Class loader context exceeds the allowed size: %s",
+                                  class_loader_context);
+        LOG(ERROR) << *error_msg;
         return -1;
     }
 
@@ -1601,21 +1976,25 @@
     bool boot_complete = (dexopt_flags & DEXOPT_BOOTCOMPLETE) != 0;
     bool profile_guided = (dexopt_flags & DEXOPT_PROFILE_GUIDED) != 0;
     bool is_secondary_dex = (dexopt_flags & DEXOPT_SECONDARY_DEX) != 0;
+    bool background_job_compile = (dexopt_flags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
+    bool enable_hidden_api_checks = (dexopt_flags & DEXOPT_ENABLE_HIDDEN_API_CHECKS) != 0;
+    bool generate_compact_dex = (dexopt_flags & DEXOPT_GENERATE_COMPACT_DEX) != 0;
+    bool generate_app_image = (dexopt_flags & DEXOPT_GENERATE_APP_IMAGE) != 0;
 
     // Check if we're dealing with a secondary dex file and if we need to compile it.
     std::string oat_dir_str;
-    std::string dex_real_path;
     if (is_secondary_dex) {
         if (process_secondary_dex_dexopt(dex_path, pkgname, dexopt_flags, volume_uuid, uid,
                 instruction_set, compiler_filter, &is_public, &dexopt_needed, &oat_dir_str,
-                &dex_real_path,
-                downgrade)) {
+                downgrade, class_loader_context, error_msg)) {
             oat_dir = oat_dir_str.c_str();
-            dex_path = dex_real_path.c_str();
             if (dexopt_needed == NO_DEXOPT_NEEDED) {
                 return 0;  // Nothing to do, report success.
             }
         } else {
+            if (error_msg->empty()) {  // TODO: Make this a CHECK.
+                *error_msg = "Failed processing secondary.";
+            }
             return -1;  // We had an error, logged in the process method.
         }
     } else {
@@ -1628,7 +2007,8 @@
     // Open the input file.
     unique_fd input_fd(open(dex_path, O_RDONLY, 0));
     if (input_fd.get() < 0) {
-        ALOGE("installd cannot open '%s' for input during dexopt\n", dex_path);
+        *error_msg = StringPrintf("installd cannot open '%s' for input during dexopt", dex_path);
+        LOG(ERROR) << *error_msg;
         return -1;
     }
 
@@ -1637,14 +2017,16 @@
     Dex2oatFileWrapper out_oat_fd = open_oat_out_file(dex_path, oat_dir, is_public, uid,
             instruction_set, is_secondary_dex, out_oat_path);
     if (out_oat_fd.get() < 0) {
+        *error_msg = "Could not open out oat file.";
         return -1;
     }
 
     // Open vdex files.
     Dex2oatFileWrapper in_vdex_fd;
     Dex2oatFileWrapper out_vdex_fd;
-    if (!open_vdex_files(dex_path, out_oat_path, dexopt_needed, instruction_set, is_public, uid,
-            is_secondary_dex, profile_guided, &in_vdex_fd, &out_vdex_fd)) {
+    if (!open_vdex_files_for_dex2oat(dex_path, out_oat_path, dexopt_needed, instruction_set,
+            is_public, uid, is_secondary_dex, profile_guided, &in_vdex_fd, &out_vdex_fd)) {
+        *error_msg = "Could not open vdex files.";
         return -1;
     }
 
@@ -1656,7 +2038,8 @@
     if (is_secondary_dex) {
         if (selinux_android_restorecon_pkgdir(oat_dir, se_info, uid,
                 SELINUX_ANDROID_RESTORECON_RECURSE)) {
-            LOG(ERROR) << "Failed to restorecon " << oat_dir;
+            *error_msg = std::string("Failed to restorecon ").append(oat_dir);
+            LOG(ERROR) << *error_msg;
             return -1;
         }
     }
@@ -1665,14 +2048,22 @@
     unique_fd swap_fd = maybe_open_dexopt_swap_file(out_oat_path);
 
     // Create the app image file if needed.
-    Dex2oatFileWrapper image_fd =
-            maybe_open_app_image(out_oat_path, profile_guided, is_public, uid, is_secondary_dex);
+    Dex2oatFileWrapper image_fd = maybe_open_app_image(
+            out_oat_path, generate_app_image, is_public, uid, is_secondary_dex);
 
     // Open the reference profile if needed.
     Dex2oatFileWrapper reference_profile_fd = maybe_open_reference_profile(
-            pkgname, dex_path, profile_guided, is_public, uid, is_secondary_dex);
+            pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex);
 
-    ALOGV("DexInv: --- BEGIN '%s' ---\n", dex_path);
+    unique_fd dex_metadata_fd;
+    if (dex_metadata_path != nullptr) {
+        dex_metadata_fd.reset(TEMP_FAILURE_RETRY(open(dex_metadata_path, O_RDONLY | O_NOFOLLOW)));
+        if (dex_metadata_fd.get() < 0) {
+            PLOG(ERROR) << "Failed to open dex metadata file " << dex_metadata_path;
+        }
+    }
+
+    LOG(VERBOSE) << "DexInv: --- BEGIN '" << dex_path << "' ---";
 
     pid_t pid = fork();
     if (pid == 0) {
@@ -1681,8 +2072,8 @@
 
         SetDex2OatScheduling(boot_complete);
         if (flock(out_oat_fd.get(), LOCK_EX | LOCK_NB) != 0) {
-            ALOGE("flock(%s) failed: %s\n", out_oat_path, strerror(errno));
-            _exit(67);
+            PLOG(ERROR) << "flock(" << out_oat_path << ") failed";
+            _exit(DexoptReturnCodes::kFlock);
         }
 
         run_dex2oat(input_fd.get(),
@@ -1697,15 +2088,22 @@
                     compiler_filter,
                     debuggable,
                     boot_complete,
+                    background_job_compile,
                     reference_profile_fd.get(),
-                    class_loader_context);
-        _exit(68);   /* only get here on exec failure */
+                    class_loader_context,
+                    target_sdk_version,
+                    enable_hidden_api_checks,
+                    generate_compact_dex,
+                    dex_metadata_fd.get(),
+                    compilation_reason);
     } else {
         int res = wait_child(pid);
         if (res == 0) {
-            ALOGV("DexInv: --- END '%s' (success) ---\n", dex_path);
+            LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' (success) ---";
         } else {
-            ALOGE("DexInv: --- END '%s' --- status=0x%04x, process failed\n", dex_path, res);
+            LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- status=0x"
+                         << std::hex << std::setw(4) << res << ", process failed";
+            *error_msg = format_dexopt_error(res, dex_path);
             return res;
         }
     }
@@ -1748,29 +2146,13 @@
     return false;
 }
 
-// Create the oat file structure for the secondary dex 'dex_path' and assign
-// the individual path component to the 'out_' parameters.
-static bool create_secondary_dex_oat_layout(const std::string& dex_path, const std::string& isa,
-        /*out*/char* out_oat_dir, /*out*/char* out_oat_isa_dir, /*out*/char* out_oat_path) {
-    size_t dirIndex = dex_path.rfind('/');
-    if (dirIndex == std::string::npos) {
-        LOG(ERROR) << "Unexpected dir structure for dex file " << dex_path;
-        return false;
-    }
-    // TODO(calin): we have similar computations in at lest 3 other places
-    // (InstalldNativeService, otapropt and dexopt). Unify them and get rid of snprintf by
-    // use string append.
-    std::string apk_dir = dex_path.substr(0, dirIndex);
-    snprintf(out_oat_dir, PKG_PATH_MAX, "%s/oat", apk_dir.c_str());
-    snprintf(out_oat_isa_dir, PKG_PATH_MAX, "%s/%s", out_oat_dir, isa.c_str());
-
-    if (!create_oat_out_path(dex_path.c_str(), isa.c_str(), out_oat_dir,
-            /*is_secondary_dex*/true, out_oat_path)) {
-        LOG(ERROR) << "Could not create oat path for secondary dex " << dex_path;
-        return false;
-    }
-    return true;
-}
+enum ReconcileSecondaryDexResult {
+    kReconcileSecondaryDexExists = 0,
+    kReconcileSecondaryDexCleanedUp = 1,
+    kReconcileSecondaryDexValidationError = 2,
+    kReconcileSecondaryDexCleanUpError = 3,
+    kReconcileSecondaryDexAccessIOError = 4,
+};
 
 // Reconcile the secondary dex 'dex_path' and its generated oat files.
 // Return true if all the parameters are valid and the secondary dex file was
@@ -1784,36 +2166,15 @@
         const std::string& pkgname, int uid, const std::vector<std::string>& isas,
         const std::unique_ptr<std::string>& volume_uuid, int storage_flag,
         /*out*/bool* out_secondary_dex_exists) {
-    // Set out to false to start with, just in case we have validation errors.
-    *out_secondary_dex_exists = false;
-    if (!validate_dex_path_size(dex_path)) {
-        return false;
-    }
-
+    *out_secondary_dex_exists = false;  // start by assuming the file does not exist.
     if (isas.size() == 0) {
         LOG(ERROR) << "reconcile_secondary_dex_file called with empty isas vector";
         return false;
     }
 
-    const char* volume_uuid_cstr = volume_uuid == nullptr ? nullptr : volume_uuid->c_str();
-
-    // Note that we cannot validate the package path here because the file might not exist
-    // and we cannot call realpath to resolve system symlinks. Since /data/user/0 symlinks to
-    // /data/data/ a lot of validations will fail if we attempt to check the package path.
-    // It is still ok to be more relaxed because any file removal is done after forking and
-    // dropping capabilities.
-    if (!validate_secondary_dex_path(pkgname.c_str(), dex_path.c_str(), volume_uuid_cstr,
-            uid, storage_flag, /*validate_package_path*/ false)) {
-        LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
-        return false;
-    }
-
-    if (access(dex_path.c_str(), F_OK) == 0) {
-        // The path exists, nothing to do. The odex files (if any) will be left untouched.
-        *out_secondary_dex_exists = true;
-        return true;
-    } else if (errno != ENOENT) {
-        PLOG(ERROR) << "Failed to check access to secondary dex " << dex_path;
+    if (storage_flag != FLAG_STORAGE_CE && storage_flag != FLAG_STORAGE_DE) {
+        LOG(ERROR) << "reconcile_secondary_dex_file called with invalid storage_flag: "
+                << storage_flag;
         return false;
     }
 
@@ -1821,23 +2182,40 @@
     // of the package user id. So we fork and drop capabilities in the child.
     pid_t pid = fork();
     if (pid == 0) {
-        // The secondary dex does not exist anymore. Clear any generated files.
+        /* child -- drop privileges before continuing */
+        drop_capabilities(uid);
+
+        const char* volume_uuid_cstr = volume_uuid == nullptr ? nullptr : volume_uuid->c_str();
+        if (!validate_secondary_dex_path(pkgname.c_str(), dex_path.c_str(), volume_uuid_cstr,
+                uid, storage_flag)) {
+            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            _exit(kReconcileSecondaryDexValidationError);
+        }
+
+        SecondaryDexAccess access_check = check_secondary_dex_access(dex_path);
+        switch (access_check) {
+            case kSecondaryDexAccessDoesNotExist:
+                 // File does not exist. Proceed with cleaning.
+                break;
+            case kSecondaryDexAccessReadOk: _exit(kReconcileSecondaryDexExists);
+            case kSecondaryDexAccessIOError: _exit(kReconcileSecondaryDexAccessIOError);
+            case kSecondaryDexAccessPermissionError: _exit(kReconcileSecondaryDexValidationError);
+            default:
+                LOG(ERROR) << "Unexpected result from check_secondary_dex_access: " << access_check;
+                _exit(kReconcileSecondaryDexValidationError);
+        }
+
+        // The secondary dex does not exist anymore or it's. Clear any generated files.
         char oat_path[PKG_PATH_MAX];
         char oat_dir[PKG_PATH_MAX];
         char oat_isa_dir[PKG_PATH_MAX];
         bool result = true;
-        /* child -- drop privileges before continuing */
-        drop_capabilities(uid);
         for (size_t i = 0; i < isas.size(); i++) {
-            if (!create_secondary_dex_oat_layout(dex_path,
-                                                 isas[i],
-                                                 oat_dir,
-                                                 oat_isa_dir,
-                                                 oat_path)) {
-                LOG(ERROR) << "Could not create secondary odex layout: "
-                           << dex_path;
-                result = false;
-                continue;
+            std::string error_msg;
+            if (!create_secondary_dex_oat_layout(
+                    dex_path,isas[i], oat_dir, oat_isa_dir, oat_path, &error_msg)) {
+                LOG(ERROR) << error_msg;
+                _exit(kReconcileSecondaryDexValidationError);
             }
 
             // Delete oat/vdex/art files.
@@ -1847,9 +2225,9 @@
 
             // Delete profiles.
             std::string current_profile = create_current_profile_path(
-                multiuser_get_user_id(uid), dex_path, /*is_secondary*/true);
+                multiuser_get_user_id(uid), pkgname, dex_path, /*is_secondary*/true);
             std::string reference_profile = create_reference_profile_path(
-                dex_path, /*is_secondary*/true);
+                pkgname, dex_path, /*is_secondary*/true);
             result = unlink_if_exists(current_profile) && result;
             result = unlink_if_exists(reference_profile) && result;
 
@@ -1862,11 +2240,127 @@
             result = rmdir_if_empty(oat_isa_dir) && result;
             result = rmdir_if_empty(oat_dir) && result;
         }
-        result ? _exit(0) : _exit(1);
+        if (!result) {
+            PLOG(ERROR) << "Failed to clean secondary dex artifacts for location " << dex_path;
+        }
+        _exit(result ? kReconcileSecondaryDexCleanedUp : kReconcileSecondaryDexAccessIOError);
     }
 
     int return_code = wait_child(pid);
-    return return_code == 0;
+    if (!WIFEXITED(return_code)) {
+        LOG(WARNING) << "reconcile dex failed for location " << dex_path << ": " << return_code;
+    } else {
+        return_code = WEXITSTATUS(return_code);
+    }
+
+    LOG(DEBUG) << "Reconcile secondary dex path " << dex_path << " result=" << return_code;
+
+    switch (return_code) {
+        case kReconcileSecondaryDexCleanedUp:
+        case kReconcileSecondaryDexValidationError:
+            // If we couldn't validate assume the dex file does not exist.
+            // This will purge the entry from the PM records.
+            *out_secondary_dex_exists = false;
+            return true;
+        case kReconcileSecondaryDexExists:
+            *out_secondary_dex_exists = true;
+            return true;
+        case kReconcileSecondaryDexAccessIOError:
+            // We had an access IO error.
+            // Return false so that we can try again.
+            // The value of out_secondary_dex_exists does not matter in this case and by convention
+            // is set to false.
+            *out_secondary_dex_exists = false;
+            return false;
+        default:
+            LOG(ERROR) << "Unexpected code from reconcile_secondary_dex_file: " << return_code;
+            *out_secondary_dex_exists = false;
+            return false;
+    }
+}
+
+// Compute and return the hash (SHA-256) of the secondary dex file at dex_path.
+// Returns true if all parameters are valid and the hash successfully computed and stored in
+// out_secondary_dex_hash.
+// Also returns true with an empty hash if the file does not currently exist or is not accessible to
+// the app.
+// For any other errors (e.g. if any of the parameters are invalid) returns false.
+bool hash_secondary_dex_file(const std::string& dex_path, const std::string& pkgname, int uid,
+        const std::unique_ptr<std::string>& volume_uuid, int storage_flag,
+        std::vector<uint8_t>* out_secondary_dex_hash) {
+    out_secondary_dex_hash->clear();
+
+    const char* volume_uuid_cstr = volume_uuid == nullptr ? nullptr : volume_uuid->c_str();
+
+    if (storage_flag != FLAG_STORAGE_CE && storage_flag != FLAG_STORAGE_DE) {
+        LOG(ERROR) << "hash_secondary_dex_file called with invalid storage_flag: "
+                << storage_flag;
+        return false;
+    }
+
+    // Pipe to get the hash result back from our child process.
+    unique_fd pipe_read, pipe_write;
+    if (!Pipe(&pipe_read, &pipe_write)) {
+        PLOG(ERROR) << "Failed to create pipe";
+        return false;
+    }
+
+    // Fork so that actual access to the files is done in the app's own UID, to ensure we only
+    // access data the app itself can access.
+    pid_t pid = fork();
+    if (pid == 0) {
+        // child -- drop privileges before continuing
+        drop_capabilities(uid);
+        pipe_read.reset();
+
+        if (!validate_secondary_dex_path(pkgname, dex_path, volume_uuid_cstr, uid, storage_flag)) {
+            LOG(ERROR) << "Could not validate secondary dex path " << dex_path;
+            _exit(DexoptReturnCodes::kHashValidatePath);
+        }
+
+        unique_fd fd(TEMP_FAILURE_RETRY(open(dex_path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
+        if (fd == -1) {
+            if (errno == EACCES || errno == ENOENT) {
+                // Not treated as an error.
+                _exit(0);
+            }
+            PLOG(ERROR) << "Failed to open secondary dex " << dex_path;
+            _exit(DexoptReturnCodes::kHashOpenPath);
+        }
+
+        SHA256_CTX ctx;
+        SHA256_Init(&ctx);
+
+        std::vector<uint8_t> buffer(65536);
+        while (true) {
+            ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer.data(), buffer.size()));
+            if (bytes_read == 0) {
+                break;
+            } else if (bytes_read == -1) {
+                PLOG(ERROR) << "Failed to read secondary dex " << dex_path;
+                _exit(DexoptReturnCodes::kHashReadDex);
+            }
+
+            SHA256_Update(&ctx, buffer.data(), bytes_read);
+        }
+
+        std::array<uint8_t, SHA256_DIGEST_LENGTH> hash;
+        SHA256_Final(hash.data(), &ctx);
+        if (!WriteFully(pipe_write, hash.data(), hash.size())) {
+            _exit(DexoptReturnCodes::kHashWrite);
+        }
+
+        _exit(0);
+    }
+
+    // parent
+    pipe_write.reset();
+
+    out_secondary_dex_hash->resize(SHA256_DIGEST_LENGTH);
+    if (!ReadFully(pipe_read, out_secondary_dex_hash->data(), out_secondary_dex_hash->size())) {
+        out_secondary_dex_hash->clear();
+    }
+    return wait_child(pid) == 0;
 }
 
 // Helper for move_ab, so that we can have common failure-case cleanup.
@@ -2031,5 +2525,303 @@
     return return_value_oat && return_value_art && return_value_vdex;
 }
 
+static bool is_absolute_path(const std::string& path) {
+    if (path.find('/') != 0 || path.find("..") != std::string::npos) {
+        LOG(ERROR) << "Invalid absolute path " << path;
+        return false;
+    } else {
+        return true;
+    }
+}
+
+static bool is_valid_instruction_set(const std::string& instruction_set) {
+    // TODO: add explicit whitelisting of instruction sets
+    if (instruction_set.find('/') != std::string::npos) {
+        LOG(ERROR) << "Invalid instruction set " << instruction_set;
+        return false;
+    } else {
+        return true;
+    }
+}
+
+bool calculate_oat_file_path_default(char path[PKG_PATH_MAX], const char *oat_dir,
+        const char *apk_path, const char *instruction_set) {
+    std::string oat_dir_ = oat_dir;
+    std::string apk_path_ = apk_path;
+    std::string instruction_set_ = instruction_set;
+
+    if (!is_absolute_path(oat_dir_)) return false;
+    if (!is_absolute_path(apk_path_)) return false;
+    if (!is_valid_instruction_set(instruction_set_)) return false;
+
+    std::string::size_type end = apk_path_.rfind('.');
+    std::string::size_type start = apk_path_.rfind('/', end);
+    if (end == std::string::npos || start == std::string::npos) {
+        LOG(ERROR) << "Invalid apk_path " << apk_path_;
+        return false;
+    }
+
+    std::string res_ = oat_dir_ + '/' + instruction_set + '/'
+            + apk_path_.substr(start + 1, end - start - 1) + ".odex";
+    const char* res = res_.c_str();
+    if (strlen(res) >= PKG_PATH_MAX) {
+        LOG(ERROR) << "Result too large";
+        return false;
+    } else {
+        strlcpy(path, res, PKG_PATH_MAX);
+        return true;
+    }
+}
+
+bool calculate_odex_file_path_default(char path[PKG_PATH_MAX], const char *apk_path,
+        const char *instruction_set) {
+    std::string apk_path_ = apk_path;
+    std::string instruction_set_ = instruction_set;
+
+    if (!is_absolute_path(apk_path_)) return false;
+    if (!is_valid_instruction_set(instruction_set_)) return false;
+
+    std::string::size_type end = apk_path_.rfind('.');
+    std::string::size_type start = apk_path_.rfind('/', end);
+    if (end == std::string::npos || start == std::string::npos) {
+        LOG(ERROR) << "Invalid apk_path " << apk_path_;
+        return false;
+    }
+
+    std::string oat_dir = apk_path_.substr(0, start + 1) + "oat";
+    return calculate_oat_file_path_default(path, oat_dir.c_str(), apk_path, instruction_set);
+}
+
+bool create_cache_path_default(char path[PKG_PATH_MAX], const char *src,
+        const char *instruction_set) {
+    std::string src_ = src;
+    std::string instruction_set_ = instruction_set;
+
+    if (!is_absolute_path(src_)) return false;
+    if (!is_valid_instruction_set(instruction_set_)) return false;
+
+    for (auto it = src_.begin() + 1; it < src_.end(); ++it) {
+        if (*it == '/') {
+            *it = '@';
+        }
+    }
+
+    std::string res_ = android_data_dir + DALVIK_CACHE + '/' + instruction_set_ + src_
+            + DALVIK_CACHE_POSTFIX;
+    const char* res = res_.c_str();
+    if (strlen(res) >= PKG_PATH_MAX) {
+        LOG(ERROR) << "Result too large";
+        return false;
+    } else {
+        strlcpy(path, res, PKG_PATH_MAX);
+        return true;
+    }
+}
+
+bool open_classpath_files(const std::string& classpath, std::vector<unique_fd>* apk_fds,
+        std::vector<std::string>* dex_locations) {
+    std::vector<std::string> classpaths_elems = base::Split(classpath, ":");
+    for (const std::string& elem : classpaths_elems) {
+        unique_fd fd(TEMP_FAILURE_RETRY(open(elem.c_str(), O_RDONLY)));
+        if (fd < 0) {
+            PLOG(ERROR) << "Could not open classpath elem " << elem;
+            return false;
+        } else {
+            apk_fds->push_back(std::move(fd));
+            dex_locations->push_back(elem);
+        }
+    }
+    return true;
+}
+
+static bool create_app_profile_snapshot(int32_t app_id,
+                                        const std::string& package_name,
+                                        const std::string& profile_name,
+                                        const std::string& classpath) {
+    int app_shared_gid = multiuser_get_shared_gid(/*user_id*/ 0, app_id);
+
+    unique_fd snapshot_fd = open_spnashot_profile(AID_SYSTEM, package_name, profile_name);
+    if (snapshot_fd < 0) {
+        return false;
+    }
+
+    std::vector<unique_fd> profiles_fd;
+    unique_fd reference_profile_fd;
+    open_profile_files(app_shared_gid, package_name, profile_name, /*is_secondary_dex*/ false,
+            &profiles_fd, &reference_profile_fd);
+    if (profiles_fd.empty() || (reference_profile_fd.get() < 0)) {
+        return false;
+    }
+
+    profiles_fd.push_back(std::move(reference_profile_fd));
+
+    // Open the class paths elements. These will be used to filter out profile data that does
+    // not belong to the classpath during merge.
+    std::vector<unique_fd> apk_fds;
+    std::vector<std::string> dex_locations;
+    if (!open_classpath_files(classpath, &apk_fds, &dex_locations)) {
+        return false;
+    }
+
+    pid_t pid = fork();
+    if (pid == 0) {
+        /* child -- drop privileges before continuing */
+        drop_capabilities(app_shared_gid);
+        run_profman_merge(profiles_fd, snapshot_fd, &apk_fds, &dex_locations);
+    }
+
+    /* parent */
+    int return_code = wait_child(pid);
+    if (!WIFEXITED(return_code)) {
+        LOG(WARNING) << "profman failed for " << package_name << ":" << profile_name;
+        return false;
+    }
+
+    return true;
+}
+
+static bool create_boot_image_profile_snapshot(const std::string& package_name,
+                                               const std::string& profile_name,
+                                               const std::string& classpath) {
+    // The reference profile directory for the android package might not be prepared. Do it now.
+    const std::string ref_profile_dir =
+            create_primary_reference_profile_package_dir_path(package_name);
+    if (fs_prepare_dir(ref_profile_dir.c_str(), 0770, AID_SYSTEM, AID_SYSTEM) != 0) {
+        PLOG(ERROR) << "Failed to prepare " << ref_profile_dir;
+        return false;
+    }
+
+    // Open and create the snapshot profile.
+    unique_fd snapshot_fd = open_spnashot_profile(AID_SYSTEM, package_name, profile_name);
+
+    // Collect all non empty profiles.
+    // The collection will traverse all applications profiles and find the non empty files.
+    // This has the potential of inspecting a large number of files and directories (depending
+    // on the number of applications and users). So there is a slight increase in the chance
+    // to get get occasionally I/O errors (e.g. for opening the file). When that happens do not
+    // fail the snapshot and aggregate whatever profile we could open.
+    //
+    // The profile snapshot is a best effort based on available data it's ok if some data
+    // from some apps is missing. It will be counter productive for the snapshot to fail
+    // because we could not open or read some of the files.
+    std::vector<std::string> profiles;
+    if (!collect_profiles(&profiles)) {
+        LOG(WARNING) << "There were errors while collecting the profiles for the boot image.";
+    }
+
+    // If we have no profiles return early.
+    if (profiles.empty()) {
+        return true;
+    }
+
+    // Open the classpath elements. These will be used to filter out profile data that does
+    // not belong to the classpath during merge.
+    std::vector<unique_fd> apk_fds;
+    std::vector<std::string> dex_locations;
+    if (!open_classpath_files(classpath, &apk_fds, &dex_locations)) {
+        return false;
+    }
+
+    // If we could not open any files from the classpath return an error.
+    if (apk_fds.empty()) {
+        LOG(ERROR) << "Could not open any of the classpath elements.";
+        return false;
+    }
+
+    // Aggregate the profiles in batches of kAggregationBatchSize.
+    // We do this to avoid opening a huge a amount of files.
+    static constexpr size_t kAggregationBatchSize = 10;
+
+    std::vector<unique_fd> profiles_fd;
+    for (size_t i = 0; i < profiles.size(); )  {
+        for (size_t k = 0; k < kAggregationBatchSize && i < profiles.size(); k++, i++) {
+            unique_fd fd = open_profile(AID_SYSTEM, profiles[i], O_RDONLY);
+            if (fd.get() >= 0) {
+                profiles_fd.push_back(std::move(fd));
+            }
+        }
+        pid_t pid = fork();
+        if (pid == 0) {
+            /* child -- drop privileges before continuing */
+            drop_capabilities(AID_SYSTEM);
+
+            // The introduction of new access flags into boot jars causes them to
+            // fail dex file verification.
+            run_profman_merge(profiles_fd, snapshot_fd, &apk_fds, &dex_locations);
+        }
+
+        /* parent */
+        int return_code = wait_child(pid);
+        if (!WIFEXITED(return_code)) {
+            PLOG(WARNING) << "profman failed for " << package_name << ":" << profile_name;
+            return false;
+        }
+        return true;
+    }
+    return true;
+}
+
+bool create_profile_snapshot(int32_t app_id, const std::string& package_name,
+        const std::string& profile_name, const std::string& classpath) {
+    if (app_id == -1) {
+        return create_boot_image_profile_snapshot(package_name, profile_name, classpath);
+    } else {
+        return create_app_profile_snapshot(app_id, package_name, profile_name, classpath);
+    }
+}
+
+bool prepare_app_profile(const std::string& package_name,
+                         userid_t user_id,
+                         appid_t app_id,
+                         const std::string& profile_name,
+                         const std::string& code_path,
+                         const std::unique_ptr<std::string>& dex_metadata) {
+    // Prepare the current profile.
+    std::string cur_profile  = create_current_profile_path(user_id, package_name, profile_name,
+            /*is_secondary_dex*/ false);
+    uid_t uid = multiuser_get_uid(user_id, app_id);
+    if (fs_prepare_file_strict(cur_profile.c_str(), 0600, uid, uid) != 0) {
+        PLOG(ERROR) << "Failed to prepare " << cur_profile;
+        return false;
+    }
+
+    // Check if we need to install the profile from the dex metadata.
+    if (dex_metadata == nullptr) {
+        return true;
+    }
+
+    // We have a dex metdata. Merge the profile into the reference profile.
+    unique_fd ref_profile_fd = open_reference_profile(uid, package_name, profile_name,
+            /*read_write*/ true, /*is_secondary_dex*/ false);
+    unique_fd dex_metadata_fd(TEMP_FAILURE_RETRY(
+            open(dex_metadata->c_str(), O_RDONLY | O_NOFOLLOW)));
+    unique_fd apk_fd(TEMP_FAILURE_RETRY(open(code_path.c_str(), O_RDONLY | O_NOFOLLOW)));
+    if (apk_fd < 0) {
+        PLOG(ERROR) << "Could not open code path " << code_path;
+        return false;
+    }
+
+    pid_t pid = fork();
+    if (pid == 0) {
+        /* child -- drop privileges before continuing */
+        gid_t app_shared_gid = multiuser_get_shared_gid(user_id, app_id);
+        drop_capabilities(app_shared_gid);
+
+        // The copy and update takes ownership over the fds.
+        run_profman_copy_and_update(std::move(dex_metadata_fd),
+                                    std::move(ref_profile_fd),
+                                    std::move(apk_fd),
+                                    code_path);
+    }
+
+    /* parent */
+    int return_code = wait_child(pid);
+    if (!WIFEXITED(return_code)) {
+        PLOG(WARNING) << "profman failed for " << package_name << ":" << profile_name;
+        return false;
+    }
+    return true;
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h
index 23446da..bb6fab3 100644
--- a/cmds/installd/dexopt.h
+++ b/cmds/installd/dexopt.h
@@ -17,6 +17,8 @@
 #ifndef DEXOPT_H_
 #define DEXOPT_H_
 
+#include "installd_constants.h"
+
 #include <sys/types.h>
 
 #include <cutils/multiuser.h>
@@ -31,28 +33,63 @@
 static constexpr int DEX2OAT_FOR_FILTER          = 3;
 static constexpr int DEX2OAT_FOR_RELOCATION      = 4;
 
-// Clear the reference profile for the primary apk of the given package.
-bool clear_primary_reference_profile(const std::string& pkgname);
-// Clear the current profile for the primary apk of the given package and user.
-bool clear_primary_current_profile(const std::string& pkgname, userid_t user);
-// Clear all current profile for the primary apk of the given package.
-bool clear_primary_current_profiles(const std::string& pkgname);
-
-bool move_ab(const char* apk_path, const char* instruction_set, const char* output_path);
+// Clear the reference profile identified by the given profile name.
+bool clear_primary_reference_profile(const std::string& pkgname, const std::string& profile_name);
+// Clear the current profile identified by the given profile name (for single user).
+bool clear_primary_current_profile(const std::string& pkgname, const std::string& profile_name,
+         userid_t user);
+// Clear all current profiles identified by the given profile name (all users).
+bool clear_primary_current_profiles(const std::string& pkgname, const std::string& profile_name);
 
 // Decide if profile guided compilation is needed or not based on existing profiles.
-// The analysis is done for the primary apks (base + splits) of the given package.
+// The analysis is done for a single profile name (which corresponds to a single code path).
 // Returns true if there is enough information in the current profiles that makes it
 // worth to recompile the package.
 // If the return value is true all the current profiles would have been merged into
 // the reference profiles accessible with open_reference_profile().
-bool analyze_primary_profiles(uid_t uid, const std::string& pkgname);
+bool analyze_primary_profiles(uid_t uid,
+                              const std::string& pkgname,
+                              const std::string& profile_name);
 
-bool dump_profiles(int32_t uid, const std::string& pkgname, const char* code_paths);
+// Create a snapshot of the profile information for the given package profile.
+// If appId is -1, the method creates the profile snapshot for the boot image.
+//
+// The profile snapshot is the aggregation of all existing profiles (all current user
+// profiles & the reference profile) and is meant to capture the all the profile information
+// without performing a merge into the reference profile which might impact future dex2oat
+// compilations.
+// The snapshot is created next to the reference profile of the package and the
+// ownership is assigned to AID_SYSTEM.
+// The snapshot location is reference_profile_location.snapshot. If a snapshot is already
+// there, it will be truncated and overwritten.
+//
+// The classpath acts as filter: only profiling data belonging to elements of the classpath
+// will end up in the snapshot.
+bool create_profile_snapshot(int32_t app_id,
+                             const std::string& package,
+                             const std::string& profile_name,
+                             const std::string& classpath);
+
+bool dump_profiles(int32_t uid,
+                   const std::string& pkgname,
+                   const std::string& profile_name,
+                   const std::string& code_path);
 
 bool copy_system_profile(const std::string& system_profile,
                          uid_t packageUid,
-                         const std::string& data_profile_location);
+                         const std::string& pkgname,
+                         const std::string& profile_name);
+
+// Prepare the app profile for the given code path:
+//  - create the current profile using profile_name
+//  - merge the profile from the dex metadata file (if present) into
+//    the reference profile.
+bool prepare_app_profile(const std::string& package_name,
+                         userid_t user_id,
+                         appid_t app_id,
+                         const std::string& profile_name,
+                         const std::string& code_path,
+                         const std::unique_ptr<std::string>& dex_metadata);
 
 bool delete_odex(const char* apk_path, const char* instruction_set, const char* output_path);
 
@@ -61,10 +98,26 @@
         const std::unique_ptr<std::string>& volumeUuid, int storage_flag,
         /*out*/bool* out_secondary_dex_exists);
 
+bool hash_secondary_dex_file(const std::string& dex_path,
+        const std::string& pkgname, int uid, const std::unique_ptr<std::string>& volume_uuid,
+        int storage_flag, std::vector<uint8_t>* out_secondary_dex_hash);
+
 int dexopt(const char *apk_path, uid_t uid, const char *pkgName, const char *instruction_set,
         int dexopt_needed, const char* oat_dir, int dexopt_flags, const char* compiler_filter,
         const char* volume_uuid, const char* class_loader_context, const char* se_info,
-        bool downgrade);
+        bool downgrade, int target_sdk_version, const char* profile_name,
+        const char* dexMetadataPath, const char* compilation_reason, std::string* error_msg);
+
+bool calculate_oat_file_path_default(char path[PKG_PATH_MAX], const char *oat_dir,
+        const char *apk_path, const char *instruction_set);
+
+bool calculate_odex_file_path_default(char path[PKG_PATH_MAX], const char *apk_path,
+        const char *instruction_set);
+
+bool create_cache_path_default(char path[PKG_PATH_MAX], const char *src,
+        const char *instruction_set);
+
+bool move_ab(const char* apk_path, const char* instruction_set, const char* output_path);
 
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/dexopt_return_codes.h b/cmds/installd/dexopt_return_codes.h
new file mode 100644
index 0000000..bbecfa4
--- /dev/null
+++ b/cmds/installd/dexopt_return_codes.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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 <dex2oat_return_codes.h>
+
+namespace android {
+namespace installd {
+
+// Constants for exit codes that installd code emits. These are failure situations before calling
+// any tools, e.g., in validation, and must not overlap with the exit codes of tools, so they
+// can be distinguished.
+enum DexoptReturnCodes : int {
+    kSetGid = 64,
+    kSetUid = 65,
+    kCapSet = 66,
+    kFlock = 67,
+    kProfmanExec = 68,
+    kSetSchedPolicy = 70,
+    kSetPriority = 71,
+    kDex2oatExec = 72,
+    kInstructionSetLength = 73,
+    kHashValidatePath = 74,
+    kHashOpenPath = 75,
+    kHashReadDex = 76,
+    kHashWrite = 77,
+};
+
+inline const char* get_installd_return_code_name(DexoptReturnCodes code) {
+    switch (code) {
+        case kSetGid:
+            return "setgid";
+        case kSetUid:
+            return "setuid";
+        case kCapSet:
+            return "capset";
+        case kFlock:
+            return "flock";
+        case kProfmanExec:
+            return "exec(profman)";
+        case kSetSchedPolicy:
+            return "setschedpolicy";
+        case kSetPriority:
+            return "setpriority";
+        case kDex2oatExec:
+            return "exec(dex2oat)";
+        case kInstructionSetLength:
+            return "instruction-set-length";
+        case kHashValidatePath:
+            return "hash(validate-path)";
+        case kHashOpenPath:
+            return "hash(open-path)";
+        case kHashReadDex:
+            return "hash(read-dex)";
+        case kHashWrite:
+            return "hash(write)";
+    }
+    return nullptr;
+}
+
+inline const char* get_dex2oat_return_code_name(art::dex2oat::ReturnCode code) {
+    switch (code) {
+        case art::dex2oat::ReturnCode::kNoFailure:
+            return "dex2oat success";
+        case art::dex2oat::ReturnCode::kOther:
+            return "unspecified dex2oat error";
+        case art::dex2oat::ReturnCode::kCreateRuntime:
+            return "dex2oat failed to create a runtime";
+    }
+    return nullptr;
+}
+
+// Get some slightly descriptive string for the return code. Handles both DexoptReturnCodes (local
+// exit codes) as well as art::dex2oat::ReturnCode.
+inline const char* get_return_code_name(int code) {
+    // Try to enforce non-overlap (see comment on DexoptReturnCodes)
+    // TODO: How could switch-case checks be used to enforce completeness?
+    switch (code) {
+        case kSetGid:
+        case kSetUid:
+        case kCapSet:
+        case kFlock:
+        case kProfmanExec:
+        case kSetSchedPolicy:
+        case kSetPriority:
+        case kDex2oatExec:
+        case kInstructionSetLength:
+        case kHashValidatePath:
+        case kHashOpenPath:
+        case kHashReadDex:
+        case kHashWrite:
+            break;
+        case static_cast<int>(art::dex2oat::ReturnCode::kNoFailure):
+        case static_cast<int>(art::dex2oat::ReturnCode::kOther):
+        case static_cast<int>(art::dex2oat::ReturnCode::kCreateRuntime):
+            break;
+    }
+    const char* value = get_installd_return_code_name(static_cast<DexoptReturnCodes>(code));
+    if (value != nullptr) {
+        return value;
+    }
+    value = get_dex2oat_return_code_name(static_cast<art::dex2oat::ReturnCode>(code));
+    return value;
+}
+
+}  // namespace installd
+}  // namespace android
diff --git a/cmds/installd/globals.cpp b/cmds/installd/globals.cpp
index edcdb6a..b3a6daf 100644
--- a/cmds/installd/globals.cpp
+++ b/cmds/installd/globals.cpp
@@ -16,15 +16,15 @@
 
 #define LOG_TAG "installd"
 
-#include <stdlib.h>
-#include <string.h>
-
-#include <log/log.h>              // TODO: Move everything to base::logging.
-
 #include <globals.h>
 #include <installd_constants.h>
 #include <utils.h>
 
+#include <android-base/logging.h>
+
+#include <stdlib.h>
+#include <string.h>
+
 namespace android {
 namespace installd {
 
@@ -44,106 +44,78 @@
 static constexpr const char* PRIVATE_APP_SUBDIR = "app-private/"; // sub-directory under
                                                                   // ANDROID_DATA
 
-/* Directory records that are used in execution of commands. */
-dir_rec_t android_app_dir;
-dir_rec_t android_app_ephemeral_dir;
-dir_rec_t android_app_lib_dir;
-dir_rec_t android_app_private_dir;
-dir_rec_t android_asec_dir;
-dir_rec_t android_data_dir;
-dir_rec_t android_media_dir;
-dir_rec_t android_mnt_expand_dir;
-dir_rec_t android_profiles_dir;
+std::string android_app_dir;
+std::string android_app_ephemeral_dir;
+std::string android_app_lib_dir;
+std::string android_app_private_dir;
+std::string android_asec_dir;
+std::string android_data_dir;
+std::string android_media_dir;
+std::string android_mnt_expand_dir;
+std::string android_profiles_dir;
+std::string android_root_dir;
 
-dir_rec_array_t android_system_dirs;
+std::vector<std::string> android_system_dirs;
 
-/**
- * Initialize all the global variables that are used elsewhere. Returns 0 upon
- * success and -1 on error.
- */
-void free_globals() {
-    size_t i;
-
-    for (i = 0; i < android_system_dirs.count; i++) {
-        if (android_system_dirs.dirs[i].path != NULL) {
-            free(android_system_dirs.dirs[i].path);
-        }
+bool init_globals_from_data_and_root() {
+    const char* data_path = getenv("ANDROID_DATA");
+    if (data_path == nullptr) {
+        LOG(ERROR) << "Could not find ANDROID_DATA";
+        return false;
     }
+    const char* root_path = getenv("ANDROID_ROOT");
+    if (root_path == nullptr) {
+        LOG(ERROR) << "Could not find ANDROID_ROOT";
+        return false;
+    }
+    return init_globals_from_data_and_root(data_path, root_path);
+}
 
-    free(android_system_dirs.dirs);
+static std::string ensure_trailing_slash(const std::string& path) {
+    if (path.rfind('/') != path.size() - 1) {
+        return path + '/';
+    } else {
+        return path;
+    }
 }
 
 bool init_globals_from_data_and_root(const char* data, const char* root) {
     // Get the android data directory.
-    if (get_path_from_string(&android_data_dir, data) < 0) {
-        return false;
-    }
+    android_data_dir = ensure_trailing_slash(data);
+
+    // Get the android root directory.
+    android_root_dir = ensure_trailing_slash(root);
 
     // Get the android app directory.
-    if (copy_and_append(&android_app_dir, &android_data_dir, APP_SUBDIR) < 0) {
-        return false;
-    }
+    android_app_dir = android_data_dir + APP_SUBDIR;
 
     // Get the android protected app directory.
-    if (copy_and_append(&android_app_private_dir, &android_data_dir, PRIVATE_APP_SUBDIR) < 0) {
-        return false;
-    }
+    android_app_private_dir = android_data_dir + PRIVATE_APP_SUBDIR;
 
     // Get the android ephemeral app directory.
-    if (copy_and_append(&android_app_ephemeral_dir, &android_data_dir, EPHEMERAL_APP_SUBDIR) < 0) {
-        return false;
-    }
+    android_app_ephemeral_dir = android_data_dir + EPHEMERAL_APP_SUBDIR;
 
     // Get the android app native library directory.
-    if (copy_and_append(&android_app_lib_dir, &android_data_dir, APP_LIB_SUBDIR) < 0) {
-        return false;
-    }
+    android_app_lib_dir = android_data_dir + APP_LIB_SUBDIR;
 
     // Get the sd-card ASEC mount point.
-    if (get_path_from_env(&android_asec_dir, ASEC_MOUNTPOINT_ENV_NAME) < 0) {
-        return false;
-    }
+    android_asec_dir = ensure_trailing_slash(getenv(ASEC_MOUNTPOINT_ENV_NAME));
 
     // Get the android media directory.
-    if (copy_and_append(&android_media_dir, &android_data_dir, MEDIA_SUBDIR) < 0) {
-        return false;
-    }
+    android_media_dir = android_data_dir + MEDIA_SUBDIR;
 
     // Get the android external app directory.
-    if (get_path_from_string(&android_mnt_expand_dir, "/mnt/expand/") < 0) {
-        return false;
-    }
+    android_mnt_expand_dir = "/mnt/expand/";
 
     // Get the android profiles directory.
-    if (copy_and_append(&android_profiles_dir, &android_data_dir, PROFILES_SUBDIR) < 0) {
-        return false;
-    }
+    android_profiles_dir = android_data_dir + PROFILES_SUBDIR;
 
     // Take note of the system and vendor directories.
-    android_system_dirs.count = 4;
-
-    android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t));
-    if (android_system_dirs.dirs == NULL) {
-        ALOGE("Couldn't allocate array for dirs; aborting\n");
-        return false;
-    }
-
-    dir_rec_t android_root_dir;
-    if (get_path_from_string(&android_root_dir, root) < 0) {
-        return false;
-    }
-
-    android_system_dirs.dirs[0].path = build_string2(android_root_dir.path, APP_SUBDIR);
-    android_system_dirs.dirs[0].len = strlen(android_system_dirs.dirs[0].path);
-
-    android_system_dirs.dirs[1].path = build_string2(android_root_dir.path, PRIV_APP_SUBDIR);
-    android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path);
-
-    android_system_dirs.dirs[2].path = strdup("/vendor/app/");
-    android_system_dirs.dirs[2].len = strlen(android_system_dirs.dirs[2].path);
-
-    android_system_dirs.dirs[3].path = strdup("/oem/app/");
-    android_system_dirs.dirs[3].len = strlen(android_system_dirs.dirs[3].path);
+    android_system_dirs.clear();
+    android_system_dirs.push_back(android_root_dir + APP_SUBDIR);
+    android_system_dirs.push_back(android_root_dir + PRIV_APP_SUBDIR);
+    android_system_dirs.push_back("/vendor/app/");
+    android_system_dirs.push_back("/oem/app/");
 
     return true;
 }
diff --git a/cmds/installd/globals.h b/cmds/installd/globals.h
index c90beec..633e33b 100644
--- a/cmds/installd/globals.h
+++ b/cmds/installd/globals.h
@@ -19,40 +19,29 @@
 #define GLOBALS_H_
 
 #include <inttypes.h>
+#include <string>
+#include <vector>
 
 namespace android {
 namespace installd {
 
-/* constants */
-
 // Name of the environment variable that contains the asec mountpoint.
 static constexpr const char* ASEC_MOUNTPOINT_ENV_NAME = "ASEC_MOUNTPOINT";
 
-/* data structures */
+extern std::string android_app_dir;
+extern std::string android_app_ephemeral_dir;
+extern std::string android_app_lib_dir;
+extern std::string android_app_private_dir;
+extern std::string android_asec_dir;
+extern std::string android_data_dir;
+extern std::string android_media_dir;
+extern std::string android_mnt_expand_dir;
+extern std::string android_profiles_dir;
+extern std::string android_root_dir;
 
-struct dir_rec_t {
-    char* path;
-    size_t len;
-};
+extern std::vector<std::string> android_system_dirs;
 
-struct dir_rec_array_t {
-    size_t count;
-    dir_rec_t* dirs;
-};
-
-extern dir_rec_t android_app_dir;
-extern dir_rec_t android_app_ephemeral_dir;
-extern dir_rec_t android_app_lib_dir;
-extern dir_rec_t android_app_private_dir;
-extern dir_rec_t android_asec_dir;
-extern dir_rec_t android_data_dir;
-extern dir_rec_t android_media_dir;
-extern dir_rec_t android_mnt_expand_dir;
-extern dir_rec_t android_profiles_dir;
-
-extern dir_rec_array_t android_system_dirs;
-
-void free_globals();
+bool init_globals_from_data_and_root();
 bool init_globals_from_data_and_root(const char* data, const char* root);
 
 }  // namespace installd
diff --git a/cmds/installd/installd.cpp b/cmds/installd/installd.cpp
index 35936a2..95ed2ff 100644
--- a/cmds/installd/installd.cpp
+++ b/cmds/installd/installd.cpp
@@ -30,6 +30,7 @@
 #include <private/android_filesystem_config.h>
 
 #include "InstalldNativeService.h"
+#include "dexopt.h"
 #include "globals.h"
 #include "installd_constants.h"
 #include "installd_deps.h"  // Need to fill in requirements of commands.
@@ -50,133 +51,22 @@
     return property_get(key, value, default_value);
 }
 
-// Compute the output path of
-bool calculate_oat_file_path(char path[PKG_PATH_MAX],
-                             const char *oat_dir,
-                             const char *apk_path,
-                             const char *instruction_set) {
-    const char *file_name_start;
-    const char *file_name_end;
-
-    file_name_start = strrchr(apk_path, '/');
-    if (file_name_start == NULL) {
-        SLOGE("apk_path '%s' has no '/'s in it\n", apk_path);
-        return false;
-    }
-    file_name_end = strrchr(apk_path, '.');
-    if (file_name_end < file_name_start) {
-        SLOGE("apk_path '%s' has no extension\n", apk_path);
-        return false;
-    }
-
-    // Calculate file_name
-    int file_name_len = file_name_end - file_name_start - 1;
-    char file_name[file_name_len + 1];
-    memcpy(file_name, file_name_start + 1, file_name_len);
-    file_name[file_name_len] = '\0';
-
-    // <apk_parent_dir>/oat/<isa>/<file_name>.odex
-    snprintf(path, PKG_PATH_MAX, "%s/%s/%s.odex", oat_dir, instruction_set, file_name);
-    return true;
+bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir, const char *apk_path,
+        const char *instruction_set) {
+    return calculate_oat_file_path_default(path, oat_dir, apk_path, instruction_set);
 }
 
-/*
- * Computes the odex file for the given apk_path and instruction_set.
- * /system/framework/whatever.jar -> /system/framework/oat/<isa>/whatever.odex
- *
- * Returns false if it failed to determine the odex file path.
- */
-bool calculate_odex_file_path(char path[PKG_PATH_MAX],
-                              const char *apk_path,
-                              const char *instruction_set) {
-    if (strlen(apk_path) + strlen("oat/") + strlen(instruction_set)
-            + strlen("/") + strlen("odex") + 1 > PKG_PATH_MAX) {
-        SLOGE("apk_path '%s' may be too long to form odex file path.\n", apk_path);
-        return false;
-    }
-
-    strcpy(path, apk_path);
-    char *end = strrchr(path, '/');
-    if (end == NULL) {
-        SLOGE("apk_path '%s' has no '/'s in it?!\n", apk_path);
-        return false;
-    }
-    const char *apk_end = apk_path + (end - path); // strrchr(apk_path, '/');
-
-    strcpy(end + 1, "oat/");       // path = /system/framework/oat/\0
-    strcat(path, instruction_set); // path = /system/framework/oat/<isa>\0
-    strcat(path, apk_end);         // path = /system/framework/oat/<isa>/whatever.jar\0
-    end = strrchr(path, '.');
-    if (end == NULL) {
-        SLOGE("apk_path '%s' has no extension.\n", apk_path);
-        return false;
-    }
-    strcpy(end + 1, "odex");
-    return true;
+bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char *apk_path,
+        const char *instruction_set) {
+    return calculate_odex_file_path_default(path, apk_path, instruction_set);
 }
 
-bool create_cache_path(char path[PKG_PATH_MAX],
-                       const char *src,
-                       const char *instruction_set) {
-    /* demand that we are an absolute path */
-    if ((src == nullptr) || (src[0] != '/') || strstr(src,"..")) {
-        return false;
-    }
-
-    size_t srclen = strlen(src);
-
-    if (srclen > PKG_PATH_MAX) {        // XXX: PKG_NAME_MAX?
-        return false;
-    }
-
-    size_t dstlen =
-        android_data_dir.len +
-        strlen(DALVIK_CACHE) +
-        1 +
-        strlen(instruction_set) +
-        srclen +
-        strlen(DALVIK_CACHE_POSTFIX) + 2;
-
-    if (dstlen > PKG_PATH_MAX) {
-        return false;
-    }
-
-    sprintf(path,"%s%s/%s/%s",
-            android_data_dir.path,
-            DALVIK_CACHE,
-            instruction_set,
-            src + 1 /* skip the leading / */);
-
-    char* tmp =
-            path +
-            android_data_dir.len +
-            strlen(DALVIK_CACHE) +
-            1 +
-            strlen(instruction_set) + 1;
-
-    for(; *tmp; tmp++) {
-        if (*tmp == '/') {
-            *tmp = '@';
-        }
-    }
-
-    strcat(path, DALVIK_CACHE_POSTFIX);
-    return true;
+bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *instruction_set) {
+    return create_cache_path_default(path, src, instruction_set);
 }
 
 static bool initialize_globals() {
-    const char* data_path = getenv("ANDROID_DATA");
-    if (data_path == nullptr) {
-        SLOGE("Could not find ANDROID_DATA");
-        return false;
-    }
-    const char* root_path = getenv("ANDROID_ROOT");
-    if (root_path == nullptr) {
-        SLOGE("Could not find ANDROID_ROOT");
-        return false;
-    }
-
-    return init_globals_from_data_and_root(data_path, root_path);
+    return init_globals_from_data_and_root();
 }
 
 static int initialize_directories() {
@@ -184,7 +74,7 @@
 
     // Read current filesystem layout version to handle upgrade paths
     char version_path[PATH_MAX];
-    snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
+    snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.c_str());
 
     int oldVersion;
     if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
@@ -206,7 +96,7 @@
         SLOGD("Upgrading to /data/misc/user directories");
 
         char misc_dir[PATH_MAX];
-        snprintf(misc_dir, PATH_MAX, "%smisc", android_data_dir.path);
+        snprintf(misc_dir, PATH_MAX, "%smisc", android_data_dir.c_str());
 
         char keychain_added_dir[PATH_MAX];
         snprintf(keychain_added_dir, PATH_MAX, "%s/keychain/cacerts-added", misc_dir);
@@ -227,7 +117,7 @@
                     if ((name[1] == '.') && (name[2] == 0)) continue;
                 }
 
-                uint32_t user_id = atoi(name);
+                uint32_t user_id = std::stoi(name);
 
                 // /data/misc/user/<user_id>
                 if (ensure_config_user_dirs(user_id) == -1) {
@@ -281,7 +171,7 @@
     return res;
 }
 
-static int log_callback(int type, const char *fmt, ...) {
+static int log_callback(int type, const char *fmt, ...) { // NOLINT
     va_list ap;
     int priority;
 
diff --git a/cmds/installd/installd_constants.h b/cmds/installd/installd_constants.h
index 2597c79..c928631 100644
--- a/cmds/installd/installd_constants.h
+++ b/cmds/installd/installd_constants.h
@@ -49,6 +49,12 @@
 constexpr int DEXOPT_FORCE          = 1 << 6;
 constexpr int DEXOPT_STORAGE_CE     = 1 << 7;
 constexpr int DEXOPT_STORAGE_DE     = 1 << 8;
+// Tells the compiler that it is invoked from the background service.  This
+// controls whether extra debugging flags can be used (taking more compile time.)
+constexpr int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+constexpr int DEXOPT_ENABLE_HIDDEN_API_CHECKS = 1 << 10;
+constexpr int DEXOPT_GENERATE_COMPACT_DEX = 1 << 11;
+constexpr int DEXOPT_GENERATE_APP_IMAGE = 1 << 12;
 
 /* all known values for dexopt flags */
 constexpr int DEXOPT_MASK =
@@ -59,7 +65,11 @@
     | DEXOPT_SECONDARY_DEX
     | DEXOPT_FORCE
     | DEXOPT_STORAGE_CE
-    | DEXOPT_STORAGE_DE;
+    | DEXOPT_STORAGE_DE
+    | DEXOPT_IDLE_BACKGROUND_JOB
+    | DEXOPT_ENABLE_HIDDEN_API_CHECKS
+    | DEXOPT_GENERATE_COMPACT_DEX
+    | DEXOPT_GENERATE_APP_IMAGE;
 
 // NOTE: keep in sync with StorageManager
 constexpr int FLAG_STORAGE_DE = 1 << 0;
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 09e1a00..96d8c47 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -43,6 +43,7 @@
 #include "globals.h"
 #include "installd_constants.h"
 #include "installd_deps.h"  // Need to fill in requirements of commands.
+#include "otapreopt_parameters.h"
 #include "otapreopt_utils.h"
 #include "system_properties.h"
 #include "utils.h"
@@ -78,8 +79,13 @@
 static_assert(DEXOPT_FORCE          == 1 << 6, "DEXOPT_FORCE unexpected.");
 static_assert(DEXOPT_STORAGE_CE     == 1 << 7, "DEXOPT_STORAGE_CE unexpected.");
 static_assert(DEXOPT_STORAGE_DE     == 1 << 8, "DEXOPT_STORAGE_DE unexpected.");
+static_assert(DEXOPT_ENABLE_HIDDEN_API_CHECKS == 1 << 10,
+        "DEXOPT_ENABLE_HIDDEN_API_CHECKS unexpected");
+static_assert(DEXOPT_GENERATE_COMPACT_DEX == 1 << 11, "DEXOPT_GENERATE_COMPACT_DEX unexpected");
+static_assert(DEXOPT_GENERATE_APP_IMAGE == 1 << 12, "DEXOPT_GENERATE_APP_IMAGE unexpected");
 
-static_assert(DEXOPT_MASK           == 0x1fe, "DEXOPT_MASK unexpected.");
+static_assert(DEXOPT_MASK           == (0x1dfe | DEXOPT_IDLE_BACKGROUND_JOB),
+              "DEXOPT_MASK unexpected.");
 
 
 
@@ -146,41 +152,25 @@
                 return 0;
             }
             // Copy in the default value.
-            strncpy(value, default_value, kPropertyValueMax - 1);
+            strlcpy(value, default_value, kPropertyValueMax - 1);
             value[kPropertyValueMax - 1] = 0;
             return strlen(default_value);// TODO: Need to truncate?
         }
-        size_t size = std::min(kPropertyValueMax - 1, prop_value->length());
-        strncpy(value, prop_value->data(), size);
-        value[size] = 0;
-        return static_cast<int>(size);
+        size_t size = std::min(kPropertyValueMax - 1, prop_value->length()) + 1;
+        strlcpy(value, prop_value->data(), size);
+        return static_cast<int>(size - 1);
     }
 
     std::string GetOTADataDirectory() const {
-        return StringPrintf("%s/%s", GetOtaDirectoryPrefix().c_str(), target_slot_.c_str());
+        return StringPrintf("%s/%s", GetOtaDirectoryPrefix().c_str(), GetTargetSlot().c_str());
     }
 
     const std::string& GetTargetSlot() const {
-        return target_slot_;
+        return parameters_.target_slot;
     }
 
 private:
 
-    struct Parameters {
-        const char *apk_path;
-        uid_t uid;
-        const char *pkgName;
-        const char *instruction_set;
-        int dexopt_needed;
-        const char* oat_dir;
-        int dexopt_flags;
-        const char* compiler_filter;
-        const char* volume_uuid;
-        const char* shared_libraries;
-        const char* se_info;
-        bool downgrade;
-    };
-
     bool ReadSystemProperties() {
         static constexpr const char* kPropertyFiles[] = {
                 "/default.prop", "/system/build.prop"
@@ -304,338 +294,7 @@
     }
 
     bool ReadArguments(int argc, char** argv) {
-        // Expected command line:
-        //   target-slot [version] dexopt {DEXOPT_PARAMETERS}
-
-        const char* target_slot_arg = argv[1];
-        if (target_slot_arg == nullptr) {
-            LOG(ERROR) << "Missing parameters";
-            return false;
-        }
-        // Sanitize value. Only allow (a-zA-Z0-9_)+.
-        target_slot_ = target_slot_arg;
-        if (!ValidateTargetSlotSuffix(target_slot_)) {
-            LOG(ERROR) << "Target slot suffix not legal: " << target_slot_;
-            return false;
-        }
-
-        // Check for version or "dexopt" next.
-        if (argv[2] == nullptr) {
-            LOG(ERROR) << "Missing parameters";
-            return false;
-        }
-
-        if (std::string("dexopt").compare(argv[2]) == 0) {
-            // This is version 1 (N) or pre-versioning version 2.
-            constexpr int kV2ArgCount =   1   // "otapreopt"
-                                        + 1   // slot
-                                        + 1   // "dexopt"
-                                        + 1   // apk_path
-                                        + 1   // uid
-                                        + 1   // pkg
-                                        + 1   // isa
-                                        + 1   // dexopt_needed
-                                        + 1   // oat_dir
-                                        + 1   // dexopt_flags
-                                        + 1   // filter
-                                        + 1   // volume
-                                        + 1   // libs
-                                        + 1;  // seinfo
-            if (argc == kV2ArgCount) {
-                return ReadArgumentsV2(argc, argv, false);
-            } else {
-                return ReadArgumentsV1(argc, argv);
-            }
-        }
-
-        uint32_t version;
-        if (!ParseUInt(argv[2], &version)) {
-            LOG(ERROR) << "Could not parse version: " << argv[2];
-            return false;
-        }
-
-        switch (version) {
-            case 2:
-                return ReadArgumentsV2(argc, argv, true);
-            case 3:
-                return ReadArgumentsV3(argc, argv);
-
-            default:
-                LOG(ERROR) << "Unsupported version " << version;
-                return false;
-        }
-    }
-
-    bool ReadArgumentsV2(int argc ATTRIBUTE_UNUSED, char** argv, bool versioned) {
-        size_t dexopt_index = versioned ? 3 : 2;
-
-        // Check for "dexopt".
-        if (argv[dexopt_index] == nullptr) {
-            LOG(ERROR) << "Missing parameters";
-            return false;
-        }
-        if (std::string("dexopt").compare(argv[dexopt_index]) != 0) {
-            LOG(ERROR) << "Expected \"dexopt\"";
-            return false;
-        }
-
-        size_t param_index = 0;
-        for (;; ++param_index) {
-            const char* param = argv[dexopt_index + 1 + param_index];
-            if (param == nullptr) {
-                break;
-            }
-
-            switch (param_index) {
-                case 0:
-                    package_parameters_.apk_path = param;
-                    break;
-
-                case 1:
-                    package_parameters_.uid = atoi(param);
-                    break;
-
-                case 2:
-                    package_parameters_.pkgName = param;
-                    break;
-
-                case 3:
-                    package_parameters_.instruction_set = param;
-                    break;
-
-                case 4:
-                    package_parameters_.dexopt_needed = atoi(param);
-                    break;
-
-                case 5:
-                    package_parameters_.oat_dir = param;
-                    break;
-
-                case 6:
-                    package_parameters_.dexopt_flags = atoi(param);
-                    break;
-
-                case 7:
-                    package_parameters_.compiler_filter = param;
-                    break;
-
-                case 8:
-                    package_parameters_.volume_uuid = ParseNull(param);
-                    break;
-
-                case 9:
-                    package_parameters_.shared_libraries = ParseNull(param);
-                    break;
-
-                case 10:
-                    package_parameters_.se_info = ParseNull(param);
-                    break;
-
-                default:
-                    LOG(ERROR) << "Too many arguments, got " << param;
-                    return false;
-            }
-        }
-
-        // Set downgrade to false. It is only relevant when downgrading compiler
-        // filter, which is not the case during ota.
-        package_parameters_.downgrade = false;
-
-        if (param_index != 11) {
-            LOG(ERROR) << "Not enough parameters";
-            return false;
-        }
-
-        return true;
-    }
-
-    bool ReadArgumentsV3(int argc ATTRIBUTE_UNUSED, char** argv) {
-        size_t dexopt_index = 3;
-
-        // Check for "dexopt".
-        if (argv[dexopt_index] == nullptr) {
-            LOG(ERROR) << "Missing parameters";
-            return false;
-        }
-        if (std::string("dexopt").compare(argv[dexopt_index]) != 0) {
-            LOG(ERROR) << "Expected \"dexopt\"";
-            return false;
-        }
-
-        size_t param_index = 0;
-        for (;; ++param_index) {
-            const char* param = argv[dexopt_index + 1 + param_index];
-            if (param == nullptr) {
-                break;
-            }
-
-            switch (param_index) {
-                case 0:
-                    package_parameters_.apk_path = param;
-                    break;
-
-                case 1:
-                    package_parameters_.uid = atoi(param);
-                    break;
-
-                case 2:
-                    package_parameters_.pkgName = param;
-                    break;
-
-                case 3:
-                    package_parameters_.instruction_set = param;
-                    break;
-
-                case 4:
-                    package_parameters_.dexopt_needed = atoi(param);
-                    break;
-
-                case 5:
-                    package_parameters_.oat_dir = param;
-                    break;
-
-                case 6:
-                    package_parameters_.dexopt_flags = atoi(param);
-                    break;
-
-                case 7:
-                    package_parameters_.compiler_filter = param;
-                    break;
-
-                case 8:
-                    package_parameters_.volume_uuid = ParseNull(param);
-                    break;
-
-                case 9:
-                    package_parameters_.shared_libraries = ParseNull(param);
-                    break;
-
-                case 10:
-                    package_parameters_.se_info = ParseNull(param);
-                    break;
-
-                case 11:
-                    package_parameters_.downgrade = ParseBool(param);
-                    break;
-
-                default:
-                    LOG(ERROR) << "Too many arguments, got " << param;
-                    return false;
-            }
-        }
-
-        if (param_index != 12) {
-            LOG(ERROR) << "Not enough parameters";
-            return false;
-        }
-
-        return true;
-    }
-
-    static int ReplaceMask(int input, int old_mask, int new_mask) {
-        return (input & old_mask) != 0 ? new_mask : 0;
-    }
-
-    bool ReadArgumentsV1(int argc ATTRIBUTE_UNUSED, char** argv) {
-        // Check for "dexopt".
-        if (argv[2] == nullptr) {
-            LOG(ERROR) << "Missing parameters";
-            return false;
-        }
-        if (std::string("dexopt").compare(argv[2]) != 0) {
-            LOG(ERROR) << "Expected \"dexopt\"";
-            return false;
-        }
-
-        size_t param_index = 0;
-        for (;; ++param_index) {
-            const char* param = argv[3 + param_index];
-            if (param == nullptr) {
-                break;
-            }
-
-            switch (param_index) {
-                case 0:
-                    package_parameters_.apk_path = param;
-                    break;
-
-                case 1:
-                    package_parameters_.uid = atoi(param);
-                    break;
-
-                case 2:
-                    package_parameters_.pkgName = param;
-                    break;
-
-                case 3:
-                    package_parameters_.instruction_set = param;
-                    break;
-
-                case 4: {
-                    // Version 1 had:
-                    //   DEXOPT_DEX2OAT_NEEDED       = 1
-                    //   DEXOPT_PATCHOAT_NEEDED      = 2
-                    //   DEXOPT_SELF_PATCHOAT_NEEDED = 3
-                    // We will simply use DEX2OAT_FROM_SCRATCH.
-                    package_parameters_.dexopt_needed = DEX2OAT_FROM_SCRATCH;
-                    break;
-                }
-
-                case 5:
-                    package_parameters_.oat_dir = param;
-                    break;
-
-                case 6: {
-                    // Version 1 had:
-                    constexpr int OLD_DEXOPT_PUBLIC         = 1 << 1;
-                    // Note: DEXOPT_SAFEMODE has been removed.
-                    // constexpr int OLD_DEXOPT_SAFEMODE       = 1 << 2;
-                    constexpr int OLD_DEXOPT_DEBUGGABLE     = 1 << 3;
-                    constexpr int OLD_DEXOPT_BOOTCOMPLETE   = 1 << 4;
-                    constexpr int OLD_DEXOPT_PROFILE_GUIDED = 1 << 5;
-                    constexpr int OLD_DEXOPT_OTA            = 1 << 6;
-                    int input = atoi(param);
-                    package_parameters_.dexopt_flags =
-                            ReplaceMask(input, OLD_DEXOPT_PUBLIC, DEXOPT_PUBLIC) |
-                            ReplaceMask(input, OLD_DEXOPT_DEBUGGABLE, DEXOPT_DEBUGGABLE) |
-                            ReplaceMask(input, OLD_DEXOPT_BOOTCOMPLETE, DEXOPT_BOOTCOMPLETE) |
-                            ReplaceMask(input, OLD_DEXOPT_PROFILE_GUIDED, DEXOPT_PROFILE_GUIDED) |
-                            ReplaceMask(input, OLD_DEXOPT_OTA, 0);
-                    break;
-                }
-
-                case 7:
-                    package_parameters_.compiler_filter = param;
-                    break;
-
-                case 8:
-                    package_parameters_.volume_uuid = ParseNull(param);
-                    break;
-
-                case 9:
-                    package_parameters_.shared_libraries = ParseNull(param);
-                    break;
-
-                default:
-                    LOG(ERROR) << "Too many arguments, got " << param;
-                    return false;
-            }
-        }
-
-        if (param_index != 10) {
-            LOG(ERROR) << "Not enough parameters";
-            return false;
-        }
-
-        // Set se_info to null. It is only relevant for secondary dex files, which we won't
-        // receive from a v1 A side.
-        package_parameters_.se_info = nullptr;
-
-        // Set downgrade to false. It is only relevant when downgrading compiler
-        // filter, which is not the case during ota.
-        package_parameters_.downgrade = false;
-
-        return true;
+        return parameters_.ReadArguments(argc, const_cast<const char**>(argv));
     }
 
     void PrepareEnvironment() {
@@ -651,11 +310,11 @@
     // Ensure that we have the right boot image. The first time any app is
     // compiled, we'll try to generate it.
     bool PrepareBootImage(bool force) const {
-        if (package_parameters_.instruction_set == nullptr) {
+        if (parameters_.instruction_set == nullptr) {
             LOG(ERROR) << "Instruction set missing.";
             return false;
         }
-        const char* isa = package_parameters_.instruction_set;
+        const char* isa = parameters_.instruction_set;
 
         // Check whether the file exists where expected.
         std::string dalvik_cache = GetOTADataDirectory() + "/" + DALVIK_CACHE;
@@ -696,7 +355,7 @@
 
         std::string preopted_boot_art_path = StringPrintf("/system/framework/%s/boot.art", isa);
         if (access(preopted_boot_art_path.c_str(), F_OK) == 0) {
-          return PatchoatBootImage(art_path, isa);
+          return PatchoatBootImage(isa_path, isa);
         } else {
           // No preopted boot image. Try to compile.
           return Dex2oatBootImage(boot_classpath_, art_path, oat_path, isa);
@@ -765,14 +424,14 @@
         CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory.";
     }
 
-    bool PatchoatBootImage(const std::string& art_path, const char* isa) const {
+    bool PatchoatBootImage(const std::string& output_dir, const char* isa) const {
         // This needs to be kept in sync with ART, see art/runtime/gc/space/image_space.cc.
 
         std::vector<std::string> cmd;
         cmd.push_back("/system/bin/patchoat");
 
         cmd.push_back("--input-image-location=/system/framework/boot.art");
-        cmd.push_back(StringPrintf("--output-image-file=%s", art_path.c_str()));
+        cmd.push_back(StringPrintf("--output-image-directory=%s", output_dir.c_str()));
 
         cmd.push_back(StringPrintf("--instruction-set=%s", isa));
 
@@ -879,17 +538,18 @@
         //       jar content must be exactly the same).
 
         //       (This is ugly as it's the only thing where we need to understand the contents
-        //        of package_parameters_, but it beats postponing the decision or using the call-
+        //        of parameters_, but it beats postponing the decision or using the call-
         //        backs to do weird things.)
-        const char* apk_path = package_parameters_.apk_path;
+        const char* apk_path = parameters_.apk_path;
         CHECK(apk_path != nullptr);
-        if (StartsWith(apk_path, android_root_.c_str())) {
+        if (StartsWith(apk_path, android_root_)) {
             const char* last_slash = strrchr(apk_path, '/');
             if (last_slash != nullptr) {
                 std::string path(apk_path, last_slash - apk_path + 1);
                 CHECK(EndsWith(path, "/"));
                 path = path + "oat";
                 if (access(path.c_str(), F_OK) == 0) {
+                    LOG(INFO) << "Skipping A/B OTA preopt of already preopted package " << apk_path;
                     return true;
                 }
             }
@@ -901,27 +561,34 @@
         // this tool will wipe the OTA artifact cache and try again (for robustness after
         // a failed OTA with remaining cache artifacts).
         if (access(apk_path, F_OK) != 0) {
-            LOG(WARNING) << "Skipping preopt of non-existing package " << apk_path;
+            LOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path;
             return true;
         }
 
         return false;
     }
 
-    // Run dexopt with the parameters of package_parameters_.
+    // Run dexopt with the parameters of parameters_.
+    // TODO(calin): embed the profile name in the parameters.
     int Dexopt() {
-        return dexopt(package_parameters_.apk_path,
-                      package_parameters_.uid,
-                      package_parameters_.pkgName,
-                      package_parameters_.instruction_set,
-                      package_parameters_.dexopt_needed,
-                      package_parameters_.oat_dir,
-                      package_parameters_.dexopt_flags,
-                      package_parameters_.compiler_filter,
-                      package_parameters_.volume_uuid,
-                      package_parameters_.shared_libraries,
-                      package_parameters_.se_info,
-                      package_parameters_.downgrade);
+        std::string dummy;
+        return dexopt(parameters_.apk_path,
+                      parameters_.uid,
+                      parameters_.pkgName,
+                      parameters_.instruction_set,
+                      parameters_.dexopt_needed,
+                      parameters_.oat_dir,
+                      parameters_.dexopt_flags,
+                      parameters_.compiler_filter,
+                      parameters_.volume_uuid,
+                      parameters_.shared_libraries,
+                      parameters_.se_info,
+                      parameters_.downgrade,
+                      parameters_.target_sdk_version,
+                      parameters_.profile_name,
+                      parameters_.dex_metadata_path,
+                      parameters_.compilation_reason,
+                      &dummy);
     }
 
     int RunPreopt() {
@@ -952,12 +619,12 @@
 
         // If this was a profile-guided run, we may have profile version issues. Try to downgrade,
         // if possible.
-        if ((package_parameters_.dexopt_flags & DEXOPT_PROFILE_GUIDED) == 0) {
+        if ((parameters_.dexopt_flags & DEXOPT_PROFILE_GUIDED) == 0) {
             return dexopt_result;
         }
 
         LOG(WARNING) << "Downgrading compiler filter in an attempt to progress compilation";
-        package_parameters_.dexopt_flags &= ~DEXOPT_PROFILE_GUIDED;
+        parameters_.dexopt_flags &= ~DEXOPT_PROFILE_GUIDED;
         return Dexopt();
     }
 
@@ -1082,13 +749,12 @@
     SystemProperties system_properties_;
 
     // Some select properties that are always needed.
-    std::string target_slot_;
     std::string android_root_;
     std::string android_data_;
     std::string boot_classpath_;
     std::string asec_mountpoint_;
 
-    Parameters package_parameters_;
+    OTAPreoptParameters parameters_;
 
     // Store environment values we need to set.
     std::vector<std::string> environ_;
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index 2030997..e90cf3b 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -59,6 +59,12 @@
 // The file descriptor denoted by status-fd will be closed. The rest of the parameters will
 // be passed on to otapreopt in the chroot.
 static int otapreopt_chroot(const int argc, char **arg) {
+    // Validate arguments
+    // We need the command, status channel and target slot, at a minimum.
+    if(argc < 3) {
+        PLOG(ERROR) << "Not enough arguments.";
+        exit(208);
+    }
     // Close all file descriptors. They are coming from the caller, we do not want to pass them
     // on across our fork/exec into a different domain.
     // 1) Default descriptors.
@@ -108,14 +114,29 @@
         LOG(ERROR) << "Target slot suffix not legal: " << arg[2];
         exit(207);
     }
-    std::string vendor_partition = StringPrintf("/dev/block/bootdevice/by-name/vendor%s",
-                                                arg[2]);
-    int vendor_result = mount(vendor_partition.c_str(),
-                              "/postinstall/vendor",
-                              "ext4",
-                              MS_RDONLY,
-                              /* data */ nullptr);
-    UNUSED(vendor_result);
+    {
+      std::string vendor_partition = StringPrintf("/dev/block/by-name/vendor%s",
+                                                  arg[2]);
+      int vendor_result = mount(vendor_partition.c_str(),
+                                "/postinstall/vendor",
+                                "ext4",
+                                MS_RDONLY,
+                                /* data */ nullptr);
+      UNUSED(vendor_result);
+    }
+
+    // Try to mount the product partition. update_engine doesn't do this for us, but we
+    // want it for product APKs. Same notes as vendor above.
+    {
+      std::string product_partition = StringPrintf("/dev/block/by-name/product%s",
+                                                   arg[2]);
+      int product_result = mount(product_partition.c_str(),
+                                 "/postinstall/product",
+                                 "ext4",
+                                 MS_RDONLY,
+                                 /* data */ nullptr);
+      UNUSED(product_result);
+    }
 
     // Chdir into /postinstall.
     if (chdir("/postinstall") != 0) {
diff --git a/cmds/installd/otapreopt_parameters.cpp b/cmds/installd/otapreopt_parameters.cpp
new file mode 100644
index 0000000..cf3de01
--- /dev/null
+++ b/cmds/installd/otapreopt_parameters.cpp
@@ -0,0 +1,367 @@
+/*
+ ** Copyright 2016, 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 "otapreopt_parameters.h"
+
+#include <android-base/logging.h>
+
+#include "dexopt.h"
+#include "installd_constants.h"
+#include "otapreopt_utils.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "otapreopt"
+#endif
+
+namespace android {
+namespace installd {
+
+static bool ParseBool(const char* in) {
+    if (strcmp(in, "true") == 0) {
+        return true;
+    }
+    return false;
+}
+
+static const char* ParseNull(const char* arg) {
+    return (strcmp(arg, "!") == 0) ? nullptr : arg;
+}
+
+static bool ParseUInt(const char* in, uint32_t* out) {
+    char* end;
+    long long int result = strtoll(in, &end, 0);
+    if (in == end || *end != '\0') {
+        return false;
+    }
+    if (result < std::numeric_limits<uint32_t>::min() ||
+            std::numeric_limits<uint32_t>::max() < result) {
+        return false;
+    }
+    *out = static_cast<uint32_t>(result);
+    return true;
+}
+
+bool OTAPreoptParameters::ReadArguments(int argc, const char** argv) {
+    // Expected command line:
+    //   target-slot [version] dexopt {DEXOPT_PARAMETERS}
+
+    const char* target_slot_arg = argv[1];
+    if (target_slot_arg == nullptr) {
+        LOG(ERROR) << "Missing parameters";
+        return false;
+    }
+    // Sanitize value. Only allow (a-zA-Z0-9_)+.
+    target_slot = target_slot_arg;
+    if (!ValidateTargetSlotSuffix(target_slot)) {
+        LOG(ERROR) << "Target slot suffix not legal: " << target_slot;
+        return false;
+    }
+
+    // Check for version or "dexopt" next.
+    if (argv[2] == nullptr) {
+        LOG(ERROR) << "Missing parameters";
+        return false;
+    }
+
+    if (std::string("dexopt").compare(argv[2]) == 0) {
+        // This is version 1 (N) or pre-versioning version 2.
+        constexpr int kV2ArgCount =   1   // "otapreopt"
+                                    + 1   // slot
+                                    + 1   // "dexopt"
+                                    + 1   // apk_path
+                                    + 1   // uid
+                                    + 1   // pkg
+                                    + 1   // isa
+                                    + 1   // dexopt_needed
+                                    + 1   // oat_dir
+                                    + 1   // dexopt_flags
+                                    + 1   // filter
+                                    + 1   // volume
+                                    + 1   // libs
+                                    + 1;  // seinfo
+        if (argc == kV2ArgCount) {
+            return ReadArgumentsPostV1(2, argv, false);
+        } else {
+            return ReadArgumentsV1(argv);
+        }
+    }
+
+    uint32_t version;
+    if (!ParseUInt(argv[2], &version)) {
+        LOG(ERROR) << "Could not parse version: " << argv[2];
+        return false;
+    }
+
+    return ReadArgumentsPostV1(version, argv, true);
+}
+
+static int ReplaceMask(int input, int old_mask, int new_mask) {
+    return (input & old_mask) != 0 ? new_mask : 0;
+}
+
+void OTAPreoptParameters::SetDefaultsForPostV1Arguments() {
+    // Set se_info to null. It is only relevant for secondary dex files, which we won't
+    // receive from a v1 A side.
+    se_info = nullptr;
+
+    // Set downgrade to false. It is only relevant when downgrading compiler
+    // filter, which is not the case during ota.
+    downgrade = false;
+
+    // Set target_sdk_version to 0, ie the platform SDK version. This is
+    // conservative and may force some classes to verify at runtime.
+    target_sdk_version = 0;
+
+    // Set the profile name to the primary apk profile.
+    profile_name = "primary.prof";
+
+    // By default we don't have a dex metadata file.
+    dex_metadata_path = nullptr;
+
+    // The compilation reason is ab-ota (match the system property pm.dexopt.ab-ota)
+    compilation_reason = "ab-ota";
+
+    // Flag is enabled by default for A/B otas.
+    dexopt_flags = DEXOPT_GENERATE_COMPACT_DEX;
+}
+
+bool OTAPreoptParameters::ReadArgumentsV1(const char** argv) {
+    // Check for "dexopt".
+    if (argv[2] == nullptr) {
+        LOG(ERROR) << "Missing parameters";
+        return false;
+    }
+    if (std::string("dexopt").compare(argv[2]) != 0) {
+        LOG(ERROR) << "Expected \"dexopt\" but found: " << argv[2];
+        return false;
+    }
+
+    SetDefaultsForPostV1Arguments();
+
+    size_t param_index = 0;
+    for (;; ++param_index) {
+        const char* param = argv[3 + param_index];
+        if (param == nullptr) {
+            break;
+        }
+
+        switch (param_index) {
+            case 0:
+                apk_path = param;
+                break;
+
+            case 1:
+                uid = atoi(param);
+                break;
+
+            case 2:
+                pkgName = param;
+                break;
+
+            case 3:
+                instruction_set = param;
+                break;
+
+            case 4: {
+                // Version 1 had:
+                //   DEXOPT_DEX2OAT_NEEDED       = 1
+                //   DEXOPT_PATCHOAT_NEEDED      = 2
+                //   DEXOPT_SELF_PATCHOAT_NEEDED = 3
+                // We will simply use DEX2OAT_FROM_SCRATCH.
+                dexopt_needed = DEX2OAT_FROM_SCRATCH;
+                break;
+            }
+
+            case 5:
+                oat_dir = param;
+                break;
+
+            case 6: {
+                // Version 1 had:
+                constexpr int OLD_DEXOPT_PUBLIC         = 1 << 1;
+                // Note: DEXOPT_SAFEMODE has been removed.
+                // constexpr int OLD_DEXOPT_SAFEMODE       = 1 << 2;
+                constexpr int OLD_DEXOPT_DEBUGGABLE     = 1 << 3;
+                constexpr int OLD_DEXOPT_BOOTCOMPLETE   = 1 << 4;
+                constexpr int OLD_DEXOPT_PROFILE_GUIDED = 1 << 5;
+                constexpr int OLD_DEXOPT_OTA            = 1 << 6;
+                static_assert(DEXOPT_GENERATE_COMPACT_DEX > OLD_DEXOPT_OTA, "must not overlap");
+                int input = atoi(param);
+                dexopt_flags |=
+                        ReplaceMask(input, OLD_DEXOPT_PUBLIC, DEXOPT_PUBLIC) |
+                        ReplaceMask(input, OLD_DEXOPT_DEBUGGABLE, DEXOPT_DEBUGGABLE) |
+                        ReplaceMask(input, OLD_DEXOPT_BOOTCOMPLETE, DEXOPT_BOOTCOMPLETE) |
+                        ReplaceMask(input, OLD_DEXOPT_PROFILE_GUIDED, DEXOPT_PROFILE_GUIDED) |
+                        ReplaceMask(input, OLD_DEXOPT_OTA, 0);
+                break;
+            }
+
+            case 7:
+                compiler_filter = param;
+                break;
+
+            case 8:
+                volume_uuid = ParseNull(param);
+                break;
+
+            case 9:
+                shared_libraries = ParseNull(param);
+                break;
+
+            default:
+                LOG(ERROR) << "Too many arguments, got " << param;
+                return false;
+        }
+    }
+
+    if (param_index != 10) {
+        LOG(ERROR) << "Not enough parameters";
+        return false;
+    }
+
+    return true;
+}
+
+bool OTAPreoptParameters::ReadArgumentsPostV1(uint32_t version, const char** argv, bool versioned) {
+    size_t num_args_expected = 0;
+    switch (version) {
+        case 2: num_args_expected = 11; break;
+        case 3: num_args_expected = 12; break;
+        case 4: num_args_expected = 13; break;
+        case 5: num_args_expected = 14; break;
+        case 6: num_args_expected = 15; break;
+        case 7:
+        // Version 8 adds a new dexopt flag: DEXOPT_GENERATE_COMPACT_DEX
+        case 8: num_args_expected = 16; break;
+        // Version 9 adds a new dexopt flag: DEXOPT_GENERATE_APP_IMAGE
+        case 9: num_args_expected = 16; break;
+        default:
+            LOG(ERROR) << "Don't know how to read arguments for version " << version;
+            return false;
+    }
+    size_t dexopt_index = versioned ? 3 : 2;
+
+    // Check for "dexopt".
+    if (argv[dexopt_index] == nullptr) {
+        LOG(ERROR) << "Missing parameters";
+        return false;
+    }
+    if (std::string("dexopt").compare(argv[dexopt_index]) != 0) {
+        LOG(ERROR) << "Expected \"dexopt\" but found: " << argv[dexopt_index];
+        return false;
+    }
+
+    // Validate the number of arguments.
+    size_t num_args_actual = 0;
+    while (argv[dexopt_index + 1 + num_args_actual] != nullptr) {
+        num_args_actual++;
+    }
+
+    if (num_args_actual != num_args_expected) {
+        LOG(ERROR) << "Invalid number of arguments. expected="
+                << num_args_expected << " actual=" << num_args_actual;
+        return false;
+    }
+
+    // The number of arguments is OK.
+    // Configure the default values for the parameters that were added after V1.
+    // The default values will be overwritten in case they are passed as arguments.
+    SetDefaultsForPostV1Arguments();
+
+    for (size_t param_index = 0; param_index < num_args_actual; ++param_index) {
+        const char* param = argv[dexopt_index + 1 + param_index];
+        switch (param_index) {
+            case 0:
+                apk_path = param;
+                break;
+
+            case 1:
+                uid = atoi(param);
+                break;
+
+            case 2:
+                pkgName = param;
+                break;
+
+            case 3:
+                instruction_set = param;
+                break;
+
+            case 4:
+                dexopt_needed = atoi(param);
+                break;
+
+            case 5:
+                oat_dir = param;
+                break;
+
+            case 6:
+                dexopt_flags = atoi(param);
+                // Add CompactDex generation flag for versions less than 8 since it wasn't passed
+                // from the package manager. Only conditionally set the flag here so that it can
+                // be fully controlled by the package manager.
+                dexopt_flags |= (version < 8) ? DEXOPT_GENERATE_COMPACT_DEX : 0u;
+                break;
+
+            case 7:
+                compiler_filter = param;
+                break;
+
+            case 8:
+                volume_uuid = ParseNull(param);
+                break;
+
+            case 9:
+                shared_libraries = ParseNull(param);
+                break;
+
+            case 10:
+                se_info = ParseNull(param);
+                break;
+
+            case 11:
+                downgrade = ParseBool(param);
+                break;
+
+            case 12:
+                target_sdk_version = atoi(param);
+                break;
+
+            case 13:
+                profile_name = ParseNull(param);
+                break;
+
+            case 14:
+                dex_metadata_path = ParseNull(param);
+                break;
+
+            case 15:
+                compilation_reason = ParseNull(param);
+                break;
+
+            default:
+                LOG(FATAL) << "Should not get here. Did you call ReadArguments "
+                        << "with the right expectation? index=" << param_index
+                        << " num_args=" << num_args_actual;
+                return false;
+        }
+    }
+
+    return true;
+}
+
+}  // namespace installd
+}  // namespace android
diff --git a/cmds/installd/otapreopt_parameters.h b/cmds/installd/otapreopt_parameters.h
new file mode 100644
index 0000000..a2f6e44
--- /dev/null
+++ b/cmds/installd/otapreopt_parameters.h
@@ -0,0 +1,61 @@
+/*
+ ** Copyright 2018, 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 OTAPREOPT_PARAMETERS_H_
+#define OTAPREOPT_PARAMETERS_H_
+
+#include <string>
+#include <sys/types.h>
+
+namespace android {
+namespace installd {
+
+class OTAPreoptParameters {
+  public:
+    bool ReadArguments(int argc, const char** argv);
+
+  private:
+    bool ReadArgumentsV1(const char** argv);
+    bool ReadArgumentsPostV1(uint32_t version, const char** argv, bool versioned);
+
+    void SetDefaultsForPostV1Arguments();
+    const char* apk_path;
+    uid_t uid;
+    const char* pkgName;
+    const char* instruction_set;
+    int dexopt_needed;
+    const char* oat_dir;
+    int dexopt_flags;
+    const char* compiler_filter;
+    const char* volume_uuid;
+    const char* shared_libraries;
+    const char* se_info;
+    bool downgrade;
+    int target_sdk_version;
+    const char* profile_name;
+    const char* dex_metadata_path;
+    const char* compilation_reason;
+
+    std::string target_slot;
+
+    friend class OTAPreoptService;
+    friend class OTAPreoptTest;
+};
+
+}  // namespace installd
+}  // namespace android
+
+#endif  //  OTAPREOPT_PARAMETERS_H_
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index 630c1f3..7438d3d 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -3,15 +3,16 @@
     name: "installd_utils_test",
     clang: true,
     srcs: ["installd_utils_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
     shared_libs: [
         "libbase",
-        "liblog",
         "libutils",
         "libcutils",
     ],
     static_libs: [
-        "libinstalld",
         "libdiskusage",
+        "libinstalld",
+        "liblog",
     ],
 }
 
@@ -19,18 +20,20 @@
     name: "installd_cache_test",
     clang: true,
     srcs: ["installd_cache_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
     shared_libs: [
         "libbase",
         "libbinder",
+        "libcrypto",
         "libcutils",
-        "liblog",
-        "liblogwrap",
         "libselinux",
         "libutils",
     ],
     static_libs: [
-        "libinstalld",
         "libdiskusage",
+        "libinstalld",
+        "liblog",
+        "liblogwrap",
     ],
 }
 
@@ -38,17 +41,57 @@
     name: "installd_service_test",
     clang: true,
     srcs: ["installd_service_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
     shared_libs: [
         "libbase",
         "libbinder",
+        "libcrypto",
         "libcutils",
-        "liblog",
-        "liblogwrap",
         "libselinux",
         "libutils",
     ],
     static_libs: [
-        "libinstalld",
         "libdiskusage",
+        "libinstalld",
+        "liblog",
+        "liblogwrap",
     ],
 }
+
+cc_test {
+    name: "installd_dexopt_test",
+    clang: true,
+    srcs: ["installd_dexopt_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libcrypto",
+        "libcutils",
+        "libselinux",
+        "libutils",
+    ],
+    static_libs: [
+        "libdiskusage",
+        "libinstalld",
+        "liblog",
+        "liblogwrap",
+    ],
+}
+
+cc_test {
+    name: "installd_otapreopt_test",
+    clang: true,
+    srcs: ["installd_otapreopt_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libutils",
+    ],
+    static_libs: [
+        "liblog",
+        "libotapreoptparameters"
+    ],
+}
+
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
new file mode 100644
index 0000000..279bce8
--- /dev/null
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -0,0 +1,1064 @@
+/*
+ * Copyright (C) 2017 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 <cstdlib>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+#include <cutils/properties.h>
+
+#include <gtest/gtest.h>
+
+#include <selinux/android.h>
+#include <selinux/avc.h>
+
+#include "dexopt.h"
+#include "InstalldNativeService.h"
+#include "globals.h"
+#include "tests/test_utils.h"
+#include "utils.h"
+
+using android::base::ReadFully;
+using android::base::unique_fd;
+
+namespace android {
+namespace installd {
+
+// TODO(calin): try to dedup this code.
+#if defined(__arm__)
+static const std::string kRuntimeIsa = "arm";
+#elif defined(__aarch64__)
+static const std::string kRuntimeIsa = "arm64";
+#elif defined(__mips__) && !defined(__LP64__)
+static const std::string kRuntimeIsa = "mips";
+#elif defined(__mips__) && defined(__LP64__)
+static const std::string kRuntimeIsa = "mips64";
+#elif defined(__i386__)
+static const std::string kRuntimeIsa = "x86";
+#elif defined(__x86_64__)
+static const std::string kRuntimeIsa = "x86_64";
+#else
+static const std::string kRuntimeIsa = "none";
+#endif
+
+int get_property(const char *key, char *value, const char *default_value) {
+    return property_get(key, value, default_value);
+}
+
+bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir, const char *apk_path,
+        const char *instruction_set) {
+    return calculate_oat_file_path_default(path, oat_dir, apk_path, instruction_set);
+}
+
+bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char *apk_path,
+        const char *instruction_set) {
+    return calculate_odex_file_path_default(path, apk_path, instruction_set);
+}
+
+bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *instruction_set) {
+    return create_cache_path_default(path, src, instruction_set);
+}
+
+static void run_cmd(const std::string& cmd) {
+    system(cmd.c_str());
+}
+
+static void mkdir(const std::string& path, uid_t owner, gid_t group, mode_t mode) {
+    ::mkdir(path.c_str(), mode);
+    ::chown(path.c_str(), owner, group);
+    ::chmod(path.c_str(), mode);
+}
+
+static int log_callback(int type, const char *fmt, ...) { // NOLINT
+    va_list ap;
+    int priority;
+
+    switch (type) {
+        case SELINUX_WARNING:
+            priority = ANDROID_LOG_WARN;
+            break;
+        case SELINUX_INFO:
+            priority = ANDROID_LOG_INFO;
+            break;
+        default:
+            priority = ANDROID_LOG_ERROR;
+            break;
+    }
+    va_start(ap, fmt);
+    LOG_PRI_VA(priority, "SELinux", fmt, ap);
+    va_end(ap);
+    return 0;
+}
+
+static bool init_selinux() {
+    int selinux_enabled = (is_selinux_enabled() > 0);
+
+    union selinux_callback cb;
+    cb.func_log = log_callback;
+    selinux_set_callback(SELINUX_CB_LOG, cb);
+
+    if (selinux_enabled && selinux_status_open(true) < 0) {
+        LOG(ERROR) << "Could not open selinux status; exiting";
+        return false;
+    }
+
+    return true;
+}
+
+// Base64 encoding of a simple dex files with 2 methods.
+static const char kDexFile[] =
+    "UEsDBBQAAAAIAOiOYUs9y6BLCgEAABQCAAALABwAY2xhc3Nlcy5kZXhVVAkAA/Ns+lkOHv1ZdXgL"
+    "AAEEI+UCAASIEwAAS0mt4DIwNmX4qpn7j/2wA7v7N+ZvoQpCJRlVx5SWa4YaiDAxMBQwMDBUhJkI"
+    "MUBBDyMDAzsDRJwFxAdioBDDHAYEYAbiFUAM1M5wAIhFGCGKDIDYAogdgNgDiH2BOAiI0xghekDm"
+    "sQIxGxQzM6ACRijNhCbOhCZfyohdPYyuh8szgtVkMkLsLhAAqeCDi+ejibPZZOZlltgxsDnqZSWW"
+    "JTKwOUFoZh9HayDhZM0g5AMS0M9JzEvX90/KSk0usWZgDAMaws5nAyXBzmpoYGlgAjsAyJoBMp0b"
+    "zQ8gGhbOTEhhzYwU3qxIYc2GFN6MClC/AhUyKUDMAYU9M1Qc5F8GKBscVgIQM0FxCwBQSwECHgMU"
+    "AAAACADojmFLPcugSwoBAAAUAgAACwAYAAAAAAAAAAAAoIEAAAAAY2xhc3Nlcy5kZXhVVAUAA/Ns"
+    "+ll1eAsAAQQj5QIABIgTAABQSwUGAAAAAAEAAQBRAAAATwEAAAAA";
+
+
+class DexoptTest : public testing::Test {
+protected:
+    static constexpr bool kDebug = false;
+    static constexpr uid_t kSystemUid = 1000;
+    static constexpr uid_t kSystemGid = 1000;
+    static constexpr int32_t kOSdkVersion = 25;
+    static constexpr int32_t kAppDataFlags = FLAG_STORAGE_CE | FLAG_STORAGE_DE;
+    static constexpr int32_t kTestUserId = 0;
+    static constexpr uid_t kTestAppId = 19999;
+
+    const gid_t kTestAppUid = multiuser_get_uid(kTestUserId, kTestAppId);
+    const uid_t kTestAppGid = multiuser_get_shared_gid(kTestUserId, kTestAppId);
+
+    InstalldNativeService* service_;
+    std::unique_ptr<std::string> volume_uuid_;
+    std::string package_name_;
+    std::string apk_path_;
+    std::string app_apk_dir_;
+    std::string app_private_dir_ce_;
+    std::string app_private_dir_de_;
+    std::string se_info_;
+    std::string app_oat_dir_;
+
+    int64_t ce_data_inode_;
+
+    std::string secondary_dex_ce_;
+    std::string secondary_dex_ce_link_;
+    std::string secondary_dex_de_;
+
+    virtual void SetUp() {
+        setenv("ANDROID_LOG_TAGS", "*:v", 1);
+        android::base::InitLogging(nullptr);
+        // Initialize the globals holding the file system main paths (/data/, /system/ etc..).
+        // This is needed in order to compute the application and profile paths.
+        ASSERT_TRUE(init_globals_from_data_and_root());
+        // Initialize selinux log callbacks.
+        // This ensures that selinux is up and running and re-directs the selinux messages
+        // to logcat (in order to make it easier to investigate test results).
+        ASSERT_TRUE(init_selinux());
+        service_ = new InstalldNativeService();
+
+        volume_uuid_ = nullptr;
+        package_name_ = "com.installd.test.dexopt";
+        se_info_ = "default";
+        app_apk_dir_ = android_app_dir + package_name_;
+
+        create_mock_app();
+    }
+
+    virtual void TearDown() {
+        if (!kDebug) {
+            service_->destroyAppData(
+                volume_uuid_, package_name_, kTestUserId, kAppDataFlags, ce_data_inode_);
+            run_cmd("rm -rf " + app_apk_dir_);
+            run_cmd("rm -rf " + app_private_dir_ce_);
+            run_cmd("rm -rf " + app_private_dir_de_);
+        }
+        delete service_;
+    }
+
+    void create_mock_app() {
+        // Create the oat dir.
+        app_oat_dir_ = app_apk_dir_ + "/oat";
+        mkdir(app_apk_dir_, kSystemUid, kSystemGid, 0755);
+        service_->createOatDir(app_oat_dir_, kRuntimeIsa);
+
+        // Copy the primary apk.
+        apk_path_ = app_apk_dir_ + "/base.jar";
+        ASSERT_TRUE(WriteBase64ToFile(kDexFile, apk_path_, kSystemUid, kSystemGid, 0644));
+
+        // Create the app user data.
+        ASSERT_TRUE(service_->createAppData(
+            volume_uuid_,
+            package_name_,
+            kTestUserId,
+            kAppDataFlags,
+            kTestAppUid,
+            se_info_,
+            kOSdkVersion,
+            &ce_data_inode_).isOk());
+
+        // Create a secondary dex file on CE storage
+        const char* volume_uuid_cstr = volume_uuid_ == nullptr ? nullptr : volume_uuid_->c_str();
+        app_private_dir_ce_ = create_data_user_ce_package_path(
+                volume_uuid_cstr, kTestUserId, package_name_.c_str());
+        secondary_dex_ce_ = app_private_dir_ce_ + "/secondary_ce.jar";
+        ASSERT_TRUE(WriteBase64ToFile(kDexFile, secondary_dex_ce_, kTestAppUid, kTestAppGid, 0600));
+        std::string app_private_dir_ce_link = create_data_user_ce_package_path_as_user_link(
+                volume_uuid_cstr, kTestUserId, package_name_.c_str());
+        secondary_dex_ce_link_ = app_private_dir_ce_link + "/secondary_ce.jar";
+
+        // Create a secondary dex file on DE storage.
+        app_private_dir_de_ = create_data_user_de_package_path(
+                volume_uuid_cstr, kTestUserId, package_name_.c_str());
+        secondary_dex_de_ = app_private_dir_de_ + "/secondary_de.jar";
+        ASSERT_TRUE(WriteBase64ToFile(kDexFile, secondary_dex_de_, kTestAppUid, kTestAppGid, 0600));
+
+        // Fix app data uid.
+        ASSERT_TRUE(service_->fixupAppData(volume_uuid_, kTestUserId).isOk());
+    }
+
+
+    std::string GetSecondaryDexArtifact(const std::string& path, const std::string& type) {
+        std::string::size_type end = path.rfind('.');
+        std::string::size_type start = path.rfind('/', end);
+        return path.substr(0, start) + "/oat/" + kRuntimeIsa + "/" +
+                path.substr(start + 1, end - start) + type;
+    }
+
+    void CompileSecondaryDex(const std::string& path, int32_t dex_storage_flag,
+            bool should_binder_call_succeed, bool should_dex_be_compiled = true,
+            /*out */ binder::Status* binder_result = nullptr, int32_t uid = -1) {
+        if (uid == -1) {
+            uid = kTestAppUid;
+        }
+        std::unique_ptr<std::string> package_name_ptr(new std::string(package_name_));
+        int32_t dexopt_needed = 0;  // does not matter;
+        std::unique_ptr<std::string> out_path = nullptr;  // does not matter
+        int32_t dex_flags = DEXOPT_SECONDARY_DEX | dex_storage_flag;
+        std::string compiler_filter = "speed-profile";
+        std::unique_ptr<std::string> class_loader_context_ptr(new std::string("&"));
+        std::unique_ptr<std::string> se_info_ptr(new std::string(se_info_));
+        bool downgrade = false;
+        int32_t target_sdk_version = 0;  // default
+        std::unique_ptr<std::string> profile_name_ptr = nullptr;
+        std::unique_ptr<std::string> dm_path_ptr = nullptr;
+        std::unique_ptr<std::string> compilation_reason_ptr = nullptr;
+
+        binder::Status result = service_->dexopt(path,
+                                                 uid,
+                                                 package_name_ptr,
+                                                 kRuntimeIsa,
+                                                 dexopt_needed,
+                                                 out_path,
+                                                 dex_flags,
+                                                 compiler_filter,
+                                                 volume_uuid_,
+                                                 class_loader_context_ptr,
+                                                 se_info_ptr,
+                                                 downgrade,
+                                                 target_sdk_version,
+                                                 profile_name_ptr,
+                                                 dm_path_ptr,
+                                                 compilation_reason_ptr);
+        ASSERT_EQ(should_binder_call_succeed, result.isOk()) << result.toString8().c_str();
+        int expected_access = should_dex_be_compiled ? 0 : -1;
+        std::string odex = GetSecondaryDexArtifact(path, "odex");
+        std::string vdex = GetSecondaryDexArtifact(path, "vdex");
+        std::string art = GetSecondaryDexArtifact(path, "art");
+        ASSERT_EQ(expected_access, access(odex.c_str(), R_OK));
+        ASSERT_EQ(expected_access, access(vdex.c_str(), R_OK));
+        ASSERT_EQ(-1, access(art.c_str(), R_OK));  // empty profiles do not generate an image.
+        if (binder_result != nullptr) {
+            *binder_result = result;
+        }
+    }
+
+    void reconcile_secondary_dex(const std::string& path, int32_t storage_flag,
+            bool should_binder_call_succeed, bool should_dex_exist, bool should_dex_be_deleted,
+            int32_t uid = -1, std::string* package_override = nullptr) {
+        if (uid == -1) {
+            uid = kTestAppUid;
+        }
+        std::vector<std::string> isas;
+        isas.push_back(kRuntimeIsa);
+        bool out_secondary_dex_exists = false;
+        binder::Status result = service_->reconcileSecondaryDexFile(
+            path,
+            package_override == nullptr ? package_name_ : *package_override,
+            uid,
+            isas,
+            volume_uuid_,
+            storage_flag,
+            &out_secondary_dex_exists);
+
+        ASSERT_EQ(should_binder_call_succeed, result.isOk()) << result.toString8().c_str();
+        ASSERT_EQ(should_dex_exist, out_secondary_dex_exists);
+
+        int expected_access = should_dex_be_deleted ? -1 : 0;
+        std::string odex = GetSecondaryDexArtifact(path, "odex");
+        std::string vdex = GetSecondaryDexArtifact(path, "vdex");
+        std::string art = GetSecondaryDexArtifact(path, "art");
+        ASSERT_EQ(expected_access, access(odex.c_str(), F_OK));
+        ASSERT_EQ(expected_access, access(vdex.c_str(), F_OK));
+        ASSERT_EQ(-1, access(art.c_str(), R_OK));  // empty profiles do not generate an image.
+    }
+
+    void CheckFileAccess(const std::string& file, uid_t uid, gid_t gid, mode_t mode) {
+        struct stat st;
+        ASSERT_EQ(0, stat(file.c_str(), &st));
+        ASSERT_EQ(uid, st.st_uid);
+        ASSERT_EQ(gid, st.st_gid);
+        ASSERT_EQ(mode, st.st_mode);
+    }
+
+    void CompilePrimaryDexOk(std::string compiler_filter,
+                             int32_t dex_flags,
+                             const char* oat_dir,
+                             int32_t uid,
+                             int32_t dexopt_needed,
+                             binder::Status* binder_result = nullptr,
+                             const char* dm_path = nullptr,
+                             bool downgrade = false) {
+        CompilePrimaryDex(compiler_filter,
+                          dex_flags,
+                          oat_dir,
+                          uid,
+                          dexopt_needed,
+                          dm_path,
+                          downgrade,
+                          true,
+                          binder_result);
+    }
+
+    void CompilePrimaryDexFail(std::string compiler_filter,
+                               int32_t dex_flags,
+                               const char* oat_dir,
+                               int32_t uid,
+                               int32_t dexopt_needed,
+                               binder::Status* binder_result = nullptr,
+                               const char* dm_path = nullptr,
+                               bool downgrade = false) {
+        CompilePrimaryDex(compiler_filter,
+                          dex_flags,
+                          oat_dir,
+                          uid,
+                          dexopt_needed,
+                          dm_path,
+                          downgrade,
+                          false,
+                          binder_result);
+    }
+
+    void CompilePrimaryDex(std::string compiler_filter,
+                           int32_t dex_flags,
+                           const char* oat_dir,
+                           int32_t uid,
+                           int32_t dexopt_needed,
+                           const char* dm_path,
+                           bool downgrade,
+                           bool should_binder_call_succeed,
+                           /*out */ binder::Status* binder_result) {
+        std::unique_ptr<std::string> package_name_ptr(new std::string(package_name_));
+        std::unique_ptr<std::string> out_path(
+                oat_dir == nullptr ? nullptr : new std::string(oat_dir));
+        std::unique_ptr<std::string> class_loader_context_ptr(new std::string("&"));
+        std::unique_ptr<std::string> se_info_ptr(new std::string(se_info_));
+        int32_t target_sdk_version = 0;  // default
+        std::unique_ptr<std::string> profile_name_ptr(new std::string("primary.prof"));
+        std::unique_ptr<std::string> dm_path_ptr = nullptr;
+        if (dm_path != nullptr) {
+            dm_path_ptr.reset(new std::string(dm_path));
+        }
+        std::unique_ptr<std::string> compilation_reason_ptr(new std::string("test-reason"));
+
+        bool prof_result;
+        binder::Status prof_binder_result = service_->prepareAppProfile(
+                package_name_, kTestUserId, kTestAppId, *profile_name_ptr, apk_path_,
+                /*dex_metadata*/ nullptr, &prof_result);
+
+        ASSERT_TRUE(prof_binder_result.isOk()) << prof_binder_result.toString8().c_str();
+        ASSERT_TRUE(prof_result);
+
+        binder::Status result = service_->dexopt(apk_path_,
+                                                 uid,
+                                                 package_name_ptr,
+                                                 kRuntimeIsa,
+                                                 dexopt_needed,
+                                                 out_path,
+                                                 dex_flags,
+                                                 compiler_filter,
+                                                 volume_uuid_,
+                                                 class_loader_context_ptr,
+                                                 se_info_ptr,
+                                                 downgrade,
+                                                 target_sdk_version,
+                                                 profile_name_ptr,
+                                                 dm_path_ptr,
+                                                 compilation_reason_ptr);
+        ASSERT_EQ(should_binder_call_succeed, result.isOk()) << result.toString8().c_str();
+
+        if (!should_binder_call_succeed) {
+            if (binder_result != nullptr) {
+                *binder_result = result;
+            }
+            return;
+        }
+        // Check the access to the compiler output.
+        //  - speed-profile artifacts are not world-wide readable.
+        //  - files are owned by the system uid.
+        std::string odex = GetPrimaryDexArtifact(oat_dir, apk_path_, "odex");
+        std::string vdex = GetPrimaryDexArtifact(oat_dir, apk_path_, "vdex");
+        std::string art = GetPrimaryDexArtifact(oat_dir, apk_path_, "art");
+
+        bool is_public = (dex_flags & DEXOPT_PUBLIC) != 0;
+        mode_t mode = S_IFREG | (is_public ? 0644 : 0640);
+        CheckFileAccess(odex, kSystemUid, uid, mode);
+        CheckFileAccess(vdex, kSystemUid, uid, mode);
+
+        if (compiler_filter == "speed-profile") {
+            CheckFileAccess(art, kSystemUid, uid, mode);
+        }
+        if (binder_result != nullptr) {
+            *binder_result = result;
+        }
+    }
+
+    std::string GetPrimaryDexArtifact(const char* oat_dir,
+                                      const std::string& dex_path,
+                                      const std::string& type) {
+        if (oat_dir == nullptr) {
+            std::string path = dex_path;
+            for (auto it = path.begin() + 1; it < path.end(); ++it) {
+                if (*it == '/') {
+                    *it = '@';
+                }
+            }
+            return android_data_dir + DALVIK_CACHE + '/' + kRuntimeIsa + "/" + path
+                    + "@classes.dex";
+        } else {
+            std::string::size_type name_end = dex_path.rfind('.');
+            std::string::size_type name_start = dex_path.rfind('/');
+            return std::string(oat_dir) + "/" + kRuntimeIsa + "/" +
+                    dex_path.substr(name_start + 1, name_end - name_start) + type;
+        }
+    }
+};
+
+
+TEST_F(DexoptTest, DexoptSecondaryCe) {
+    LOG(INFO) << "DexoptSecondaryCe";
+    CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_CE,
+        /*binder_ok*/ true, /*compile_ok*/ true);
+}
+
+TEST_F(DexoptTest, DexoptSecondaryCeLink) {
+    LOG(INFO) << "DexoptSecondaryCeLink";
+    CompileSecondaryDex(secondary_dex_ce_link_, DEXOPT_STORAGE_CE,
+        /*binder_ok*/ true, /*compile_ok*/ true);
+}
+
+TEST_F(DexoptTest, DexoptSecondaryDe) {
+    LOG(INFO) << "DexoptSecondaryDe";
+    CompileSecondaryDex(secondary_dex_de_, DEXOPT_STORAGE_DE,
+        /*binder_ok*/ true, /*compile_ok*/ true);
+}
+
+TEST_F(DexoptTest, DexoptSecondaryDoesNotExist) {
+    LOG(INFO) << "DexoptSecondaryDoesNotExist";
+    // If the file validates but does not exist we do not treat it as an error.
+    binder::Status status;
+    CompileSecondaryDex(secondary_dex_ce_ + "not.there", DEXOPT_STORAGE_CE,
+        /*binder_ok*/ true,  /*compile_ok*/ false, &status);
+    EXPECT_STREQ(status.toString8().c_str(), "No error");
+}
+
+TEST_F(DexoptTest, DexoptSecondaryStorageValidationError) {
+    LOG(INFO) << "DexoptSecondaryStorageValidationError";
+    binder::Status status;
+    CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_DE,
+        /*binder_ok*/ false,  /*compile_ok*/ false, &status);
+    EXPECT_STREQ(status.toString8().c_str(),
+                 "Status(-8): '-1: Dexoptanalyzer path validation failed'");
+}
+
+TEST_F(DexoptTest, DexoptSecondaryAppOwnershipValidationError) {
+    LOG(INFO) << "DexoptSecondaryAppOwnershipValidationError";
+    binder::Status status;
+    CompileSecondaryDex("/data/data/random.app/secondary.jar", DEXOPT_STORAGE_CE,
+        /*binder_ok*/ false,  /*compile_ok*/ false, &status);
+    EXPECT_STREQ(status.toString8().c_str(),
+                 "Status(-8): '-1: Dexoptanalyzer path validation failed'");
+}
+
+TEST_F(DexoptTest, DexoptSecondaryAcessViaDifferentUidError) {
+    LOG(INFO) << "DexoptSecondaryAcessViaDifferentUidError";
+    binder::Status status;
+    CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_CE,
+        /*binder_ok*/ false,  /*compile_ok*/ false, &status, kSystemUid);
+    EXPECT_STREQ(status.toString8().c_str(), "Status(-8): '-1: Dexoptanalyzer open zip failed'");
+}
+
+TEST_F(DexoptTest, DexoptPrimaryPublic) {
+    LOG(INFO) << "DexoptPrimaryPublic";
+    CompilePrimaryDexOk("verify",
+                        DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC,
+                        app_oat_dir_.c_str(),
+                        kTestAppGid,
+                        DEX2OAT_FROM_SCRATCH);
+}
+
+TEST_F(DexoptTest, DexoptPrimaryFailedInvalidFilter) {
+    LOG(INFO) << "DexoptPrimaryFailedInvalidFilter";
+    binder::Status status;
+    CompilePrimaryDexFail("awesome-filter",
+                          DEXOPT_IDLE_BACKGROUND_JOB | DEXOPT_PUBLIC,
+                          app_oat_dir_.c_str(),
+                          kTestAppGid,
+                          DEX2OAT_FROM_SCRATCH,
+                          &status);
+    EXPECT_STREQ(status.toString8().c_str(),
+                 "Status(-8): \'256: Dex2oat invocation for "
+                 "/data/app/com.installd.test.dexopt/base.jar failed: unspecified dex2oat error'");
+}
+
+TEST_F(DexoptTest, DexoptPrimaryProfileNonPublic) {
+    LOG(INFO) << "DexoptPrimaryProfileNonPublic";
+    CompilePrimaryDexOk("speed-profile",
+                        DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_GENERATE_APP_IMAGE,
+                        app_oat_dir_.c_str(),
+                        kTestAppGid,
+                        DEX2OAT_FROM_SCRATCH);
+}
+
+TEST_F(DexoptTest, DexoptPrimaryProfilePublic) {
+    LOG(INFO) << "DexoptPrimaryProfilePublic";
+    CompilePrimaryDexOk("speed-profile",
+                        DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_PUBLIC |
+                                DEXOPT_GENERATE_APP_IMAGE,
+                        app_oat_dir_.c_str(),
+                        kTestAppGid,
+                        DEX2OAT_FROM_SCRATCH);
+}
+
+TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) {
+    LOG(INFO) << "DexoptPrimaryBackgroundOk";
+    CompilePrimaryDexOk("speed-profile",
+                        DEXOPT_IDLE_BACKGROUND_JOB | DEXOPT_PROFILE_GUIDED |
+                                DEXOPT_GENERATE_APP_IMAGE,
+                        app_oat_dir_.c_str(),
+                        kTestAppGid,
+                        DEX2OAT_FROM_SCRATCH);
+}
+
+class PrimaryDexReCompilationTest : public DexoptTest {
+  public:
+    virtual void SetUp() {
+        DexoptTest::SetUp();
+        CompilePrimaryDexOk("verify",
+                            DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC,
+                            app_oat_dir_.c_str(),
+                            kTestAppGid,
+                            DEX2OAT_FROM_SCRATCH);
+        std::string odex = GetSecondaryDexArtifact(apk_path_, "odex");
+        std::string vdex = GetSecondaryDexArtifact(apk_path_, "vdex");
+
+        first_compilation_odex_fd_.reset(open(odex.c_str(), O_RDONLY));
+        first_compilation_vdex_fd_.reset(open(vdex.c_str(), O_RDONLY));
+    }
+
+    virtual void TearDown() {
+        first_compilation_odex_fd_.reset(-1);
+        first_compilation_vdex_fd_.reset(-1);
+        DexoptTest::TearDown();
+    }
+
+  protected:
+    unique_fd first_compilation_odex_fd_;
+    unique_fd first_compilation_vdex_fd_;
+};
+
+TEST_F(PrimaryDexReCompilationTest, DexoptPrimaryUpdateInPlaceVdex) {
+    LOG(INFO) << "DexoptPrimaryUpdateInPlaceVdex";
+
+    CompilePrimaryDexOk("verify",
+                        DEXOPT_IDLE_BACKGROUND_JOB | DEXOPT_PUBLIC,
+                        app_oat_dir_.c_str(),
+                        kTestAppGid,
+                        DEX2OAT_FOR_BOOT_IMAGE);
+}
+
+class ReconcileTest : public DexoptTest {
+    virtual void SetUp() {
+        DexoptTest::SetUp();
+        CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_CE,
+            /*binder_ok*/ true, /*compile_ok*/ true);
+        CompileSecondaryDex(secondary_dex_de_, DEXOPT_STORAGE_DE,
+            /*binder_ok*/ true, /*compile_ok*/ true);
+    }
+};
+
+TEST_F(ReconcileTest, ReconcileSecondaryCeExists) {
+    LOG(INFO) << "ReconcileSecondaryCeExists";
+    reconcile_secondary_dex(secondary_dex_ce_, FLAG_STORAGE_CE,
+        /*binder_ok*/ true, /*dex_ok */ true, /*odex_deleted*/ false);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryCeLinkExists) {
+    LOG(INFO) << "ReconcileSecondaryCeLinkExists";
+    reconcile_secondary_dex(secondary_dex_ce_link_, FLAG_STORAGE_CE,
+        /*binder_ok*/ true, /*dex_ok */ true, /*odex_deleted*/ false);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryDeExists) {
+    LOG(INFO) << "ReconcileSecondaryDeExists";
+    reconcile_secondary_dex(secondary_dex_de_, FLAG_STORAGE_DE,
+        /*binder_ok*/ true, /*dex_ok */ true, /*odex_deleted*/ false);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryDeDoesNotExist) {
+    LOG(INFO) << "ReconcileSecondaryDeDoesNotExist";
+    run_cmd("rm -rf " + secondary_dex_de_);
+    reconcile_secondary_dex(secondary_dex_de_, FLAG_STORAGE_DE,
+        /*binder_ok*/ true, /*dex_ok */ false, /*odex_deleted*/ true);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryStorageValidationError) {
+    // Validation errors will not clean the odex/vdex/art files but will mark
+    // the file as non existent so that the PM knows it should purge it from its
+    // records.
+    LOG(INFO) << "ReconcileSecondaryStorageValidationError";
+    reconcile_secondary_dex(secondary_dex_ce_, FLAG_STORAGE_DE,
+        /*binder_ok*/ true, /*dex_ok */ false, /*odex_deleted*/ false);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryAppOwnershipValidationError) {
+    LOG(INFO) << "ReconcileSecondaryAppOwnershipValidationError";
+    // Attempt to reconcile the dex files of the test app from a different app.
+    std::string another_app = "another.app";
+    reconcile_secondary_dex(secondary_dex_ce_, FLAG_STORAGE_CE,
+        /*binder_ok*/ true, /*dex_ok */ false, /*odex_deleted*/ false, kSystemUid, &another_app);
+}
+
+TEST_F(ReconcileTest, ReconcileSecondaryAcessViaDifferentUidError) {
+    LOG(INFO) << "ReconcileSecondaryAcessViaDifferentUidError";
+    reconcile_secondary_dex(secondary_dex_ce_, FLAG_STORAGE_CE,
+        /*binder_ok*/ true, /*dex_ok */ false, /*odex_deleted*/ false, kSystemUid);
+}
+
+class ProfileTest : public DexoptTest {
+  protected:
+    std::string cur_profile_;
+    std::string ref_profile_;
+    std::string snap_profile_;
+
+    static constexpr const char* kPrimaryProfile = "primary.prof";
+
+    virtual void SetUp() {
+        DexoptTest::SetUp();
+        cur_profile_ = create_current_profile_path(
+                kTestUserId, package_name_, kPrimaryProfile, /*is_secondary_dex*/ false);
+        ref_profile_ = create_reference_profile_path(package_name_, kPrimaryProfile,
+                /*is_secondary_dex*/ false);
+        snap_profile_ = create_snapshot_profile_path(package_name_, kPrimaryProfile);
+    }
+
+    void SetupProfile(const std::string& path, uid_t uid, gid_t gid, mode_t mode,
+            int32_t num_dex) {
+        run_cmd("profman --generate-test-profile-seed=" + std::to_string(num_dex) +
+                " --generate-test-profile-num-dex=" + std::to_string(num_dex) +
+                " --generate-test-profile=" + path);
+        ::chmod(path.c_str(), mode);
+        ::chown(path.c_str(), uid, gid);
+    }
+
+    void SetupProfiles(bool setup_ref) {
+        SetupProfile(cur_profile_, kTestAppUid, kTestAppGid, 0600, 1);
+        if (setup_ref) {
+            SetupProfile(ref_profile_, kTestAppUid, kTestAppGid, 0600, 2);
+        }
+    }
+
+    void createProfileSnapshot(int32_t appid, const std::string& package_name,
+            bool expected_result) {
+        bool result;
+        binder::Status binder_result = service_->createProfileSnapshot(
+                appid, package_name, kPrimaryProfile, apk_path_, &result);
+        ASSERT_TRUE(binder_result.isOk()) << binder_result.toString8().c_str();
+        ASSERT_EQ(expected_result, result);
+
+        if (!expected_result) {
+            // Do not check the files if we expect to fail.
+            return;
+        }
+
+        // Check that the snapshot was created witht he expected acess flags.
+        CheckFileAccess(snap_profile_, kSystemUid, kSystemGid, 0600 | S_IFREG);
+
+        // The snapshot should be equivalent to the merge of profiles.
+        std::string expected_profile_content = snap_profile_ + ".expected";
+        run_cmd("rm -f " + expected_profile_content);
+        run_cmd("touch " + expected_profile_content);
+        run_cmd("profman --profile-file=" + cur_profile_ +
+                " --profile-file=" + ref_profile_ +
+                " --reference-profile-file=" + expected_profile_content +
+                " --apk=" + apk_path_);
+
+        ASSERT_TRUE(AreFilesEqual(expected_profile_content, snap_profile_));
+
+        pid_t pid = fork();
+        if (pid == 0) {
+            /* child */
+            TransitionToSystemServer();
+
+            // System server should be able to open the the spanshot.
+            unique_fd fd(open(snap_profile_.c_str(), O_RDONLY));
+            ASSERT_TRUE(fd > -1) << "Failed to open profile as kSystemUid: " << strerror(errno);
+            _exit(0);
+        }
+        /* parent */
+        ASSERT_TRUE(WIFEXITED(wait_child(pid)));
+    }
+
+    void mergePackageProfiles(const std::string& package_name,
+                              const std::string& code_path,
+                              bool expected_result) {
+        bool result;
+        binder::Status binder_result = service_->mergeProfiles(
+                kTestAppUid, package_name, code_path, &result);
+        ASSERT_TRUE(binder_result.isOk()) << binder_result.toString8().c_str();
+        ASSERT_EQ(expected_result, result);
+
+        if (!expected_result) {
+            // Do not check the files if we expect to fail.
+            return;
+        }
+
+        // Check that the snapshot was created witht he expected acess flags.
+        CheckFileAccess(ref_profile_, kTestAppUid, kTestAppUid, 0600 | S_IFREG);
+
+        // The snapshot should be equivalent to the merge of profiles.
+        std::string ref_profile_content = ref_profile_ + ".expected";
+        run_cmd("rm -f " + ref_profile_content);
+        run_cmd("touch " + ref_profile_content);
+        run_cmd("profman --profile-file=" + cur_profile_ +
+                " --profile-file=" + ref_profile_ +
+                " --reference-profile-file=" + ref_profile_content);
+
+        ASSERT_TRUE(AreFilesEqual(ref_profile_content, ref_profile_));
+    }
+
+    // TODO(calin): add dex metadata tests once the ART change is merged.
+    void preparePackageProfile(const std::string& package_name, const std::string& profile_name,
+            bool expected_result) {
+        bool result;
+        binder::Status binder_result = service_->prepareAppProfile(
+                package_name, kTestUserId, kTestAppId, profile_name, apk_path_,
+                /*dex_metadata*/ nullptr, &result);
+        ASSERT_TRUE(binder_result.isOk()) << binder_result.toString8().c_str();
+        ASSERT_EQ(expected_result, result);
+
+        if (!expected_result) {
+            // Do not check the files if we expect to fail.
+            return;
+        }
+
+        std::string code_path_cur_prof = create_current_profile_path(
+                kTestUserId, package_name, profile_name, /*is_secondary_dex*/ false);
+        std::string code_path_ref_profile = create_reference_profile_path(package_name,
+                profile_name, /*is_secondary_dex*/ false);
+
+        // Check that we created the current profile.
+        CheckFileAccess(code_path_cur_prof, kTestAppUid, kTestAppUid, 0600 | S_IFREG);
+
+        // Without dex metadata we don't generate a reference profile.
+        ASSERT_EQ(-1, access(code_path_ref_profile.c_str(), R_OK));
+    }
+
+  protected:
+    void TransitionToSystemServer() {
+        ASSERT_TRUE(DropCapabilities(kSystemUid, kSystemGid));
+        int32_t res = selinux_android_setcontext(
+                kSystemUid, true, se_info_.c_str(), "system_server");
+        ASSERT_EQ(0, res) << "Failed to setcon " << strerror(errno);
+    }
+
+    bool AreFilesEqual(const std::string& file1, const std::string& file2) {
+        std::vector<uint8_t> content1;
+        std::vector<uint8_t> content2;
+
+        if (!ReadAll(file1, &content1)) return false;
+        if (!ReadAll(file2, &content2)) return false;
+        return content1 == content2;
+    }
+
+    bool ReadAll(const std::string& file, std::vector<uint8_t>* content) {
+        unique_fd fd(open(file.c_str(), O_RDONLY));
+        if (fd < 0) {
+            PLOG(ERROR) << "Failed to open " << file;
+            return false;
+        }
+        struct stat st;
+        if (fstat(fd, &st) != 0) {
+            PLOG(ERROR) << "Failed to stat " << file;
+            return false;
+        }
+        content->resize(st.st_size);
+        bool result = ReadFully(fd, content->data(), content->size());
+        if (!result) {
+            PLOG(ERROR) << "Failed to read " << file;
+        }
+        return result;
+    }
+};
+
+TEST_F(ProfileTest, ProfileSnapshotOk) {
+    LOG(INFO) << "ProfileSnapshotOk";
+
+    SetupProfiles(/*setup_ref*/ true);
+    createProfileSnapshot(kTestAppId, package_name_, /*expected_result*/ true);
+}
+
+// The reference profile is created on the fly. We need to be able to
+// snapshot without one.
+TEST_F(ProfileTest, ProfileSnapshotOkNoReference) {
+    LOG(INFO) << "ProfileSnapshotOkNoReference";
+
+    SetupProfiles(/*setup_ref*/ false);
+    createProfileSnapshot(kTestAppId, package_name_, /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfileSnapshotFailWrongPackage) {
+    LOG(INFO) << "ProfileSnapshotFailWrongPackage";
+
+    SetupProfiles(/*setup_ref*/ true);
+    createProfileSnapshot(kTestAppId, "not.there", /*expected_result*/ false);
+}
+
+TEST_F(ProfileTest, ProfileSnapshotDestroySnapshot) {
+    LOG(INFO) << "ProfileSnapshotDestroySnapshot";
+
+    SetupProfiles(/*setup_ref*/ true);
+    createProfileSnapshot(kTestAppId, package_name_, /*expected_result*/ true);
+
+    binder::Status binder_result = service_->destroyProfileSnapshot(package_name_, kPrimaryProfile);
+    ASSERT_TRUE(binder_result.isOk()) << binder_result.toString8().c_str();
+    struct stat st;
+    ASSERT_EQ(-1, stat(snap_profile_.c_str(), &st));
+    ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(ProfileTest, ProfileMergeOk) {
+    LOG(INFO) << "ProfileMergeOk";
+
+    SetupProfiles(/*setup_ref*/ true);
+    mergePackageProfiles(package_name_, "primary.prof", /*expected_result*/ true);
+}
+
+// The reference profile is created on the fly. We need to be able to
+// merge without one.
+TEST_F(ProfileTest, ProfileMergeOkNoReference) {
+    LOG(INFO) << "ProfileMergeOkNoReference";
+
+    SetupProfiles(/*setup_ref*/ false);
+    mergePackageProfiles(package_name_, "primary.prof", /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfileMergeFailWrongPackage) {
+    LOG(INFO) << "ProfileMergeFailWrongPackage";
+
+    SetupProfiles(/*setup_ref*/ true);
+    mergePackageProfiles("not.there", "primary.prof", /*expected_result*/ false);
+}
+
+TEST_F(ProfileTest, ProfileDirOk) {
+    LOG(INFO) << "ProfileDirOk";
+
+    std::string cur_profile_dir = create_primary_current_profile_package_dir_path(
+            kTestUserId, package_name_);
+    std::string cur_profile_file = create_current_profile_path(kTestUserId, package_name_,
+            kPrimaryProfile, /*is_secondary_dex*/false);
+    std::string ref_profile_dir = create_primary_reference_profile_package_dir_path(package_name_);
+
+    CheckFileAccess(cur_profile_dir, kTestAppUid, kTestAppUid, 0700 | S_IFDIR);
+    CheckFileAccess(ref_profile_dir, kSystemUid, kTestAppGid, 0770 | S_IFDIR);
+}
+
+// Verify that the profile directories are fixed up during an upgrade.
+// (The reference profile directory is prepared lazily).
+TEST_F(ProfileTest, ProfileDirOkAfterFixup) {
+    LOG(INFO) << "ProfileDirOkAfterFixup";
+
+    std::string cur_profile_dir = create_primary_current_profile_package_dir_path(
+            kTestUserId, package_name_);
+    std::string cur_profile_file = create_current_profile_path(kTestUserId, package_name_,
+            kPrimaryProfile, /*is_secondary_dex*/false);
+    std::string ref_profile_dir = create_primary_reference_profile_package_dir_path(package_name_);
+
+    // Simulate a pre-P setup by changing the owner to kTestAppGid and permissions to 0700.
+    ASSERT_EQ(0, chown(ref_profile_dir.c_str(), kTestAppGid, kTestAppGid));
+    ASSERT_EQ(0, chmod(ref_profile_dir.c_str(), 0700));
+
+    // Run createAppData again which will offer to fix-up the profile directories.
+    ASSERT_TRUE(service_->createAppData(
+            volume_uuid_,
+            package_name_,
+            kTestUserId,
+            kAppDataFlags,
+            kTestAppUid,
+            se_info_,
+            kOSdkVersion,
+            &ce_data_inode_).isOk());
+
+    // Check the file access.
+    CheckFileAccess(cur_profile_dir, kTestAppUid, kTestAppUid, 0700 | S_IFDIR);
+    CheckFileAccess(ref_profile_dir, kSystemUid, kTestAppGid, 0770 | S_IFDIR);
+}
+
+TEST_F(ProfileTest, ProfilePrepareOk) {
+    LOG(INFO) << "ProfilePrepareOk";
+    preparePackageProfile(package_name_, "split.prof", /*expected_result*/ true);
+}
+
+TEST_F(ProfileTest, ProfilePrepareFailInvalidPackage) {
+    LOG(INFO) << "ProfilePrepareFailInvalidPackage";
+    preparePackageProfile("not.there.package", "split.prof", /*expected_result*/ false);
+}
+
+TEST_F(ProfileTest, ProfilePrepareFailProfileChangedUid) {
+    LOG(INFO) << "ProfilePrepareFailProfileChangedUid";
+    SetupProfiles(/*setup_ref*/ false);
+    // Change the uid on the profile to trigger a failure.
+    ::chown(cur_profile_.c_str(), kTestAppUid + 1, kTestAppGid + 1);
+    preparePackageProfile(package_name_, "primary.prof", /*expected_result*/ false);
+}
+
+
+class BootProfileTest : public ProfileTest {
+  public:
+    virtual void setup() {
+        ProfileTest::SetUp();
+        intial_android_profiles_dir = android_profiles_dir;
+    }
+
+    virtual void TearDown() {
+        android_profiles_dir = intial_android_profiles_dir;
+        ProfileTest::TearDown();
+    }
+
+    void UpdateAndroidProfilesDir(const std::string& profile_dir) {
+        android_profiles_dir = profile_dir;
+        // We need to create the reference profile directory in the new profile dir.
+        run_cmd("mkdir -p " + profile_dir + "/ref");
+    }
+
+    void createBootImageProfileSnapshot(const std::string& classpath, bool expected_result) {
+        bool result;
+        binder::Status binder_result = service_->createProfileSnapshot(
+                -1, "android", "android.prof", classpath, &result);
+        ASSERT_TRUE(binder_result.isOk());
+        ASSERT_EQ(expected_result, result);
+
+        if (!expected_result) {
+            // Do not check the files if we expect to fail.
+            return;
+        }
+
+        // Check that the snapshot was created with he expected access flags.
+        const std::string boot_profile = create_snapshot_profile_path("android", "android.prof");
+        CheckFileAccess(boot_profile, kSystemUid, kSystemGid, 0600 | S_IFREG);
+
+        pid_t pid = fork();
+        if (pid == 0) {
+            /* child */
+            TransitionToSystemServer();
+
+            // System server should be able to open the snapshot.
+            unique_fd fd(open(boot_profile.c_str(), O_RDONLY));
+            ASSERT_TRUE(fd > -1) << "Failed to open profile as kSystemUid: " << strerror(errno);
+            _exit(0);
+        }
+        /* parent */
+        ASSERT_TRUE(WIFEXITED(wait_child(pid)));
+    }
+  protected:
+    std::string intial_android_profiles_dir;
+};
+
+TEST_F(BootProfileTest, BootProfileSnapshotOk) {
+    LOG(INFO) << "BootProfileSnapshotOk";
+    char* boot_classpath = getenv("BOOTCLASSPATH");
+    ASSERT_TRUE(boot_classpath != nullptr);
+    createBootImageProfileSnapshot(boot_classpath, /*expected_result*/ true);
+}
+
+TEST_F(BootProfileTest, BootProfileSnapshotFailEmptyClasspath) {
+    LOG(INFO) << "BootProfileSnapshotFailEmptyClasspath";
+
+    createBootImageProfileSnapshot(/*boot_classpath*/ "", /*expected_result*/ false);
+}
+
+TEST_F(BootProfileTest, BootProfileSnapshotOkNoProfiles) {
+    LOG(INFO) << "BootProfileSnapshotOkNoProfiles";
+    char* boot_classpath = getenv("BOOTCLASSPATH");
+    ASSERT_TRUE(boot_classpath != nullptr);
+
+    // The app_apk_dir has no profiles. So we shouldn't be able to merge anything.
+    // Still, this is not a failure case.
+    UpdateAndroidProfilesDir(app_apk_dir_);
+    createBootImageProfileSnapshot(boot_classpath, /*expected_result*/ true);
+}
+
+// Verify that profile collection.
+TEST_F(BootProfileTest, CollectProfiles) {
+    LOG(INFO) << "CollectProfiles";
+
+    // Create some profile directories mimicking the real profile structure.
+    run_cmd("mkdir -p " + app_private_dir_de_ + "/profiles/ref");
+    run_cmd("mkdir -p " + app_private_dir_de_ + "/profiles/cur/0/");
+    run_cmd("mkdir -p " + app_private_dir_de_ + "/profiles/cur/1/");
+    // Create an empty profile.
+    run_cmd("touch " + app_private_dir_de_ + "/profiles/cur/1/primary.prof");
+    // Create a random file.
+    run_cmd("touch " + app_private_dir_de_ + "/profiles/cur/0/non.profile.file");
+
+    // Create some non-empty profiles.
+    std::string current_prof = app_private_dir_de_ + "/profiles/cur/0/primary.prof";
+    run_cmd("echo 1 > " + current_prof);
+    std::string ref_prof = app_private_dir_de_ + "/profiles/ref/primary.prof";
+    run_cmd("echo 1 > " + ref_prof);
+
+    UpdateAndroidProfilesDir(app_private_dir_de_ + "/profiles");
+
+    std::vector<std::string> profiles;
+    collect_profiles(&profiles);
+
+    // Only two profiles should be in the output.
+    ASSERT_EQ(2u, profiles.size());
+    ASSERT_TRUE(std::find(profiles.begin(), profiles.end(), current_prof) != profiles.end());
+    ASSERT_TRUE(std::find(profiles.begin(), profiles.end(), ref_prof) != profiles.end());
+}
+
+}  // namespace installd
+}  // namespace android
diff --git a/cmds/installd/tests/installd_otapreopt_test.cpp b/cmds/installd/tests/installd_otapreopt_test.cpp
new file mode 100644
index 0000000..b518507
--- /dev/null
+++ b/cmds/installd/tests/installd_otapreopt_test.cpp
@@ -0,0 +1,231 @@
+/**
+ * Copyright (C) 2018 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 <stdlib.h>
+#include <string.h>
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include "installd_constants.h"
+#include "otapreopt_parameters.h"
+
+namespace android {
+namespace installd {
+
+static bool ParseBool(const char* in) {
+    if (strcmp(in, "true") == 0) {
+        return true;
+    }
+    return false;
+}
+
+static const char* ParseNull(const char* arg) {
+    return (strcmp(arg, "!") == 0) ? nullptr : arg;
+}
+
+class OTAPreoptTest : public testing::Test {
+protected:
+    virtual void SetUp() {
+        setenv("ANDROID_LOG_TAGS", "*:f", 1);
+        android::base::InitLogging(nullptr, android::base::StderrLogger);
+    }
+
+    void verifyPackageParameters(const OTAPreoptParameters& params,
+                                 uint32_t version,
+                                 bool versioned,
+                                 const char** args) {
+        //  otapreopt target-slot [version] dexopt {DEXOPT_PARAMETERS}
+        int i = 0;
+        if (version > 2 || (version == 2 && versioned)) {
+            i += 4;
+        } else {
+            i += 3;
+        }
+        ASSERT_STREQ(params.target_slot.c_str(), args[1]);
+        ASSERT_STREQ(params.apk_path, args[i++]);
+        ASSERT_EQ(params.uid, static_cast<uid_t>(atoi(args[i++])));
+        ASSERT_STREQ(params.pkgName, args[i++]);
+        ASSERT_STREQ(params.instruction_set, args[i++]);
+        ASSERT_EQ(params.dexopt_needed, atoi(args[i++]));
+        ASSERT_STREQ(params.oat_dir, args[i++]);
+        const int dexopt_flags = atoi(args[i++]);
+        ASSERT_STREQ(params.compiler_filter, args[i++]);
+        ASSERT_STREQ(params.volume_uuid, ParseNull(args[i++]));
+        ASSERT_STREQ(params.shared_libraries, ParseNull(args[i++]));
+        if (version > 1) {
+            ASSERT_STREQ(params.se_info, ParseNull(args[i++]));
+        } else {
+            ASSERT_EQ(params.se_info, nullptr);
+        }
+        if (version > 2) {
+            ASSERT_EQ(params.downgrade, ParseBool(args[i++]));
+        } else {
+            ASSERT_FALSE(params.downgrade);
+        }
+        if (version > 3) {
+            ASSERT_EQ(params.target_sdk_version, atoi(args[i++]));
+        } else {
+            ASSERT_EQ(params.target_sdk_version, 0);
+        }
+        if (version > 4) {
+            ASSERT_STREQ(params.profile_name, ParseNull(args[i++]));
+        } else {
+            ASSERT_STREQ(params.profile_name, "primary.prof");
+        }
+        if (version > 5) {
+            ASSERT_STREQ(params.dex_metadata_path, ParseNull(args[i++]));
+        } else {
+            ASSERT_EQ(params.dex_metadata_path, nullptr);
+        }
+        if (version > 6) {
+            ASSERT_STREQ(params.compilation_reason, ParseNull(args[i++]));
+        } else {
+            ASSERT_STREQ(params.compilation_reason, "ab-ota");
+        }
+        if (version > 7) {
+            ASSERT_EQ(params.dexopt_flags, dexopt_flags);
+        } else {
+            ASSERT_EQ(params.dexopt_flags, dexopt_flags | DEXOPT_GENERATE_COMPACT_DEX);
+        }
+    }
+
+    const char* getVersionCStr(uint32_t version) {
+        switch (version) {
+            case 1: return "1";
+            case 2: return "2";
+            case 3: return "3";
+            case 4: return "4";
+            case 5: return "5";
+            case 6: return "6";
+            case 7: return "7";
+            case 8: return "8";
+            case 9: return "9";
+        }
+        return nullptr;
+    }
+
+    std::vector<const char*> getArgs(uint32_t version, bool versioned) {
+        std::vector<const char*> args;
+        args.push_back("otapreopt");  // "otapreopt"
+        args.push_back("a");  // slot
+        if (versioned) {
+            args.push_back(getVersionCStr(version));
+        }
+        args.push_back("dexopt");  // "dexopt"
+        args.push_back("foo.apk");  // apk_path
+        args.push_back("123");  // uid
+        args.push_back("pkgname");  // pkg
+        args.push_back("arm");  // isa
+        args.push_back("1");  // dexopt_needed (DEX2OAT_FROM_SCRATCH)
+        args.push_back("oat_dir");  // oat_dir
+        args.push_back("0");  // dexopt_flags
+        args.push_back("speed");  // filter
+        args.push_back("!");  // volume
+        args.push_back("shared.lib");  // libs
+
+        if (version > 1) {
+            args.push_back("!");  // seinfo
+        }
+        if (version > 2) {
+            args.push_back("true");  // downgrade
+        }
+        if (version > 3) {
+            args.push_back("28");  // sdk_version
+        }
+        if (version > 4) {
+            args.push_back("split_a.prof");  // profile_name
+        }
+        if (version > 5) {
+            args.push_back("dex_metadata.dm");  // dex_metadata_path
+        }
+        if (version > 6) {
+            args.push_back("ab-ota-test");  // compilation_reason
+        }
+        args.push_back(nullptr);  // we have to end with null.
+        return args;
+    }
+
+    void VerifyReadArguments(uint32_t version, bool versioned) {
+        OTAPreoptParameters params;
+        std::vector<const char*> args = getArgs(version, versioned);
+        ASSERT_TRUE(params.ReadArguments(args.size() - 1, args.data()));
+        verifyPackageParameters(params, version, versioned, args.data());
+    }
+};
+
+TEST_F(OTAPreoptTest, ReadArgumentsV1) {
+    VerifyReadArguments(1, false);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV2Unversioned) {
+    VerifyReadArguments(2, false);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV2) {
+    VerifyReadArguments(2, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV3) {
+    VerifyReadArguments(3, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV4) {
+    VerifyReadArguments(4, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV5) {
+    VerifyReadArguments(5, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV6) {
+    VerifyReadArguments(6, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsV7) {
+    VerifyReadArguments(7, true);
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsFailToManyArgs) {
+    OTAPreoptParameters params;
+    std::vector<const char*> args = getArgs(5, true);
+    args[2] = "3";  // pretend it's version 3. It should fail since there are too many args.
+    ASSERT_FALSE(params.ReadArguments(args.size() - 1, args.data()));
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsFailInsufficientArgs) {
+    OTAPreoptParameters params;
+    std::vector<const char*> args = getArgs(4, true);
+    args[2] = "5";  // pretend it's version 5. It should fail since there are insufficient args.
+    ASSERT_FALSE(params.ReadArguments(args.size() - 1, args.data()));
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsFailInvalidDexopt) {
+    OTAPreoptParameters params;
+    std::vector<const char*> args = getArgs(4, true);
+    args[3] = "dexopt-invalid";
+    ASSERT_FALSE(params.ReadArguments(args.size() - 1, args.data()));
+}
+
+TEST_F(OTAPreoptTest, ReadArgumentsFailInvalidSlot) {
+    OTAPreoptParameters params;
+    std::vector<const char*> args = getArgs(3, true);
+    args[1] = "invalid-slot???";
+    ASSERT_FALSE(params.ReadArguments(args.size() - 1, args.data()));
+}
+
+}  // namespace installd
+}  // namespace android
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index 34818f6..a5af5d7 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <sstream>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/statvfs.h>
@@ -25,6 +26,7 @@
 #include <gtest/gtest.h>
 
 #include "InstalldNativeService.h"
+#include "dexopt.h"
 #include "globals.h"
 #include "utils.h"
 
@@ -41,37 +43,33 @@
     return property_get(key, value, default_value);
 }
 
-bool calculate_oat_file_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
-        const char *oat_dir ATTRIBUTE_UNUSED,
-        const char *apk_path ATTRIBUTE_UNUSED,
-        const char *instruction_set ATTRIBUTE_UNUSED) {
-    return false;
-}
-
-bool calculate_odex_file_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
-        const char *apk_path ATTRIBUTE_UNUSED,
-        const char *instruction_set ATTRIBUTE_UNUSED) {
-    return false;
-}
-
-bool create_cache_path(char path[PKG_PATH_MAX],
-        const char *src,
+bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char *oat_dir, const char *apk_path,
         const char *instruction_set) {
-    // Not really a valid path but it's good enough for testing.
-    sprintf(path,"/data/dalvik-cache/%s/%s", instruction_set, src);
-    return true;
+    return calculate_oat_file_path_default(path, oat_dir, apk_path, instruction_set);
+}
+
+bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char *apk_path,
+        const char *instruction_set) {
+    return calculate_odex_file_path_default(path, apk_path, instruction_set);
+}
+
+bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *instruction_set) {
+    return create_cache_path_default(path, src, instruction_set);
+}
+
+static std::string get_full_path(const char* path) {
+    return StringPrintf("/data/local/tmp/user/0/%s", path);
 }
 
 static void mkdir(const char* path, uid_t owner, gid_t group, mode_t mode) {
-    const char* fullPath = StringPrintf("/data/local/tmp/user/0/%s", path).c_str();
-    ::mkdir(fullPath, mode);
-    ::chown(fullPath, owner, group);
-    ::chmod(fullPath, mode);
+    const std::string fullPath = get_full_path(path);
+    ::mkdir(fullPath.c_str(), mode);
+    ::chown(fullPath.c_str(), owner, group);
+    ::chmod(fullPath.c_str(), mode);
 }
 
 static void touch(const char* path, uid_t owner, gid_t group, mode_t mode) {
-    int fd = ::open(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(),
-            O_RDWR | O_CREAT, mode);
+    int fd = ::open(get_full_path(path).c_str(), O_RDWR | O_CREAT, mode);
     ::fchown(fd, owner, group);
     ::fchmod(fd, mode);
     ::close(fd);
@@ -79,13 +77,13 @@
 
 static int stat_gid(const char* path) {
     struct stat buf;
-    ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+    ::stat(get_full_path(path).c_str(), &buf);
     return buf.st_gid;
 }
 
 static int stat_mode(const char* path) {
     struct stat buf;
-    ::stat(StringPrintf("/data/local/tmp/user/0/%s", path).c_str(), &buf);
+    ::stat(get_full_path(path).c_str(), &buf);
     return buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISGID);
 }
 
@@ -102,6 +100,8 @@
         testUuid = std::make_unique<std::string>();
         *testUuid = std::string(kTestUuid);
         system("mkdir -p /data/local/tmp/user/0");
+
+        init_globals_from_data_and_root();
     }
 
     virtual void TearDown() {
@@ -153,12 +153,91 @@
     EXPECT_EQ(10000, stat_gid("com.example/bar/file"));
 }
 
-TEST_F(ServiceTest, RmDexNoDalvikCache) {
-    LOG(INFO) << "RmDexNoDalvikCache";
+TEST_F(ServiceTest, HashSecondaryDex) {
+    LOG(INFO) << "HashSecondaryDex";
 
-    // Try to remove a non existing dalvik cache dex. The call should be
-    // successful because there's nothing to remove.
-    EXPECT_TRUE(service->rmdex("com.example", "arm").isOk());
+    mkdir("com.example", 10000, 10000, 0700);
+    mkdir("com.example/foo", 10000, 10000, 0700);
+    touch("com.example/foo/file", 10000, 20000, 0700);
+
+    std::vector<uint8_t> result;
+    std::string dexPath = get_full_path("com.example/foo/file");
+    EXPECT_TRUE(service->hashSecondaryDexFile(
+        dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+    EXPECT_EQ(result.size(), 32U);
+
+    std::ostringstream output;
+    output << std::hex << std::setfill('0');
+    for (auto b : result) {
+        output << std::setw(2) << +b;
+    }
+
+    // This is the SHA256 of an empty string (sha256sum /dev/null)
+    EXPECT_EQ(output.str(), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_NoSuch) {
+    LOG(INFO) << "HashSecondaryDex_NoSuch";
+
+    std::vector<uint8_t> result;
+    std::string dexPath = get_full_path("com.example/foo/file");
+    EXPECT_TRUE(service->hashSecondaryDexFile(
+        dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+    EXPECT_EQ(result.size(), 0U);
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_Unreadable) {
+    LOG(INFO) << "HashSecondaryDex_Unreadable";
+
+    mkdir("com.example", 10000, 10000, 0700);
+    mkdir("com.example/foo", 10000, 10000, 0700);
+    touch("com.example/foo/file", 10000, 20000, 0300);
+
+    std::vector<uint8_t> result;
+    std::string dexPath = get_full_path("com.example/foo/file");
+    EXPECT_TRUE(service->hashSecondaryDexFile(
+        dexPath, "com.example", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+
+    EXPECT_EQ(result.size(), 0U);
+}
+
+TEST_F(ServiceTest, HashSecondaryDex_WrongApp) {
+    LOG(INFO) << "HashSecondaryDex_WrongApp";
+
+    mkdir("com.example", 10000, 10000, 0700);
+    mkdir("com.example/foo", 10000, 10000, 0700);
+    touch("com.example/foo/file", 10000, 20000, 0700);
+
+    std::vector<uint8_t> result;
+    std::string dexPath = get_full_path("com.example/foo/file");
+    EXPECT_FALSE(service->hashSecondaryDexFile(
+        dexPath, "com.wrong", 10000, testUuid, FLAG_STORAGE_CE, &result).isOk());
+}
+
+TEST_F(ServiceTest, CalculateOat) {
+    char buf[PKG_PATH_MAX];
+
+    EXPECT_TRUE(calculate_oat_file_path(buf, "/path/to/oat", "/path/to/file.apk", "isa"));
+    EXPECT_EQ("/path/to/oat/isa/file.odex", std::string(buf));
+
+    EXPECT_FALSE(calculate_oat_file_path(buf, "/path/to/oat", "/path/to/file", "isa"));
+    EXPECT_FALSE(calculate_oat_file_path(buf, "/path/to/oat", "file", "isa"));
+}
+
+TEST_F(ServiceTest, CalculateOdex) {
+    char buf[PKG_PATH_MAX];
+
+    EXPECT_TRUE(calculate_odex_file_path(buf, "/path/to/file.apk", "isa"));
+    EXPECT_EQ("/path/to/oat/isa/file.odex", std::string(buf));
+}
+
+TEST_F(ServiceTest, CalculateCache) {
+    char buf[PKG_PATH_MAX];
+
+    EXPECT_TRUE(create_cache_path(buf, "/path/to/file.apk", "isa"));
+    EXPECT_EQ("/data/dalvik-cache/isa/path@to@file.apk@classes.dex", std::string(buf));
 }
 
 }  // namespace installd
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index 46ed85f..bcdd03e 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -17,6 +17,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <android-base/logging.h>
 #include <gtest/gtest.h>
 
 #include "InstalldNativeService.h"
@@ -27,11 +28,12 @@
 #define LOG_TAG "utils_test"
 
 #define TEST_DATA_DIR "/data/"
+#define TEST_ROOT_DIR "/system/"
 #define TEST_APP_DIR "/data/app/"
 #define TEST_APP_PRIVATE_DIR "/data/app-private/"
 #define TEST_APP_EPHEMERAL_DIR "/data/app-ephemeral/"
 #define TEST_ASEC_DIR "/mnt/asec/"
-#define TEST_EXPAND_DIR "/mnt/expand/"
+#define TEST_EXPAND_DIR "/mnt/expand/00000000-0000-0000-0000-000000000000/"
 
 #define TEST_SYSTEM_DIR1 "/system/app/"
 #define TEST_SYSTEM_DIR2 "/vendor/app/"
@@ -44,39 +46,13 @@
 class UtilsTest : public testing::Test {
 protected:
     virtual void SetUp() {
-        android_app_dir.path = (char*) TEST_APP_DIR;
-        android_app_dir.len = strlen(TEST_APP_DIR);
+        setenv("ANDROID_LOG_TAGS", "*:v", 1);
+        android::base::InitLogging(nullptr);
 
-        android_app_private_dir.path = (char*) TEST_APP_PRIVATE_DIR;
-        android_app_private_dir.len = strlen(TEST_APP_PRIVATE_DIR);
-
-        android_app_ephemeral_dir.path = (char*) TEST_APP_EPHEMERAL_DIR;
-        android_app_ephemeral_dir.len = strlen(TEST_APP_EPHEMERAL_DIR);
-
-        android_data_dir.path = (char*) TEST_DATA_DIR;
-        android_data_dir.len = strlen(TEST_DATA_DIR);
-
-        android_asec_dir.path = (char*) TEST_ASEC_DIR;
-        android_asec_dir.len = strlen(TEST_ASEC_DIR);
-
-        android_mnt_expand_dir.path = (char*) TEST_EXPAND_DIR;
-        android_mnt_expand_dir.len = strlen(TEST_EXPAND_DIR);
-
-        android_system_dirs.count = 2;
-
-        android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t));
-        android_system_dirs.dirs[0].path = (char*) TEST_SYSTEM_DIR1;
-        android_system_dirs.dirs[0].len = strlen(TEST_SYSTEM_DIR1);
-
-        android_system_dirs.dirs[1].path = (char*) TEST_SYSTEM_DIR2;
-        android_system_dirs.dirs[1].len = strlen(TEST_SYSTEM_DIR2);
-
-        android_profiles_dir.path = (char*) TEST_PROFILE_DIR;
-        android_profiles_dir.len = strlen(TEST_PROFILE_DIR);
+        init_globals_from_data_and_root(TEST_DATA_DIR, TEST_ROOT_DIR);
     }
 
     virtual void TearDown() {
-        free(android_system_dirs.dirs);
     }
 
     std::string create_too_long_path(const std::string& seed) {
@@ -140,6 +116,41 @@
             << bad_path5 << " should be rejected as a invalid path";
 }
 
+TEST_F(UtilsTest, IsValidApkPath_TopDir) {
+    EXPECT_EQ(0, validate_apk_path(TEST_DATA_DIR "app/com.example"));
+    EXPECT_EQ(0, validate_apk_path(TEST_EXPAND_DIR "app/com.example"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_DATA_DIR "data/com.example"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_EXPAND_DIR "data/com.example"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_TopFile) {
+    EXPECT_EQ(0, validate_apk_path(TEST_DATA_DIR "app/com.example/base.apk"));
+    EXPECT_EQ(0, validate_apk_path(TEST_EXPAND_DIR "app/com.example/base.apk"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_DATA_DIR "data/com.example/base.apk"));
+    EXPECT_EQ(-1, validate_apk_path(TEST_EXPAND_DIR "data/com.example/base.apk"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDir) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDirDir) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat/arm64"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat/arm64"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat/arm64"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat/arm64"));
+}
+
+TEST_F(UtilsTest, IsValidApkPath_OatDirDirFile) {
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_DATA_DIR "app/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(0, validate_apk_path_subdirs(TEST_EXPAND_DIR "app/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_DATA_DIR "data/com.example/oat/arm64/base.odex"));
+    EXPECT_EQ(-1, validate_apk_path_subdirs(TEST_EXPAND_DIR "data/com.example/oat/arm64/base.odex"));
+}
+
 TEST_F(UtilsTest, IsValidApkPath_Private) {
     // Internal directories
     const char *private1 = TEST_APP_PRIVATE_DIR "example.apk";
@@ -192,12 +203,6 @@
             << badasec1 << " should be rejected as a invalid path";
 }
 
-TEST_F(UtilsTest, IsValidApkPath_DoubleSlashFail) {
-    const char *badasec2 = TEST_ASEC_DIR "com.example.asec//pkg.apk";
-    EXPECT_EQ(-1, validate_apk_path(badasec2))
-            << badasec2 << " should be rejected as a invalid path";
-}
-
 TEST_F(UtilsTest, IsValidApkPath_SubdirEscapeFail) {
     const char *badasec3 = TEST_ASEC_DIR "com.example.asec/../../../pkg.apk";
     EXPECT_EQ(-1, validate_apk_path(badasec3))
@@ -276,184 +281,6 @@
             << badapp2 << " should be rejected not a system path";
 }
 
-TEST_F(UtilsTest, GetPathFromString_NullPathFail) {
-    dir_rec_t test1;
-    EXPECT_EQ(-1, get_path_from_string(&test1, (const char *) NULL))
-            << "Should not allow NULL as a path.";
-}
-
-TEST_F(UtilsTest, GetPathFromString_EmptyPathFail) {
-    dir_rec_t test1;
-    EXPECT_EQ(-1, get_path_from_string(&test1, ""))
-            << "Should not allow empty paths.";
-}
-
-TEST_F(UtilsTest, GetPathFromString_RelativePathFail) {
-    dir_rec_t test1;
-    EXPECT_EQ(-1, get_path_from_string(&test1, "mnt/asec"))
-            << "Should not allow relative paths.";
-}
-
-TEST_F(UtilsTest, GetPathFromString_NonCanonical) {
-    dir_rec_t test1;
-
-    EXPECT_EQ(0, get_path_from_string(&test1, "/mnt/asec"))
-            << "Should be able to canonicalize directory /mnt/asec";
-    EXPECT_STREQ("/mnt/asec/", test1.path)
-            << "/mnt/asec should be canonicalized to /mnt/asec/";
-    EXPECT_EQ(10, (ssize_t) test1.len)
-            << "path len should be equal to the length of /mnt/asec/ (10)";
-    free(test1.path);
-}
-
-TEST_F(UtilsTest, GetPathFromString_CanonicalPath) {
-    dir_rec_t test3;
-    EXPECT_EQ(0, get_path_from_string(&test3, "/data/app/"))
-            << "Should be able to canonicalize directory /data/app/";
-    EXPECT_STREQ("/data/app/", test3.path)
-            << "/data/app/ should be canonicalized to /data/app/";
-    EXPECT_EQ(10, (ssize_t) test3.len)
-            << "path len should be equal to the length of /data/app/ (10)";
-    free(test3.path);
-}
-
-TEST_F(UtilsTest, CreatePkgPath_LongPkgNameSuccess) {
-    char path[PKG_PATH_MAX];
-
-    // Create long packagename of "aaaaa..."
-    size_t pkgnameSize = PKG_NAME_MAX;
-    char pkgname[pkgnameSize + 1];
-    memset(pkgname, 'a', pkgnameSize);
-    pkgname[1] = '.';
-    pkgname[pkgnameSize] = '\0';
-
-    EXPECT_EQ(0, create_pkg_path(path, pkgname, "", 0))
-            << "Should successfully be able to create package name.";
-
-    std::string prefix = std::string(TEST_DATA_DIR) + PRIMARY_USER_PREFIX;
-    size_t offset = prefix.length();
-
-    EXPECT_STREQ(pkgname, path + offset)
-             << "Package path should be a really long string of a's";
-}
-
-TEST_F(UtilsTest, CreatePkgPath_LongPostfixFail) {
-    char path[PKG_PATH_MAX];
-
-    // Create long packagename of "aaaaa..."
-    size_t postfixSize = PKG_PATH_MAX;
-    char postfix[postfixSize + 1];
-    memset(postfix, 'a', postfixSize);
-    postfix[postfixSize] = '\0';
-
-    EXPECT_EQ(-1, create_pkg_path(path, "com.example.package", postfix, 0))
-            << "Should return error because postfix is too long.";
-}
-
-TEST_F(UtilsTest, CreatePkgPath_PrimaryUser) {
-    char path[PKG_PATH_MAX];
-
-    EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 0))
-            << "Should return error because postfix is too long.";
-
-    std::string p = std::string(TEST_DATA_DIR)
-                    + PRIMARY_USER_PREFIX
-                    + "com.example.package";
-    EXPECT_STREQ(p.c_str(), path)
-            << "Package path should be in /data/data/";
-}
-
-TEST_F(UtilsTest, CreatePkgPath_SecondaryUser) {
-    char path[PKG_PATH_MAX];
-
-    EXPECT_EQ(0, create_pkg_path(path, "com.example.package", "", 1))
-            << "Should successfully create package path.";
-
-    std::string p = std::string(TEST_DATA_DIR)
-                    + SECONDARY_USER_PREFIX
-                    + "1/com.example.package";
-    EXPECT_STREQ(p.c_str(), path)
-            << "Package path should be in /data/user/";
-}
-
-TEST_F(UtilsTest, CreateMovePath_Primary) {
-    char path[PKG_PATH_MAX];
-
-    EXPECT_EQ(0, create_move_path(path, "com.android.test", "shared_prefs", 0))
-            << "Should be able to create move path for primary user";
-
-    EXPECT_STREQ("/data/data/com.android.test/shared_prefs", path)
-            << "Primary user package directory should be created correctly";
-}
-
-
-TEST_F(UtilsTest, CreateMovePath_Fail_AppTooLong) {
-    char path[PKG_PATH_MAX];
-    std::string really_long_app_name = create_too_long_path("com.example");
-    EXPECT_EQ(-1, create_move_path(path, really_long_app_name.c_str(), "shared_prefs", 0))
-            << "Should fail to create move path for primary user";
-}
-
-TEST_F(UtilsTest, CreateMovePath_Fail_LeafTooLong) {
-    char path[PKG_PATH_MAX];
-    std::string really_long_leaf_name = create_too_long_path("leaf_");
-    EXPECT_EQ(-1, create_move_path(path, "com.android.test", really_long_leaf_name.c_str(), 0))
-            << "Should fail to create move path for primary user";
-}
-
-TEST_F(UtilsTest, CopyAndAppend_Normal) {
-    //int copy_and_append(dir_rec_t* dst, dir_rec_t* src, char* suffix)
-    dir_rec_t dst;
-    dir_rec_t src;
-
-    src.path = (char*) "/data/";
-    src.len = strlen(src.path);
-
-    EXPECT_EQ(0, copy_and_append(&dst, &src, "app/"))
-            << "Should return error because postfix is too long.";
-
-    EXPECT_STREQ("/data/app/", dst.path)
-            << "Appended path should be correct";
-
-    EXPECT_EQ(10, (ssize_t) dst.len)
-            << "Appended path should be length of '/data/app/' (10)";
-}
-
-TEST_F(UtilsTest, AppendAndIncrement_Normal) {
-    size_t dst_size = 10;
-    char dst[dst_size];
-    char *dstp = dst;
-    const char* src = "FOO";
-
-    EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size))
-            << "String should append successfully";
-
-    EXPECT_STREQ("FOO", dst)
-            << "String should append correctly";
-
-    EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size))
-            << "String should append successfully again";
-
-    EXPECT_STREQ("FOOFOO", dst)
-            << "String should append correctly again";
-}
-
-TEST_F(UtilsTest, AppendAndIncrement_TooBig) {
-    size_t dst_size = 5;
-    char dst[dst_size];
-    char *dstp = dst;
-    const char* src = "FOO";
-
-    EXPECT_EQ(0, append_and_increment(&dstp, src, &dst_size))
-            << "String should append successfully";
-
-    EXPECT_STREQ("FOO", dst)
-            << "String should append correctly";
-
-    EXPECT_EQ(-1, append_and_increment(&dstp, src, &dst_size))
-            << "String should fail because it's too large to fit";
-}
-
 TEST_F(UtilsTest, CreateDataPath) {
     EXPECT_EQ("/data", create_data_path(nullptr));
     EXPECT_EQ("/mnt/expand/57f8f4bc-abf4-655f-bf67-946fc0f9f25b",
@@ -545,28 +372,50 @@
 }
 
 TEST_F(UtilsTest, CreatePrimaryCurrentProfile) {
-    std::string expected =
+    std::string expected_base =
         create_primary_current_profile_package_dir_path(0, "com.example") + "/primary.prof";
-    EXPECT_EQ(expected,
-            create_current_profile_path(/*user*/0, "com.example", /*is_secondary*/false));
+    EXPECT_EQ(expected_base,
+            create_current_profile_path(/*user*/0, "com.example", "primary.prof",
+                    /*is_secondary*/false));
+
+    std::string expected_split =
+        create_primary_current_profile_package_dir_path(0, "com.example") + "/split.prof";
+    EXPECT_EQ(expected_split,
+            create_current_profile_path(/*user*/0, "com.example", "split.prof",
+                    /*is_secondary*/false));
 }
 
 TEST_F(UtilsTest, CreatePrimaryReferenceProfile) {
-    std::string expected =
+    std::string expected_base =
         create_primary_reference_profile_package_dir_path("com.example") + "/primary.prof";
-    EXPECT_EQ(expected,
-            create_reference_profile_path("com.example", /*is_secondary*/false));
+    EXPECT_EQ(expected_base,
+            create_reference_profile_path("com.example", "primary.prof", /*is_secondary*/false));
+
+    std::string expected_split =
+        create_primary_reference_profile_package_dir_path("com.example") + "/split.prof";
+    EXPECT_EQ(expected_split,
+            create_reference_profile_path("com.example", "split.prof", /*is_secondary*/false));
+}
+
+TEST_F(UtilsTest, CreateProfileSnapshot) {
+    std::string expected_base =
+        create_primary_reference_profile_package_dir_path("com.example") + "/primary.prof.snapshot";
+    EXPECT_EQ(expected_base, create_snapshot_profile_path("com.example", "primary.prof"));
+
+    std::string expected_split =
+        create_primary_reference_profile_package_dir_path("com.example") + "/split.prof.snapshot";
+    EXPECT_EQ(expected_split, create_snapshot_profile_path("com.example", "split.prof"));
 }
 
 TEST_F(UtilsTest, CreateSecondaryCurrentProfile) {
     EXPECT_EQ("/data/user/0/com.example/oat/secondary.dex.cur.prof",
-            create_current_profile_path(/*user*/0,
+            create_current_profile_path(/*user*/0, "com.example",
                     "/data/user/0/com.example/secondary.dex", /*is_secondary*/true));
 }
 
 TEST_F(UtilsTest, CreateSecondaryReferenceProfile) {
     EXPECT_EQ("/data/user/0/com.example/oat/secondary.dex.prof",
-            create_reference_profile_path(
+            create_reference_profile_path("com.example",
                     "/data/user/0/com.example/secondary.dex", /*is_secondary*/true));
 }
 
@@ -587,6 +436,7 @@
 TEST_F(UtilsTest, ValidateSecondaryDexFilesPath) {
     std::string package_name = "com.test.app";
     std::string app_dir_ce_user_0 = "/data/data/" + package_name;
+    std::string app_dir_ce_user_0_link = "/data/user/0/" + package_name;
     std::string app_dir_ce_user_10 = "/data/user/10/" + package_name;
 
     std::string app_dir_de_user_0 = "/data/user_de/0/" + package_name;
@@ -608,6 +458,8 @@
     // Standard path for user 0 on CE storage.
     pass_secondary_dex_validation(
         package_name, app_dir_ce_user_0 + "/ce0.dex", app_uid_for_user_0, FLAG_STORAGE_CE);
+    pass_secondary_dex_validation(
+        package_name, app_dir_ce_user_0_link + "/ce0.dex", app_uid_for_user_0, FLAG_STORAGE_CE);
     // Standard path for user 10 on CE storage.
     pass_secondary_dex_validation(
         package_name, app_dir_ce_user_10 + "/ce10.dex", app_uid_for_user_10, FLAG_STORAGE_CE);
@@ -653,5 +505,29 @@
         package_name, app_dir_ce_user_10 + "/" + too_long, app_uid_for_user_10, FLAG_STORAGE_CE);
 }
 
+TEST_F(UtilsTest, ValidateApkPath) {
+    EXPECT_EQ(0, validate_apk_path("/data/app/com.example"));
+    EXPECT_EQ(0, validate_apk_path("/data/app/com.example/file"));
+    EXPECT_EQ(0, validate_apk_path("/data/app/com.example//file"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/file"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/dir/file"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/dir//file"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/dir/dir/file"));
+    EXPECT_NE(0, validate_apk_path("/data/app/com.example/dir/dir/dir//file"));
+}
+
+TEST_F(UtilsTest, ValidateApkPathSubdirs) {
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example/file"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example//file"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example/dir/"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example/dir/file"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example/dir/dir/file"));
+    EXPECT_EQ(0, validate_apk_path_subdirs("/data/app/com.example/dir/dir//file"));
+    EXPECT_NE(0, validate_apk_path_subdirs("/data/app/com.example/dir/dir/dir/file"));
+    EXPECT_NE(0, validate_apk_path_subdirs("/data/app/com.example/dir/dir/dir//file"));
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/tests/test_utils.h b/cmds/installd/tests/test_utils.h
new file mode 100644
index 0000000..b8785c6
--- /dev/null
+++ b/cmds/installd/tests/test_utils.h
@@ -0,0 +1,134 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+
+#include <android-base/logging.h>
+#include <selinux/android.h>
+
+uint8_t kBase64Map[256] = {
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
+     52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255,
+    255, 254, 255, 255, 255,   0,   1,   2,   3,   4,   5,   6,
+      7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,
+     19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
+    255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,
+     37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
+     49,  50,  51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+    255, 255, 255, 255
+};
+
+uint8_t* DecodeBase64(const char* src, size_t* dst_size) {
+    CHECK(dst_size != nullptr);
+    std::vector<uint8_t> tmp;
+    uint32_t t = 0, y = 0;
+    int g = 3;
+    for (size_t i = 0; src[i] != '\0'; ++i) {
+        uint8_t c = kBase64Map[src[i] & 0xFF];
+        if (c == 255) continue;
+        // the final = symbols are read and used to trim the remaining bytes
+        if (c == 254) {
+            c = 0;
+            // prevent g < 0 which would potentially allow an overflow later
+            if (--g < 0) {
+                *dst_size = 0;
+                return nullptr;
+            }
+        } else if (g != 3) {
+            // we only allow = to be at the end
+            *dst_size = 0;
+            return nullptr;
+        }
+        t = (t << 6) | c;
+        if (++y == 4) {
+            tmp.push_back((t >> 16) & 255);
+            if (g > 1) {
+                tmp.push_back((t >> 8) & 255);
+            }
+            if (g > 2) {
+                tmp.push_back(t & 255);
+            }
+            y = t = 0;
+        }
+    }
+    if (y != 0) {
+        *dst_size = 0;
+        return nullptr;
+    }
+    std::unique_ptr<uint8_t[]> dst(new uint8_t[tmp.size()]);
+    *dst_size = tmp.size();
+    std::copy(tmp.begin(), tmp.end(), dst.get());
+    return dst.release();
+}
+
+bool WriteBase64ToFile(const char* base64, const std::string& file,
+        uid_t uid, gid_t gid, int mode) {
+    CHECK(base64 != nullptr);
+    size_t length;
+    std::unique_ptr<uint8_t[]> bytes(DecodeBase64(base64, &length));
+    CHECK(bytes != nullptr);
+
+
+    int fd = open(file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+
+    if (fd < 0) {
+        PLOG(ERROR) << "Could not open file " << file;
+        return false;
+    }
+
+    size_t wrote = 0;
+    while (wrote < length) {
+        ssize_t cur = write(fd, bytes.get() + wrote, length - wrote);
+        if (cur == -1) {
+            PLOG(ERROR) << "Could not write file " << file;
+            return false;
+        }
+        wrote += cur;
+    }
+
+    if (::chown(file.c_str(), uid, gid) != 0) {
+        PLOG(ERROR) << "Could not chown file " << file;
+        return false;
+    }
+    if (::chmod(file.c_str(), mode) != 0) {
+        PLOG(ERROR) << "Could not chmod file " << file;
+        return false;
+    }
+    return true;
+}
+
+// TODO(calin): fix dexopt drop_capabilities and move to general utils (b/69678790).
+bool DropCapabilities(uid_t uid, gid_t gid) {
+    if (setgid(gid) != 0) {
+        PLOG(ERROR) << "setgid failed: " <<  gid;
+        return false;
+    }
+    if (setuid(uid) != 0) {
+        PLOG(ERROR) << "setuid failed: " <<  uid;
+        return false;
+    }
+    // drop capabilities
+    struct __user_cap_header_struct capheader;
+    struct __user_cap_data_struct capdata[2];
+    memset(&capheader, 0, sizeof(capheader));
+    memset(&capdata, 0, sizeof(capdata));
+    capheader.version = _LINUX_CAPABILITY_VERSION_3;
+    if (capset(&capheader, &capdata[0]) < 0) {
+        PLOG(ERROR) << "capset failed";
+        return false;
+    }
+
+    return true;
+}
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index dd32ac6..1ff45e4 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -26,7 +26,9 @@
 #include <sys/statvfs.h>
 
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <cutils/fs.h>
 #include <cutils/properties.h>
 #include <log/log.h>
@@ -40,7 +42,9 @@
 
 #define DEBUG_XATTRS 0
 
+using android::base::EndsWith;
 using android::base::StringPrintf;
+using android::base::unique_fd;
 
 namespace android {
 namespace installd {
@@ -87,6 +91,20 @@
             create_data_user_ce_path(volume_uuid, user).c_str(), package_name);
 }
 
+/**
+ * Create the path name where package data should be stored for the given
+ * volume UUID, package name, and user ID. An empty UUID is assumed to be
+ * internal storage.
+ * Compared to create_data_user_ce_package_path this method always return the
+ * ".../user/..." directory.
+ */
+std::string create_data_user_ce_package_path_as_user_link(
+        const char* volume_uuid, userid_t userid, const char* package_name) {
+    check_package_name(package_name);
+    std::string data(create_data_path(volume_uuid));
+    return StringPrintf("%s/user/%u/%s", data.c_str(), userid, package_name);
+}
+
 std::string create_data_user_ce_package_path(const char* volume_uuid, userid_t user,
         const char* package_name, ino_t ce_data_inode) {
     // For testing purposes, rely on the inode when defined; this could be
@@ -129,24 +147,6 @@
             create_data_user_de_path(volume_uuid, user).c_str(), package_name);
 }
 
-int create_pkg_path(char path[PKG_PATH_MAX], const char *pkgname,
-        const char *postfix, userid_t userid) {
-    if (!is_valid_package_name(pkgname)) {
-        path[0] = '\0';
-        return -1;
-    }
-
-    std::string _tmp(create_data_user_ce_package_path(nullptr, userid, pkgname) + postfix);
-    const char* tmp = _tmp.c_str();
-    if (strlen(tmp) >= PKG_PATH_MAX) {
-        path[0] = '\0';
-        return -1;
-    } else {
-        strcpy(path, tmp);
-        return 0;
-    }
-}
-
 std::string create_data_path(const char* volume_uuid) {
     if (volume_uuid == nullptr) {
         return "/data";
@@ -213,7 +213,7 @@
 }
 
 std::string create_primary_cur_profile_dir_path(userid_t userid) {
-    return StringPrintf("%s/cur/%u", android_profiles_dir.path, userid);
+    return StringPrintf("%s/cur/%u", android_profiles_dir.c_str(), userid);
 }
 
 std::string create_primary_current_profile_package_dir_path(userid_t user,
@@ -224,12 +224,12 @@
 }
 
 std::string create_primary_ref_profile_dir_path() {
-    return StringPrintf("%s/ref", android_profiles_dir.path);
+    return StringPrintf("%s/ref", android_profiles_dir.c_str());
 }
 
 std::string create_primary_reference_profile_package_dir_path(const std::string& package_name) {
     check_package_name(package_name.c_str());
-    return StringPrintf("%s/ref/%s", android_profiles_dir.path, package_name.c_str());
+    return StringPrintf("%s/ref/%s", android_profiles_dir.c_str(), package_name.c_str());
 }
 
 std::string create_data_dalvik_cache_path() {
@@ -239,7 +239,7 @@
 // Keep profile paths in sync with ActivityThread and LoadedApk.
 const std::string PROFILE_EXT = ".prof";
 const std::string CURRENT_PROFILE_EXT = ".cur";
-const std::string PRIMARY_PROFILE_NAME = "primary" + PROFILE_EXT;
+const std::string SNAPSHOT_PROFILE_EXT = ".snapshot";
 
 // Gets the parent directory and the file name for the given secondary dex path.
 // Returns true on success, false on failure (if the dex_path does not have the expected
@@ -259,8 +259,8 @@
    return true;
 }
 
-std::string create_current_profile_path(userid_t user, const std::string& location,
-        bool is_secondary_dex) {
+std::string create_current_profile_path(userid_t user, const std::string& package_name,
+        const std::string& location, bool is_secondary_dex) {
     if (is_secondary_dex) {
         // Secondary dex current profiles are stored next to the dex files under the oat folder.
         std::string dex_dir;
@@ -272,12 +272,14 @@
                 PROFILE_EXT.c_str());
     } else {
         // Profiles for primary apks are under /data/misc/profiles/cur.
-        std::string profile_dir = create_primary_current_profile_package_dir_path(user, location);
-        return StringPrintf("%s/%s", profile_dir.c_str(), PRIMARY_PROFILE_NAME.c_str());
+        std::string profile_dir = create_primary_current_profile_package_dir_path(
+                user, package_name);
+        return StringPrintf("%s/%s", profile_dir.c_str(), location.c_str());
     }
 }
 
-std::string create_reference_profile_path(const std::string& location, bool is_secondary_dex) {
+std::string create_reference_profile_path(const std::string& package_name,
+        const std::string& location, bool is_secondary_dex) {
     if (is_secondary_dex) {
         // Secondary dex reference profiles are stored next to the dex files under the oat folder.
         std::string dex_dir;
@@ -288,11 +290,18 @@
                 dex_dir.c_str(), dex_name.c_str(), PROFILE_EXT.c_str());
     } else {
         // Reference profiles for primary apks are stored in /data/misc/profile/ref.
-        std::string profile_dir = create_primary_reference_profile_package_dir_path(location);
-        return StringPrintf("%s/%s", profile_dir.c_str(), PRIMARY_PROFILE_NAME.c_str());
+        std::string profile_dir = create_primary_reference_profile_package_dir_path(package_name);
+        return StringPrintf("%s/%s", profile_dir.c_str(), location.c_str());
     }
 }
 
+std::string create_snapshot_profile_path(const std::string& package,
+        const std::string& profile_name) {
+    std::string ref_profile = create_reference_profile_path(package, profile_name,
+            /*is_secondary_dex*/ false);
+    return ref_profile + SNAPSHOT_PROFILE_EXT;
+}
+
 std::vector<userid_t> get_known_users(const char* volume_uuid) {
     std::vector<userid_t> users;
 
@@ -378,20 +387,6 @@
     return 0;
 }
 
-int create_move_path(char path[PKG_PATH_MAX],
-    const char* pkgname,
-    const char* leaf,
-    userid_t userid ATTRIBUTE_UNUSED)
-{
-    if ((android_data_dir.len + strlen(PRIMARY_USER_PREFIX) + strlen(pkgname) + strlen(leaf) + 1)
-            >= PKG_PATH_MAX) {
-        return -1;
-    }
-
-    sprintf(path, "%s%s%s/%s", android_data_dir.path, PRIMARY_USER_PREFIX, pkgname, leaf);
-    return 0;
-}
-
 /**
  * Checks whether the package name is valid. Returns -1 on error and
  * 0 on success.
@@ -756,27 +751,47 @@
     }
 }
 
+void remove_path_xattr(const std::string& path, const char* inode_xattr) {
+    if (removexattr(path.c_str(), inode_xattr) && errno != ENODATA) {
+        PLOG(ERROR) << "Failed to remove xattr " << inode_xattr << " at " << path;
+    }
+}
+
 /**
  * Validate that the path is valid in the context of the provided directory.
  * The path is allowed to have at most one subdirectory and no indirections
  * to top level directories (i.e. have "..").
  */
-static int validate_path(const dir_rec_t* dir, const char* path, int maxSubdirs) {
-    size_t dir_len = dir->len;
-    const char* subdir = strchr(path + dir_len, '/');
-
-    // Only allow the path to have at most one subdirectory.
-    if (subdir != NULL) {
-        ++subdir;
-        if ((--maxSubdirs == 0) && strchr(subdir, '/') != NULL) {
-            ALOGE("invalid apk path '%s' (subdir?)\n", path);
-            return -1;
-        }
+static int validate_path(const std::string& dir, const std::string& path, int maxSubdirs) {
+    // Argument sanity checking
+    if (dir.find('/') != 0 || dir.rfind('/') != dir.size() - 1
+            || dir.find("..") != std::string::npos) {
+        LOG(ERROR) << "Invalid directory " << dir;
+        return -1;
+    }
+    if (path.find("..") != std::string::npos) {
+        LOG(ERROR) << "Invalid path " << path;
+        return -1;
     }
 
-    // Directories can't have a period directly after the directory markers to prevent "..".
-    if ((path[dir_len] == '.') || ((subdir != NULL) && (*subdir == '.'))) {
-        ALOGE("invalid apk path '%s' (trickery)\n", path);
+    if (path.compare(0, dir.size(), dir) != 0) {
+        // Common case, path isn't under directory
+        return -1;
+    }
+
+    // Count number of subdirectories
+    auto pos = path.find('/', dir.size());
+    int count = 0;
+    while (pos != std::string::npos) {
+        auto next = path.find('/', pos + 1);
+        if (next > pos + 1) {
+            count++;
+        }
+        pos = next;
+    }
+
+    if (count > maxSubdirs) {
+        LOG(ERROR) << "Invalid path depth " << path << " when tested against " << dir;
         return -1;
     }
 
@@ -788,20 +803,17 @@
  * if it is a system app or -1 if it is not.
  */
 int validate_system_app_path(const char* path) {
-    size_t i;
-
-    for (i = 0; i < android_system_dirs.count; i++) {
-        const size_t dir_len = android_system_dirs.dirs[i].len;
-        if (!strncmp(path, android_system_dirs.dirs[i].path, dir_len)) {
-            return validate_path(android_system_dirs.dirs + i, path, 1);
+    std::string path_ = path;
+    for (const auto& dir : android_system_dirs) {
+        if (validate_path(dir, path, 1) == 0) {
+            return 0;
         }
     }
-
     return -1;
 }
 
 bool validate_secondary_dex_path(const std::string& pkgname, const std::string& dex_path,
-        const char* volume_uuid, int uid, int storage_flag, bool validate_package_path) {
+        const char* volume_uuid, int uid, int storage_flag) {
     CHECK(storage_flag == FLAG_STORAGE_CE || storage_flag == FLAG_STORAGE_DE);
 
     // Empty paths are not allowed.
@@ -815,16 +827,20 @@
     // The path should be at most PKG_PATH_MAX long.
     if (dex_path.size() > PKG_PATH_MAX) { return false; }
 
-    if (validate_package_path) {
-        // If we are asked to validate the package path check that
-        // the dex_path is under the app data directory.
-        std::string app_private_dir = storage_flag == FLAG_STORAGE_CE
+    // The dex_path should be under the app data directory.
+    std::string app_private_dir = storage_flag == FLAG_STORAGE_CE
             ? create_data_user_ce_package_path(
                     volume_uuid, multiuser_get_user_id(uid), pkgname.c_str())
             : create_data_user_de_package_path(
                     volume_uuid, multiuser_get_user_id(uid), pkgname.c_str());
 
-        if (strncmp(dex_path.c_str(), app_private_dir.c_str(), app_private_dir.size()) != 0) {
+    if (strncmp(dex_path.c_str(), app_private_dir.c_str(), app_private_dir.size()) != 0) {
+        // The check above might fail if the dex file is accessed via the /data/user/0 symlink.
+        // If that's the case, attempt to validate against the user data link.
+        std::string app_private_dir_symlink = create_data_user_ce_package_path_as_user_link(
+                volume_uuid, multiuser_get_user_id(uid), pkgname.c_str());
+        if (strncmp(dex_path.c_str(), app_private_dir_symlink.c_str(),
+                app_private_dir_symlink.size()) != 0) {
             return false;
         }
     }
@@ -834,116 +850,30 @@
 }
 
 /**
- * Get the contents of a environment variable that contains a path. Caller
- * owns the string that is inserted into the directory record. Returns
- * 0 on success and -1 on error.
- */
-int get_path_from_env(dir_rec_t* rec, const char* var) {
-    const char* path = getenv(var);
-    int ret = get_path_from_string(rec, path);
-    if (ret < 0) {
-        ALOGW("Problem finding value for environment variable %s\n", var);
-    }
-    return ret;
-}
-
-/**
- * Puts the string into the record as a directory. Appends '/' to the end
- * of all paths. Caller owns the string that is inserted into the directory
- * record. A null value will result in an error.
- *
- * Returns 0 on success and -1 on error.
- */
-int get_path_from_string(dir_rec_t* rec, const char* path) {
-    if (path == NULL) {
-        return -1;
-    } else {
-        const size_t path_len = strlen(path);
-        if (path_len <= 0) {
-            return -1;
-        }
-
-        // Make sure path is absolute.
-        if (path[0] != '/') {
-            return -1;
-        }
-
-        if (path[path_len - 1] == '/') {
-            // Path ends with a forward slash. Make our own copy.
-
-            rec->path = strdup(path);
-            if (rec->path == NULL) {
-                return -1;
-            }
-
-            rec->len = path_len;
-        } else {
-            // Path does not end with a slash. Generate a new string.
-            char *dst;
-
-            // Add space for slash and terminating null.
-            size_t dst_size = path_len + 2;
-
-            rec->path = (char*) malloc(dst_size);
-            if (rec->path == NULL) {
-                return -1;
-            }
-
-            dst = rec->path;
-
-            if (append_and_increment(&dst, path, &dst_size) < 0
-                    || append_and_increment(&dst, "/", &dst_size)) {
-                ALOGE("Error canonicalizing path");
-                return -1;
-            }
-
-            rec->len = dst - rec->path;
-        }
-    }
-    return 0;
-}
-
-int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix) {
-    dst->len = src->len + strlen(suffix);
-    const size_t dstSize = dst->len + 1;
-    dst->path = (char*) malloc(dstSize);
-
-    if (dst->path == NULL
-            || snprintf(dst->path, dstSize, "%s%s", src->path, suffix)
-                    != (ssize_t) dst->len) {
-        ALOGE("Could not allocate memory to hold appended path; aborting\n");
-        return -1;
-    }
-
-    return 0;
-}
-
-/**
  * Check whether path points to a valid path for an APK file. The path must
  * begin with a whitelisted prefix path and must be no deeper than |maxSubdirs| within
  * that path. Returns -1 when an invalid path is encountered and 0 when a valid path
  * is encountered.
  */
-static int validate_apk_path_internal(const char *path, int maxSubdirs) {
-    const dir_rec_t* dir = NULL;
-    if (!strncmp(path, android_app_dir.path, android_app_dir.len)) {
-        dir = &android_app_dir;
-    } else if (!strncmp(path, android_app_private_dir.path, android_app_private_dir.len)) {
-        dir = &android_app_private_dir;
-    } else if (!strncmp(path, android_app_ephemeral_dir.path, android_app_ephemeral_dir.len)) {
-        dir = &android_app_ephemeral_dir;
-    } else if (!strncmp(path, android_asec_dir.path, android_asec_dir.len)) {
-        dir = &android_asec_dir;
-    } else if (!strncmp(path, android_mnt_expand_dir.path, android_mnt_expand_dir.len)) {
-        dir = &android_mnt_expand_dir;
-        if (maxSubdirs < 2) {
-            maxSubdirs = 2;
+static int validate_apk_path_internal(const std::string& path, int maxSubdirs) {
+    if (validate_path(android_app_dir, path, maxSubdirs) == 0) {
+        return 0;
+    } else if (validate_path(android_app_private_dir, path, maxSubdirs) == 0) {
+        return 0;
+    } else if (validate_path(android_app_ephemeral_dir, path, maxSubdirs) == 0) {
+        return 0;
+    } else if (validate_path(android_asec_dir, path, maxSubdirs) == 0) {
+        return 0;
+    } else if (android::base::StartsWith(path, android_mnt_expand_dir)) {
+        // Rewrite the path as if it were on internal storage, and test that
+        size_t end = path.find('/', android_mnt_expand_dir.size() + 1);
+        if (end != std::string::npos) {
+            auto modified = path;
+            modified.replace(0, end + 1, android_data_dir);
+            return validate_apk_path_internal(modified, maxSubdirs);
         }
-    } else {
-        return -1;
     }
-
-    return validate_path(dir, path, maxSubdirs);
+    return -1;
 }
 
 int validate_apk_path(const char* path) {
@@ -954,48 +884,6 @@
     return validate_apk_path_internal(path, 3 /* maxSubdirs */);
 }
 
-int append_and_increment(char** dst, const char* src, size_t* dst_size) {
-    ssize_t ret = strlcpy(*dst, src, *dst_size);
-    if (ret < 0 || (size_t) ret >= *dst_size) {
-        return -1;
-    }
-    *dst += ret;
-    *dst_size -= ret;
-    return 0;
-}
-
-char *build_string2(const char *s1, const char *s2) {
-    if (s1 == NULL || s2 == NULL) return NULL;
-
-    int len_s1 = strlen(s1);
-    int len_s2 = strlen(s2);
-    int len = len_s1 + len_s2 + 1;
-    char *result = (char *) malloc(len);
-    if (result == NULL) return NULL;
-
-    strcpy(result, s1);
-    strcpy(result + len_s1, s2);
-
-    return result;
-}
-
-char *build_string3(const char *s1, const char *s2, const char *s3) {
-    if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL;
-
-    int len_s1 = strlen(s1);
-    int len_s2 = strlen(s2);
-    int len_s3 = strlen(s3);
-    int len = len_s1 + len_s2 + len_s3 + 1;
-    char *result = (char *) malloc(len);
-    if (result == NULL) return NULL;
-
-    strcpy(result, s1);
-    strcpy(result + len_s1, s2);
-    strcpy(result + len_s1 + len_s2, s3);
-
-    return result;
-}
-
 int ensure_config_user_dirs(userid_t userid) {
     // writable by system, readable by any app within the same user
     const int uid = multiuser_get_uid(userid, AID_SYSTEM);
@@ -1068,7 +956,7 @@
     } else {
         // Mismatched GID/mode is recoverable; fall through to update
         LOG(DEBUG) << "Mismatched cache GID/mode at " << path << ": found " << st.st_gid
-                << " but expected " << gid;
+                << "/" << actual_mode << " but expected " << gid << "/" << target_mode;
     }
 
     // Directory is owned correctly, but GID or mode mismatch means it's
@@ -1104,5 +992,75 @@
     return 0;
 }
 
+// Collect all non empty profiles from the given directory and puts then into profile_paths.
+// The profiles are identified based on PROFILE_EXT extension.
+// If a subdirectory or profile file cannot be opened the method logs a warning and moves on.
+// It returns true if there were no errors at all, and false otherwise.
+static bool collect_profiles(DIR* d,
+                             const std::string& current_path,
+                             std::vector<std::string>* profiles_paths) {
+    int32_t dir_fd = dirfd(d);
+    if (dir_fd < 0) {
+        return false;
+    }
+
+    bool result = true;
+    struct dirent* dir_entry;
+    while ((dir_entry = readdir(d))) {
+        std::string name = dir_entry->d_name;
+        std::string local_path = current_path + "/" + name;
+
+        if (dir_entry->d_type == DT_REG) {
+            // Check if this is a non empty profile file.
+            if (EndsWith(name, PROFILE_EXT)) {
+                struct stat st;
+                if (stat(local_path.c_str(), &st) != 0) {
+                    PLOG(WARNING) << "Cannot stat local path " << local_path;
+                    result = false;
+                    continue;
+                } else if (st.st_size > 0) {
+                    profiles_paths->push_back(local_path);
+                }
+            }
+        } else if (dir_entry->d_type == DT_DIR) {
+            // always skip "." and ".."
+            if (name == "." || name == "..") {
+                continue;
+            }
+
+            unique_fd subdir_fd(openat(dir_fd, name.c_str(),
+                    O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC));
+            if (subdir_fd < 0) {
+                PLOG(WARNING) << "Could not open dir path " << local_path;
+                result = false;
+                continue;
+            }
+
+            DIR* subdir = fdopendir(subdir_fd);
+            if (subdir == NULL) {
+                PLOG(WARNING) << "Could not open dir path " << local_path;
+                result = false;
+                continue;
+            }
+            bool new_result = collect_profiles(subdir, local_path, profiles_paths);
+            result = result && new_result;
+            if (closedir(subdir) != 0) {
+                PLOG(WARNING) << "Could not close dir path " << local_path;
+            }
+        }
+    }
+
+    return result;
+}
+
+bool collect_profiles(std::vector<std::string>* profiles_paths) {
+    DIR* d = opendir(android_profiles_dir.c_str());
+    if (d == NULL) {
+        return false;
+    } else {
+        return collect_profiles(d, android_profiles_dir, profiles_paths);
+    }
+}
+
 }  // namespace installd
 }  // namespace android
diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h
index e938042..5829c4f 100644
--- a/cmds/installd/utils.h
+++ b/cmds/installd/utils.h
@@ -36,23 +36,16 @@
 #define BYPASS_QUOTA 0
 #define BYPASS_SDCARDFS 0
 
-#define APPLY_HARD_QUOTAS 1
+#define APPLY_HARD_QUOTAS 0
 
 namespace android {
 namespace installd {
 
-struct dir_rec_t;
-
 constexpr const char* kXattrInodeCache = "user.inode_cache";
 constexpr const char* kXattrInodeCodeCache = "user.inode_code_cache";
 constexpr const char* kXattrCacheGroup = "user.cache_group";
 constexpr const char* kXattrCacheTombstone = "user.cache_tombstone";
 
-int create_pkg_path(char path[PKG_PATH_MAX],
-                    const char *pkgname,
-                    const char *postfix,
-                    userid_t userid);
-
 std::string create_data_path(const char* volume_uuid);
 
 std::string create_data_app_path(const char* volume_uuid);
@@ -67,6 +60,8 @@
         userid_t user, const char* package_name, ino_t ce_data_inode);
 std::string create_data_user_de_package_path(const char* volume_uuid,
         userid_t user, const char* package_name);
+std::string create_data_user_ce_package_path_as_user_link(
+        const char* volume_uuid, userid_t userid, const char* package_name);
 
 std::string create_data_media_path(const char* volume_uuid, userid_t userid);
 std::string create_data_media_obb_path(const char* volume_uuid, const char* package_name);
@@ -85,9 +80,17 @@
 std::string create_primary_reference_profile_package_dir_path(const std::string& package_name);
 
 std::string create_current_profile_path(
-        userid_t user, const std::string& package_name, bool is_secondary_dex);
+        userid_t user,
+        const std::string& package_name,
+        const std::string& location,
+        bool is_secondary_dex);
 std::string create_reference_profile_path(
-        const std::string& package_name, bool is_secondary_dex);
+        const std::string& package_name,
+        const std::string& location,
+        bool is_secondary_dex);
+std::string create_snapshot_profile_path(
+        const std::string& package,
+        const std::string& profile_name);
 
 std::vector<userid_t> get_known_users(const char* volume_uuid);
 
@@ -96,11 +99,6 @@
 
 int create_user_config_path(char path[PKG_PATH_MAX], userid_t userid);
 
-int create_move_path(char path[PKG_PATH_MAX],
-                     const char* pkgname,
-                     const char* leaf,
-                     userid_t userid);
-
 bool is_valid_filename(const std::string& name);
 bool is_valid_package_name(const std::string& packageName);
 
@@ -122,25 +120,15 @@
 
 int write_path_inode(const std::string& parent, const char* name, const char* inode_xattr);
 std::string read_path_inode(const std::string& parent, const char* name, const char* inode_xattr);
+void remove_path_xattr(const std::string& path, const char* inode_xattr);
 
 int validate_system_app_path(const char* path);
 bool validate_secondary_dex_path(const std::string& pkgname, const std::string& dex_path,
-        const char* volume_uuid, int uid, int storage_flag, bool validate_package_path = true);
-
-int get_path_from_env(dir_rec_t* rec, const char* var);
-
-int get_path_from_string(dir_rec_t* rec, const char* path);
-
-int copy_and_append(dir_rec_t* dst, const dir_rec_t* src, const char* suffix);
+        const char* volume_uuid, int uid, int storage_flag);
 
 int validate_apk_path(const char *path);
 int validate_apk_path_subdirs(const char *path);
 
-int append_and_increment(char** dst, const char* src, size_t* dst_size);
-
-char *build_string2(const char *s1, const char *s2);
-char *build_string3(const char *s1, const char *s2, const char *s3);
-
 int ensure_config_user_dirs(userid_t userid);
 
 int wait_child(pid_t pid);
@@ -148,6 +136,12 @@
 int prepare_app_cache_dir(const std::string& parent, const char* name, mode_t target_mode,
         uid_t uid, gid_t gid);
 
+// Collect all non empty profiles from the global profile directory and
+// put then into profile_paths. The profiles are identified based on PROFILE_EXT extension.
+// If a subdirectory or profile file cannot be opened the method logs a warning and moves on.
+// It returns true if there were no errors at all, and false otherwise.
+bool collect_profiles(std::vector<std::string>* profiles_paths);
+
 }  // namespace installd
 }  // namespace android
 
diff --git a/cmds/ip-up-vpn/Android.mk b/cmds/ip-up-vpn/Android.mk
index 36bbdf5..e1e2204 100644
--- a/cmds/ip-up-vpn/Android.mk
+++ b/cmds/ip-up-vpn/Android.mk
@@ -18,6 +18,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := ip-up-vpn.c
+LOCAL_CFLAGS := -Wall -Werror
 LOCAL_SHARED_LIBRARIES := libcutils liblog
 LOCAL_MODULE := ip-up-vpn
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/ppp
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 67b5b46..93d878b 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -20,16 +20,27 @@
         "libutils",
         "libhidlbase",
         "libhidltransport",
+        "libhidl-gen-hash",
         "libhidl-gen-utils",
         "libvintf",
     ],
+    static_libs: [
+        "libprocpartition",
+    ],
     srcs: [
         "DebugCommand.cpp",
+        "HelpCommand.cpp",
         "Lshal.cpp",
         "ListCommand.cpp",
         "PipeRelay.cpp",
+        "TableEntry.cpp",
+        "TextTable.cpp",
         "utils.cpp",
     ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_defaults {
@@ -37,10 +48,15 @@
     shared_libs: [
         "libbase",
         "libhidlbase",
+        "libhidl-gen-utils",
         "libhidltransport",
         "liblshal",
         "libutils",
-    ]
+    ],
+    static_libs: [
+        "libprocpartition",
+    ],
+    cflags: ["-Wall", "-Werror"],
 }
 
 cc_binary {
@@ -59,6 +75,7 @@
         "libgmock"
     ],
     shared_libs: [
+        "libvintf",
         "android.hardware.tests.baz@1.0"
     ],
     srcs: [
diff --git a/cmds/lshal/Command.h b/cmds/lshal/Command.h
new file mode 100644
index 0000000..4f128ab
--- /dev/null
+++ b/cmds/lshal/Command.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 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 FRAMEWORK_NATIVE_CMDS_LSHAL_COMMAND_H_
+#define FRAMEWORK_NATIVE_CMDS_LSHAL_COMMAND_H_
+
+#include "utils.h"
+
+namespace android {
+namespace lshal {
+
+class Lshal;
+
+// Base class for all *Commands
+class Command {
+public:
+    Command(Lshal& lshal) : mLshal(lshal) {}
+    virtual ~Command() = default;
+    // Expect optind to be set by Lshal::main and points to the next argument
+    // to process.
+    virtual Status main(const Arg &arg) = 0;
+
+    virtual void usage() const = 0;
+
+    // e.g. "list"
+    virtual std::string getName() const = 0;
+
+    // e.g. "list HALs"
+    virtual std::string getSimpleDescription() const = 0;
+
+protected:
+    Lshal& mLshal;
+};
+
+
+}  // namespace lshal
+}  // namespace android
+
+#endif  // FRAMEWORK_NATIVE_CMDS_LSHAL_LIST_COMMAND_H_
diff --git a/cmds/lshal/DebugCommand.cpp b/cmds/lshal/DebugCommand.cpp
index 672cad6..dd8812d 100644
--- a/cmds/lshal/DebugCommand.cpp
+++ b/cmds/lshal/DebugCommand.cpp
@@ -18,17 +18,31 @@
 
 #include "Lshal.h"
 
+#include <hidl-util/FQName.h>
+
 namespace android {
 namespace lshal {
 
-DebugCommand::DebugCommand(Lshal &lshal) : mLshal(lshal) {
+std::string DebugCommand::getName() const {
+    return "debug";
 }
 
-Status DebugCommand::parseArgs(const std::string &command, const Arg &arg) {
+std::string DebugCommand::getSimpleDescription() const {
+    return "Debug a specified HAL.";
+}
+
+Status DebugCommand::parseArgs(const Arg &arg) {
     if (optind >= arg.argc) {
-        mLshal.usage(command);
         return USAGE;
     }
+
+    // Optargs cannnot be used because the flag should not be considered set
+    // if it should really be contained in mOptions.
+    if (std::string(arg.argv[optind]) == "-E") {
+        mExcludesParentInstances = true;
+        optind++;
+    }
+
     mInterfaceName = arg.argv[optind];
     ++optind;
     for (; optind < arg.argc; ++optind) {
@@ -37,18 +51,41 @@
     return OK;
 }
 
-Status DebugCommand::main(const std::string &command, const Arg &arg) {
-    Status status = parseArgs(command, arg);
+Status DebugCommand::main(const Arg &arg) {
+    Status status = parseArgs(arg);
     if (status != OK) {
         return status;
     }
+
     auto pair = splitFirst(mInterfaceName, '/');
+
+    FQName fqName(pair.first);
+    if (!fqName.isValid() || fqName.isIdentifier() || !fqName.isFullyQualified()) {
+        mLshal.err() << "Invalid fully-qualified name '" << pair.first << "'\n\n";
+        return USAGE;
+    }
+
     return mLshal.emitDebugInfo(
             pair.first, pair.second.empty() ? "default" : pair.second, mOptions,
+            mExcludesParentInstances,
             mLshal.out().buf(),
             mLshal.err());
 }
 
+void DebugCommand::usage() const {
+
+    static const std::string debug =
+            "debug:\n"
+            "    lshal debug [-E] <interface> [options [options [...]]] \n"
+            "        Print debug information of a specified interface.\n"
+            "        -E: excludes debug output if HAL is actually a subclass.\n"
+            "        <inteface>: Format is `android.hardware.foo@1.0::IFoo/default`.\n"
+            "            If instance name is missing `default` is used.\n"
+            "        options: space separated options to IBase::debug.\n";
+
+    mLshal.err() << debug;
+}
+
 }  // namespace lshal
 }  // namespace android
 
diff --git a/cmds/lshal/DebugCommand.h b/cmds/lshal/DebugCommand.h
index fa0f0fa..6e12008 100644
--- a/cmds/lshal/DebugCommand.h
+++ b/cmds/lshal/DebugCommand.h
@@ -21,6 +21,7 @@
 
 #include <android-base/macros.h>
 
+#include "Command.h"
 #include "utils.h"
 
 namespace android {
@@ -28,17 +29,24 @@
 
 class Lshal;
 
-class DebugCommand {
+class DebugCommand : public Command {
 public:
-    DebugCommand(Lshal &lshal);
-    Status main(const std::string &command, const Arg &arg);
+    DebugCommand(Lshal &lshal) : Command(lshal) {}
+    ~DebugCommand() = default;
+    Status main(const Arg &arg) override;
+    void usage() const override;
+    std::string getSimpleDescription() const override;
+    std::string getName() const override;
 private:
-    Status parseArgs(const std::string &command, const Arg &arg);
+    Status parseArgs(const Arg &arg);
 
-    Lshal &mLshal;
     std::string mInterfaceName;
     std::vector<std::string> mOptions;
 
+    // Outputs the actual descriptor of a hal instead of the debug output
+    // if the arguments provided are a superclass of the actual hal impl.
+    bool mExcludesParentInstances;
+
     DISALLOW_COPY_AND_ASSIGN(DebugCommand);
 };
 
diff --git a/cmds/lshal/HelpCommand.cpp b/cmds/lshal/HelpCommand.cpp
new file mode 100644
index 0000000..6773ace
--- /dev/null
+++ b/cmds/lshal/HelpCommand.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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 "HelpCommand.h"
+
+#include "Lshal.h"
+
+namespace android {
+namespace lshal {
+
+std::string HelpCommand::GetName() {
+    return "help";
+}
+
+std::string HelpCommand::getSimpleDescription() const {
+    return "Print help message.";
+}
+
+Status HelpCommand::main(const Arg &arg) {
+    if (optind >= arg.argc) {
+        // `lshal help` prints global usage.
+        mLshal.usage();
+        return OK;
+    }
+    (void)usageOfCommand(arg.argv[optind]);
+    return OK;
+}
+
+Status HelpCommand::usageOfCommand(const std::string& c) const {
+    if (c.empty()) {
+        mLshal.usage();
+        return USAGE;
+    }
+    auto command = mLshal.selectCommand(c);
+    if (command == nullptr) {
+        // from HelpCommand::main, `lshal help unknown`
+        mLshal.usage();
+        return USAGE;
+    }
+
+    command->usage();
+    return USAGE;
+
+}
+
+void HelpCommand::usage() const {
+    mLshal.err()
+            << "help:" << std::endl
+            << "    lshal -h" << std::endl
+            << "    lshal --help" << std::endl
+            << "    lshal help" << std::endl
+            << "        Print this help message" << std::endl;
+    mLshal.forEachCommand([&](const Command* e) {
+        mLshal.err() << "    lshal help " << e->getName() << std::endl
+                     << "        Print help message for " << e->getName() << std::endl;
+    });
+
+}
+
+}  // namespace lshal
+}  // namespace android
+
diff --git a/cmds/lshal/HelpCommand.h b/cmds/lshal/HelpCommand.h
new file mode 100644
index 0000000..cc709f8
--- /dev/null
+++ b/cmds/lshal/HelpCommand.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_
+#define FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_
+
+#include <string>
+
+#include <android-base/macros.h>
+
+#include "Command.h"
+#include "utils.h"
+
+namespace android {
+namespace lshal {
+
+class Lshal;
+
+class HelpCommand : public Command {
+public:
+    HelpCommand(Lshal &lshal) : Command(lshal) {}
+    ~HelpCommand() = default;
+    Status main(const Arg &arg) override;
+    void usage() const override;
+    std::string getSimpleDescription() const override;
+    std::string getName() const override { return GetName(); }
+    static std::string GetName();
+    Status usageOfCommand(const std::string& c) const;
+};
+
+
+}  // namespace lshal
+}  // namespace android
+
+#endif  // FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 7c6cfd9..ff22048 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -25,12 +25,15 @@
 #include <sstream>
 #include <regex>
 
+#include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
+#include <hidl-hash/Hash.h>
 #include <hidl-util/FQName.h>
 #include <private/android_filesystem_config.h>
 #include <sys/stat.h>
 #include <vintf/HalManifest.h>
+#include <vintf/parse_string.h>
 #include <vintf/parse_xml.h>
 
 #include "Lshal.h"
@@ -39,22 +42,35 @@
 #include "utils.h"
 
 using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hidl::base::V1_0::DebugInfo;
+using ::android::hidl::base::V1_0::IBase;
 using ::android::hidl::manager::V1_0::IServiceManager;
 
 namespace android {
 namespace lshal {
 
-ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal), mErr(lshal.err()), mOut(lshal.out()) {
+vintf::SchemaType toSchemaType(Partition p) {
+    return (p == Partition::SYSTEM) ? vintf::SchemaType::FRAMEWORK : vintf::SchemaType::DEVICE;
 }
 
-std::string getCmdline(pid_t pid) {
-    std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline");
-    std::string cmdline;
-    if (!ifs.is_open()) {
-        return "";
-    }
-    ifs >> cmdline;
-    return cmdline;
+NullableOStream<std::ostream> ListCommand::out() const {
+    return mLshal.out();
+}
+
+NullableOStream<std::ostream> ListCommand::err() const {
+    return mLshal.err();
+}
+
+std::string ListCommand::GetName() {
+    return "list";
+}
+std::string ListCommand::getSimpleDescription() const {
+    return "List HALs.";
+}
+
+std::string ListCommand::parseCmdline(pid_t pid) const {
+    return android::procpartition::getCmdline(pid);
 }
 
 const std::string &ListCommand::getCmdline(pid_t pid) {
@@ -62,7 +78,7 @@
     if (pair != mCmdlines.end()) {
         return pair->second;
     }
-    mCmdlines[pid] = ::android::lshal::getCmdline(pid);
+    mCmdlines[pid] = parseCmdline(pid);
     return mCmdlines[pid];
 }
 
@@ -73,7 +89,43 @@
     }), pids->end());
 }
 
-bool scanBinderContext(pid_t pid,
+Partition ListCommand::getPartition(pid_t pid) {
+    auto it = mPartitions.find(pid);
+    if (it != mPartitions.end()) {
+        return it->second;
+    }
+    Partition partition = android::procpartition::getPartition(pid);
+    mPartitions.emplace(pid, partition);
+    return partition;
+}
+
+// Give sensible defaults when nothing can be inferred from runtime.
+// process: Partition inferred from executable location or cmdline.
+Partition ListCommand::resolvePartition(Partition process, const FQName& fqName) const {
+    if (fqName.inPackage("vendor") ||
+        fqName.inPackage("com")) {
+        return Partition::VENDOR;
+    }
+
+    if (fqName.inPackage("android.frameworks") ||
+        fqName.inPackage("android.system") ||
+        fqName.inPackage("android.hidl")) {
+        return Partition::SYSTEM;
+    }
+
+    // Some android.hardware HALs are served from system. Check the value from executable
+    // location / cmdline first.
+    if (fqName.inPackage("android.hardware")) {
+        if (process != Partition::UNKNOWN) {
+            return process;
+        }
+        return Partition::VENDOR;
+    }
+
+    return process;
+}
+
+static bool scanBinderContext(pid_t pid,
         const std::string &contextName,
         std::function<void(const std::string&)> eachLine) {
     std::ifstream ifs("/d/binder/proc/" + std::to_string(pid));
@@ -113,7 +165,7 @@
             uint64_t ptr;
             if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
                 // Should not reach here, but just be tolerant.
-                mErr << "Could not parse number " << ptrString << std::endl;
+                err() << "Could not parse number " << ptrString << std::endl;
                 return;
             }
             const std::string proc = " proc ";
@@ -122,7 +174,7 @@
                 for (const std::string &pidStr : split(line.substr(pos + proc.size()), ' ')) {
                     int32_t pid;
                     if (!::android::base::ParseInt(pidStr, &pid)) {
-                        mErr << "Could not parse number " << pidStr << std::endl;
+                        err() << "Could not parse number " << pidStr << std::endl;
                         return;
                     }
                     pidInfo->refPids[ptr].push_back(pid);
@@ -159,6 +211,16 @@
     });
 }
 
+const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) {
+    auto pair = mCachedPidInfos.insert({serverPid, PidInfo{}});
+    if (pair.second /* did insertion take place? */) {
+        if (!getPidInfo(serverPid, &pair.first->second)) {
+            return nullptr;
+        }
+    }
+    return &pair.first->second;
+}
+
 // Must process hwbinder services first, then passthrough services.
 void ListCommand::forEachTable(const std::function<void(Table &)> &f) {
     f(mServicesTable);
@@ -183,6 +245,9 @@
                 entry.clientCmdlines.push_back(this->getCmdline(pid));
             }
         }
+        for (TableEntry& entry : table) {
+            entry.partition = getPartition(entry.serverPid);
+        }
     });
     // use a double for loop here because lshal doesn't care about efficiency.
     for (TableEntry &packageEntry : mImplementationsTable) {
@@ -204,44 +269,19 @@
             }
         }
     }
-}
 
-void ListCommand::printLine(
-        const std::string &interfaceName,
-        const std::string &transport,
-        const std::string &arch,
-        const std::string &threadUsage,
-        const std::string &server,
-        const std::string &serverCmdline,
-        const std::string &address,
-        const std::string &clients,
-        const std::string &clientCmdlines) const {
-    if (mSelectedColumns & ENABLE_INTERFACE_NAME)
-        mOut << std::setw(80) << interfaceName << "\t";
-    if (mSelectedColumns & ENABLE_TRANSPORT)
-        mOut << std::setw(10) << transport << "\t";
-    if (mSelectedColumns & ENABLE_ARCH)
-        mOut << std::setw(5) << arch << "\t";
-    if (mSelectedColumns & ENABLE_THREADS) {
-        mOut << std::setw(8) << threadUsage << "\t";
-    }
-    if (mSelectedColumns & ENABLE_SERVER_PID) {
-        if (mEnableCmdlines) {
-            mOut << std::setw(15) << serverCmdline << "\t";
-        } else {
-            mOut << std::setw(5)  << server << "\t";
-        }
-    }
-    if (mSelectedColumns & ENABLE_SERVER_ADDR)
-        mOut << std::setw(16) << address << "\t";
-    if (mSelectedColumns & ENABLE_CLIENT_PIDS) {
-        if (mEnableCmdlines) {
-            mOut << std::setw(0)  << clientCmdlines;
-        } else {
-            mOut << std::setw(0)  << clients;
-        }
-    }
-    mOut << std::endl;
+    mServicesTable.setDescription(
+            "All binderized services (registered services through hwservicemanager)");
+    mPassthroughRefTable.setDescription(
+            "All interfaces that getService() has ever return as a passthrough interface;\n"
+            "PIDs / processes shown below might be inaccurate because the process\n"
+            "might have relinquished the interface or might have died.\n"
+            "The Server / Server CMD column can be ignored.\n"
+            "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n"
+            "the library and successfully fetched the passthrough implementation.");
+    mImplementationsTable.setDescription(
+            "All available passthrough implementations (all -impl.so files).\n"
+            "These may return subclasses through their respective HIDL_FETCH_I* functions.");
 }
 
 static inline bool findAndBumpVersion(vintf::ManifestHal* hal, const vintf::Version& version) {
@@ -254,23 +294,12 @@
     return false;
 }
 
-void ListCommand::dumpVintf() const {
+void ListCommand::dumpVintf(const NullableOStream<std::ostream>& out) const {
     using vintf::operator|=;
-    mOut << "<!-- " << std::endl
-         << "    This is a skeleton device manifest. Notes: " << std::endl
-         << "    1. android.hidl.*, android.frameworks.*, android.system.* are not included." << std::endl
-         << "    2. If a HAL is supported in both hwbinder and passthrough transport, " << std::endl
-         << "       only hwbinder is shown." << std::endl
-         << "    3. It is likely that HALs in passthrough transport does not have" << std::endl
-         << "       <interface> declared; users will have to write them by hand." << std::endl
-         << "    4. A HAL with lower minor version can be overridden by a HAL with" << std::endl
-         << "       higher minor version if they have the same name and major version." << std::endl
-         << "    5. sepolicy version is set to 0.0. It is recommended that the entry" << std::endl
-         << "       is removed from the manifest file and written by assemble_vintf" << std::endl
-         << "       at build time." << std::endl
-         << "-->" << std::endl;
+    using vintf::operator<<;
 
     vintf::HalManifest manifest;
+    manifest.setType(toSchemaType(mVintfPartition));
     forEachTable([this, &manifest] (const Table &table) {
         for (const TableEntry &entry : table) {
 
@@ -283,16 +312,27 @@
             auto splittedFqInstanceName = splitFirst(fqInstanceName, '/');
             FQName fqName(splittedFqInstanceName.first);
             if (!fqName.isValid()) {
-                mErr << "Warning: '" << splittedFqInstanceName.first
+                err() << "Warning: '" << splittedFqInstanceName.first
                      << "' is not a valid FQName." << std::endl;
                 continue;
             }
-            // Strip out system libs.
-            if (fqName.inPackage("android.hidl") ||
-                fqName.inPackage("android.frameworks") ||
-                fqName.inPackage("android.system")) {
+
+            if (fqName.package() == gIBaseFqName.package()) {
+                continue; // always remove IBase from manifest
+            }
+
+            Partition partition = resolvePartition(entry.partition, fqName);
+
+            if (partition == Partition::UNKNOWN) {
+                err() << "Warning: Cannot guess the partition of instance " << fqInstanceName
+                      << ". It is removed from the generated manifest." << std::endl;
                 continue;
             }
+
+            if (partition != mVintfPartition) {
+                continue; // strip out instances that is in a different partition.
+            }
+
             std::string interfaceName =
                     &table == &mImplementationsTable ? "" : fqName.name();
             std::string instanceName =
@@ -316,12 +356,12 @@
                         arch = vintf::Arch::ARCH_32_64; break;
                     case lshal::ARCH_UNKNOWN: // fallthrough
                     default:
-                        mErr << "Warning: '" << fqName.package()
+                        err() << "Warning: '" << fqName.package()
                              << "' doesn't have bitness info, assuming 32+64." << std::endl;
                         arch = vintf::Arch::ARCH_32_64;
                 }
             } else {
-                mErr << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl;
+                err() << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl;
                 continue;
             }
 
@@ -329,7 +369,7 @@
             for (vintf::ManifestHal *hal : manifest.getHals(fqName.package())) {
                 if (hal->transport() != transport) {
                     if (transport != vintf::Transport::PASSTHROUGH) {
-                        mErr << "Fatal: should not reach here. Generated result may be wrong for '"
+                        err() << "Fatal: should not reach here. Generated result may be wrong for '"
                              << hal->name << "'."
                              << std::endl;
                     }
@@ -338,8 +378,7 @@
                 }
                 if (findAndBumpVersion(hal, version)) {
                     if (&table != &mImplementationsTable) {
-                        hal->interfaces[interfaceName].name = interfaceName;
-                        hal->interfaces[interfaceName].instances.insert(instanceName);
+                        hal->insertLegacyInstance(interfaceName, instanceName);
                     }
                     hal->transportArch.arch |= arch;
                     done = true;
@@ -349,41 +388,35 @@
             if (done) {
                 continue; // to next TableEntry
             }
-            decltype(vintf::ManifestHal::interfaces) interfaces;
+            vintf::ManifestHal manifestHal{
+                    vintf::HalFormat::HIDL,
+                    std::string{fqName.package()},
+                    {version},
+                    {transport, arch},
+                    {}};
             if (&table != &mImplementationsTable) {
-                interfaces[interfaceName].name = interfaceName;
-                interfaces[interfaceName].instances.insert(instanceName);
+                manifestHal.insertLegacyInstance(interfaceName, instanceName);
             }
-            if (!manifest.add(vintf::ManifestHal{
-                    .format = vintf::HalFormat::HIDL,
-                    .name = fqName.package(),
-                    .versions = {version},
-                    .transportArch = {transport, arch},
-                    .interfaces = interfaces})) {
-                mErr << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl;
+            if (!manifest.add(std::move(manifestHal))) {
+                err() << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl;
             }
         }
     });
-    mOut << vintf::gHalManifestConverter(manifest);
+    out << "<!-- " << std::endl
+         << "    This is a skeleton " << manifest.type() << " manifest. Notes: " << std::endl
+         << INIT_VINTF_NOTES
+         << "-->" << std::endl;
+    out << vintf::gHalManifestConverter(manifest, vintf::SerializeFlag::HALS_NO_FQNAME);
 }
 
-static const std::string &getArchString(Architecture arch) {
-    static const std::string sStr64 = "64";
-    static const std::string sStr32 = "32";
-    static const std::string sStrBoth = "32+64";
-    static const std::string sStrUnknown = "";
-    switch (arch) {
-        case ARCH64:
-            return sStr64;
-        case ARCH32:
-            return sStr32;
-        case ARCH_BOTH:
-            return sStrBoth;
-        case ARCH_UNKNOWN: // fall through
-        default:
-            return sStrUnknown;
-    }
-}
+std::string ListCommand::INIT_VINTF_NOTES{
+    "    1. If a HAL is supported in both hwbinder and passthrough transport, \n"
+    "       only hwbinder is shown.\n"
+    "    2. It is likely that HALs in passthrough transport does not have\n"
+    "       <interface> declared; users will have to write them by hand.\n"
+    "    3. A HAL with lower minor version can be overridden by a HAL with\n"
+    "       higher minor version if they have the same name and major version.\n"
+};
 
 static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo::Architecture a) {
     switch (a) {
@@ -397,68 +430,55 @@
     }
 }
 
-void ListCommand::dumpTable() {
-    mServicesTable.description =
-            "All binderized services (registered services through hwservicemanager)";
-    mPassthroughRefTable.description =
-            "All interfaces that getService() has ever return as a passthrough interface;\n"
-            "PIDs / processes shown below might be inaccurate because the process\n"
-            "might have relinquished the interface or might have died.\n"
-            "The Server / Server CMD column can be ignored.\n"
-            "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n"
-            "the library and successfully fetched the passthrough implementation.";
-    mImplementationsTable.description =
-            "All available passthrough implementations (all -impl.so files)";
-    forEachTable([this] (const Table &table) {
-        if (!mNeat) {
-            mOut << table.description << std::endl;
-        }
-        mOut << std::left;
-        if (!mNeat) {
-            printLine("Interface", "Transport", "Arch", "Thread Use", "Server",
-                      "Server CMD", "PTR", "Clients", "Clients CMD");
-        }
+void ListCommand::dumpTable(const NullableOStream<std::ostream>& out) const {
+    if (mNeat) {
+        MergedTable({&mServicesTable, &mPassthroughRefTable, &mImplementationsTable})
+            .createTextTable().dump(out.buf());
+        return;
+    }
 
-        for (const auto &entry : table) {
-            printLine(entry.interfaceName,
-                    entry.transport,
-                    getArchString(entry.arch),
-                    entry.getThreadUsage(),
-                    entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid),
-                    entry.serverCmdline,
-                    entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress),
-                    join(entry.clientPids, " "),
-                    join(entry.clientCmdlines, ";"));
+    forEachTable([this, &out](const Table &table) {
 
-            // We're only interested in dumping debug info for already
-            // instantiated services. There's little value in dumping the
-            // debug info for a service we create on the fly, so we only operate
-            // on the "mServicesTable".
-            if (mEmitDebugInfo && &table == &mServicesTable) {
-                auto pair = splitFirst(entry.interfaceName, '/');
-                mLshal.emitDebugInfo(pair.first, pair.second, {}, mOut.buf(),
-                        NullableOStream<std::ostream>(nullptr));
-            }
+        // We're only interested in dumping debug info for already
+        // instantiated services. There's little value in dumping the
+        // debug info for a service we create on the fly, so we only operate
+        // on the "mServicesTable".
+        std::function<std::string(const std::string&)> emitDebugInfo = nullptr;
+        if (mEmitDebugInfo && &table == &mServicesTable) {
+            emitDebugInfo = [this](const auto& iName) {
+                std::stringstream ss;
+                auto pair = splitFirst(iName, '/');
+                mLshal.emitDebugInfo(pair.first, pair.second, {},
+                                     false /* excludesParentInstances */, ss,
+                                     NullableOStream<std::ostream>(nullptr));
+                return ss.str();
+            };
         }
-        if (!mNeat) {
-            mOut << std::endl;
-        }
+        table.createTextTable(mNeat, emitDebugInfo).dump(out.buf());
+        out << std::endl;
     });
-
 }
 
-void ListCommand::dump() {
-    if (mVintf) {
-        dumpVintf();
-        if (!!mFileOutput) {
-            mFileOutput.buf().close();
-            delete &mFileOutput.buf();
-            mFileOutput = nullptr;
-        }
-        mOut = std::cout;
-    } else {
-        dumpTable();
+Status ListCommand::dump() {
+    auto dump = mVintf ? &ListCommand::dumpVintf : &ListCommand::dumpTable;
+
+    if (mFileOutputPath.empty()) {
+        (*this.*dump)(out());
+        return OK;
     }
+
+    std::ofstream fileOutput(mFileOutputPath);
+    if (!fileOutput.is_open()) {
+        err() << "Could not open file '" << mFileOutputPath << "'." << std::endl;
+        return IO_ERROR;
+    }
+    chown(mFileOutputPath.c_str(), AID_SHELL, AID_SHELL);
+
+    (*this.*dump)(NullableOStream<std::ostream>(fileOutput));
+
+    fileOutput.flush();
+    fileOutput.close();
+    return OK;
 }
 
 void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) {
@@ -471,10 +491,10 @@
         case LIST_DLLIB :
             table = &mImplementationsTable; break;
         default:
-            mErr << "Error: Unknown source of entry " << source << std::endl;
+            err() << "Error: Unknown source of entry " << source << std::endl;
     }
     if (table) {
-        table->entries.push_back(std::forward<TableEntry>(entry));
+        table->add(std::forward<TableEntry>(entry));
     }
 }
 
@@ -483,7 +503,7 @@
     using namespace ::android::hidl::manager::V1_0;
     using namespace ::android::hidl::base::V1_0;
     using std::literals::chrono_literals::operator""s;
-    auto ret = timeoutIPC(2s, manager, &IServiceManager::debugDump, [&] (const auto &infos) {
+    auto ret = timeoutIPC(10s, manager, &IServiceManager::debugDump, [&] (const auto &infos) {
         std::map<std::string, TableEntry> entries;
         for (const auto &info : infos) {
             std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" +
@@ -491,10 +511,7 @@
             entries.emplace(interfaceName, TableEntry{
                 .interfaceName = interfaceName,
                 .transport = "passthrough",
-                .serverPid = NO_PID,
-                .serverObjectAddress = NO_PTR,
                 .clientPids = info.clientPids,
-                .arch = ARCH_UNKNOWN
             }).first->second.arch |= fromBaseArchitecture(info.arch);
         }
         for (auto &&pair : entries) {
@@ -502,7 +519,7 @@
         }
     });
     if (!ret.isOk()) {
-        mErr << "Error: Failed to call list on getPassthroughServiceManager(): "
+        err() << "Error: Failed to call list on getPassthroughServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_ALL_LIBS_ERROR;
     }
@@ -525,14 +542,13 @@
                         std::string{info.instanceName.c_str()},
                 .transport = "passthrough",
                 .serverPid = info.clientPids.size() == 1 ? info.clientPids[0] : NO_PID,
-                .serverObjectAddress = NO_PTR,
                 .clientPids = info.clientPids,
                 .arch = fromBaseArchitecture(info.arch)
             });
         }
     });
     if (!ret.isOk()) {
-        mErr << "Error: Failed to call debugDump on defaultServiceManager(): "
+        err() << "Error: Failed to call debugDump on defaultServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_PASSTHROUGH_ERROR;
     }
@@ -540,10 +556,6 @@
 }
 
 Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
-    using namespace ::std;
-    using namespace ::android::hardware;
-    using namespace ::android::hidl::manager::V1_0;
-    using namespace ::android::hidl::base::V1_0;
     const std::string mode = "hwbinder";
 
     hidl_vec<hidl_string> fqInstanceNames;
@@ -552,86 +564,123 @@
         fqInstanceNames = names;
     });
     if (!listRet.isOk()) {
-        mErr << "Error: Failed to list services for " << mode << ": "
+        err() << "Error: Failed to list services for " << mode << ": "
              << listRet.description() << std::endl;
         return DUMP_BINDERIZED_ERROR;
     }
 
     Status status = OK;
-    // server pid, .ptr value of binder object, child pids
-    std::map<std::string, DebugInfo> allDebugInfos;
-    std::map<pid_t, PidInfo> allPids;
+    std::map<std::string, TableEntry> allTableEntries;
     for (const auto &fqInstanceName : fqInstanceNames) {
-        const auto pair = splitFirst(fqInstanceName, '/');
-        const auto &serviceName = pair.first;
-        const auto &instanceName = pair.second;
-        auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
-        if (!getRet.isOk()) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
-                 << "cannot be fetched from service manager:"
-                 << getRet.description() << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
-            continue;
-        }
-        sp<IBase> service = getRet;
-        if (service == nullptr) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
-                 << "cannot be fetched from service manager (null)"
-                 << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
-            continue;
-        }
-        auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &debugInfo) {
-            allDebugInfos[fqInstanceName] = debugInfo;
-            if (debugInfo.pid >= 0) {
-                allPids[static_cast<pid_t>(debugInfo.pid)] = PidInfo();
-            }
+        // create entry and default assign all fields.
+        TableEntry& entry = allTableEntries[fqInstanceName];
+        entry.interfaceName = fqInstanceName;
+        entry.transport = mode;
+
+        status |= fetchBinderizedEntry(manager, &entry);
+    }
+
+    for (auto& pair : allTableEntries) {
+        putEntry(HWSERVICEMANAGER_LIST, std::move(pair.second));
+    }
+    return status;
+}
+
+Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
+                                         TableEntry *entry) {
+    Status status = OK;
+    const auto handleError = [&](Status additionalError, const std::string& msg) {
+        err() << "Warning: Skipping \"" << entry->interfaceName << "\": " << msg << std::endl;
+        status |= DUMP_BINDERIZED_ERROR | additionalError;
+    };
+
+    const auto pair = splitFirst(entry->interfaceName, '/');
+    const auto &serviceName = pair.first;
+    const auto &instanceName = pair.second;
+    auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
+    if (!getRet.isOk()) {
+        handleError(TRANSACTION_ERROR,
+                    "cannot be fetched from service manager:" + getRet.description());
+        return status;
+    }
+    sp<IBase> service = getRet;
+    if (service == nullptr) {
+        handleError(NO_INTERFACE, "cannot be fetched from service manager (null)");
+        return status;
+    }
+
+    // getDebugInfo
+    do {
+        DebugInfo debugInfo;
+        auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &received) {
+            debugInfo = received;
         });
         if (!debugRet.isOk()) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
-                 << "debugging information cannot be retrieved:"
-                 << debugRet.description() << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
+            handleError(TRANSACTION_ERROR,
+                        "debugging information cannot be retrieved: " + debugRet.description());
+            break; // skip getPidInfo
         }
-    }
 
-    for (auto &pair : allPids) {
-        pid_t serverPid = pair.first;
-        if (!getPidInfo(serverPid, &allPids[serverPid])) {
-            mErr << "Warning: no information for PID " << serverPid
-                      << ", are you root?" << std::endl;
-            status |= DUMP_BINDERIZED_ERROR;
-        }
-    }
-    for (const auto &fqInstanceName : fqInstanceNames) {
-        auto it = allDebugInfos.find(fqInstanceName);
-        if (it == allDebugInfos.end()) {
-            putEntry(HWSERVICEMANAGER_LIST, {
-                .interfaceName = fqInstanceName,
-                .transport = mode,
-                .serverPid = NO_PID,
-                .serverObjectAddress = NO_PTR,
-                .clientPids = {},
-                .threadUsage = 0,
-                .threadCount = 0,
-                .arch = ARCH_UNKNOWN
-            });
-            continue;
-        }
-        const DebugInfo &info = it->second;
-        bool writePidInfo = info.pid != NO_PID && info.ptr != NO_PTR;
+        entry->serverPid = debugInfo.pid;
+        entry->serverObjectAddress = debugInfo.ptr;
+        entry->arch = fromBaseArchitecture(debugInfo.arch);
 
-        putEntry(HWSERVICEMANAGER_LIST, {
-            .interfaceName = fqInstanceName,
-            .transport = mode,
-            .serverPid = info.pid,
-            .serverObjectAddress = info.ptr,
-            .clientPids = writePidInfo ? allPids[info.pid].refPids[info.ptr] : Pids{},
-            .threadUsage = writePidInfo ? allPids[info.pid].threadUsage : 0,
-            .threadCount = writePidInfo ? allPids[info.pid].threadCount : 0,
-            .arch = fromBaseArchitecture(info.arch),
+        if (debugInfo.pid != NO_PID) {
+            const PidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+            if (pidInfo == nullptr) {
+                handleError(IO_ERROR,
+                            "no information for PID " + std::to_string(debugInfo.pid) +
+                            ", are you root?");
+                break;
+            }
+            if (debugInfo.ptr != NO_PTR) {
+                auto it = pidInfo->refPids.find(debugInfo.ptr);
+                if (it != pidInfo->refPids.end()) {
+                    entry->clientPids = it->second;
+                }
+            }
+            entry->threadUsage = pidInfo->threadUsage;
+            entry->threadCount = pidInfo->threadCount;
+        }
+    } while (0);
+
+    // hash
+    do {
+        ssize_t hashIndex = -1;
+        auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
+            for (size_t i = 0; i < c.size(); ++i) {
+                if (serviceName == c[i]) {
+                    hashIndex = static_cast<ssize_t>(i);
+                    break;
+                }
+            }
         });
-    }
+        if (!ifaceChainRet.isOk()) {
+            handleError(TRANSACTION_ERROR,
+                        "interfaceChain fails: " + ifaceChainRet.description());
+            break; // skip getHashChain
+        }
+        if (hashIndex < 0) {
+            handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
+            break; // skip getHashChain
+        }
+        auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
+            if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
+                handleError(BAD_IMPL,
+                            "interfaceChain indicates position " + std::to_string(hashIndex) +
+                            " but getHashChain returns " + std::to_string(hashChain.size()) +
+                            " hashes");
+                return;
+            }
+
+            auto&& hashArray = hashChain[hashIndex];
+            std::vector<uint8_t> hashVec{hashArray.data(), hashArray.data() + hashArray.size()};
+            entry->hash = Hash::hexString(hashVec);
+        });
+        if (!hashRet.isOk()) {
+            handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+        }
+    } while (0);
     return status;
 }
 
@@ -639,7 +688,7 @@
     Status status = OK;
     auto bManager = mLshal.serviceManager();
     if (bManager == nullptr) {
-        mErr << "Failed to get defaultServiceManager()!" << std::endl;
+        err() << "Failed to get defaultServiceManager()!" << std::endl;
         status |= NO_BINDERIZED_MANAGER;
     } else {
         status |= fetchBinderized(bManager);
@@ -649,7 +698,7 @@
 
     auto pManager = mLshal.passthroughManager();
     if (pManager == nullptr) {
-        mErr << "Failed to get getPassthroughServiceManager()!" << std::endl;
+        err() << "Failed to get getPassthroughServiceManager()!" << std::endl;
         status |= NO_PASSTHROUGH_MANAGER;
     } else {
         status |= fetchAllLibraries(pManager);
@@ -657,139 +706,279 @@
     return status;
 }
 
-Status ListCommand::parseArgs(const std::string &command, const Arg &arg) {
-    static struct option longOptions[] = {
-        // long options with short alternatives
-        {"help",      no_argument,       0, 'h' },
-        {"interface", no_argument,       0, 'i' },
-        {"transport", no_argument,       0, 't' },
-        {"arch",      no_argument,       0, 'r' },
-        {"pid",       no_argument,       0, 'p' },
-        {"address",   no_argument,       0, 'a' },
-        {"clients",   no_argument,       0, 'c' },
-        {"threads",   no_argument,       0, 'e' },
-        {"cmdline",   no_argument,       0, 'm' },
-        {"debug",     optional_argument, 0, 'd' },
+void ListCommand::registerAllOptions() {
+    int v = mOptions.size();
+    // A list of acceptable command line options
+    // key: value returned by getopt_long
+    // long options with short alternatives
+    mOptions.push_back({'h', "help", no_argument, v++, [](ListCommand*, const char*) {
+        return USAGE;
+    }, ""});
+    mOptions.push_back({'i', "interface", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME);
+        return OK;
+    }, "print the instance name column"});
+    mOptions.push_back({'l', "released", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::RELEASED);
+        return OK;
+    }, "print the 'is released?' column\n(Y=released, empty=unreleased or unknown)"});
+    mOptions.push_back({'t', "transport", no_argument, v++, [](ListCommand* thiz, const char*) {
+        thiz->mSelectedColumns.push_back(TableColumnType::TRANSPORT);
+        return OK;
+    }, "print the transport mode column"});
+    mOptions.push_back({'r', "arch", no_arg