Merge "apexd: Create top-level /data/apex folders at boot"
diff --git a/adb/fdevent.cpp b/adb/fdevent.cpp
index fa3738d..32f9086 100644
--- a/adb/fdevent.cpp
+++ b/adb/fdevent.cpp
@@ -32,6 +32,7 @@
 #include <functional>
 #include <list>
 #include <mutex>
+#include <optional>
 #include <unordered_map>
 #include <utility>
 #include <variant>
@@ -225,14 +226,22 @@
 
 void fdevent_add(fdevent* fde, unsigned events) {
     check_main_thread();
+    CHECK(!(events & FDE_TIMEOUT));
     fdevent_set(fde, (fde->state & FDE_EVENTMASK) | events);
 }
 
 void fdevent_del(fdevent* fde, unsigned events) {
     check_main_thread();
+    CHECK(!(events & FDE_TIMEOUT));
     fdevent_set(fde, (fde->state & FDE_EVENTMASK) & ~events);
 }
 
+void fdevent_set_timeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout) {
+    check_main_thread();
+    fde->timeout = timeout;
+    fde->last_active = std::chrono::steady_clock::now();
+}
+
 static std::string dump_pollfds(const std::vector<adb_pollfd>& pollfds) {
     std::string result;
     for (const auto& pollfd : pollfds) {
@@ -248,6 +257,32 @@
     return result;
 }
 
+static std::optional<std::chrono::milliseconds> calculate_timeout() {
+    std::optional<std::chrono::milliseconds> result = std::nullopt;
+    auto now = std::chrono::steady_clock::now();
+    check_main_thread();
+
+    for (const auto& [fd, pollnode] : g_poll_node_map) {
+        UNUSED(fd);
+        auto timeout_opt = pollnode.fde->timeout;
+        if (timeout_opt) {
+            auto deadline = pollnode.fde->last_active + *timeout_opt;
+            auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now);
+            if (time_left < std::chrono::milliseconds::zero()) {
+                time_left = std::chrono::milliseconds::zero();
+            }
+
+            if (!result) {
+                result = time_left;
+            } else {
+                result = std::min(*result, time_left);
+            }
+        }
+    }
+
+    return result;
+}
+
 static void fdevent_process() {
     std::vector<adb_pollfd> pollfds;
     for (const auto& pair : g_poll_node_map) {
@@ -256,11 +291,22 @@
     CHECK_GT(pollfds.size(), 0u);
     D("poll(), pollfds = %s", dump_pollfds(pollfds).c_str());
 
-    int ret = adb_poll(&pollfds[0], pollfds.size(), -1);
+    auto timeout = calculate_timeout();
+    int timeout_ms;
+    if (!timeout) {
+        timeout_ms = -1;
+    } else {
+        timeout_ms = timeout->count();
+    }
+
+    int ret = adb_poll(&pollfds[0], pollfds.size(), timeout_ms);
     if (ret == -1) {
         PLOG(ERROR) << "poll(), ret = " << ret;
         return;
     }
+
+    auto post_poll = std::chrono::steady_clock::now();
+
     for (const auto& pollfd : pollfds) {
         if (pollfd.revents != 0) {
             D("for fd %d, revents = %x", pollfd.fd, pollfd.revents);
@@ -282,12 +328,24 @@
             events |= FDE_READ | FDE_ERROR;
         }
 #endif
+        auto it = g_poll_node_map.find(pollfd.fd);
+        CHECK(it != g_poll_node_map.end());
+        fdevent* fde = it->second.fde;
+
+        if (events == 0) {
+            // Check for timeout.
+            if (fde->timeout) {
+                auto deadline = fde->last_active + *fde->timeout;
+                if (deadline < post_poll) {
+                    events |= FDE_TIMEOUT;
+                }
+            }
+        }
+
         if (events != 0) {
-            auto it = g_poll_node_map.find(pollfd.fd);
-            CHECK(it != g_poll_node_map.end());
-            fdevent* fde = it->second.fde;
             CHECK_EQ(fde->fd.get(), pollfd.fd);
             fde->events |= events;
+            fde->last_active = post_poll;
             D("%s got events %x", dump_fde(fde).c_str(), events);
             fde->state |= FDE_PENDING;
             g_pending_list.push_back(fde);
diff --git a/adb/fdevent.h b/adb/fdevent.h
index 70e0a96..42dbb9e 100644
--- a/adb/fdevent.h
+++ b/adb/fdevent.h
@@ -18,17 +18,20 @@
 #define __FDEVENT_H
 
 #include <stddef.h>
-#include <stdint.h>  /* for int64_t */
+#include <stdint.h>
 
+#include <chrono>
 #include <functional>
+#include <optional>
 #include <variant>
 
 #include "adb_unique_fd.h"
 
-/* events that may be observed */
-#define FDE_READ              0x0001
-#define FDE_WRITE             0x0002
-#define FDE_ERROR             0x0004
+// Events that may be observed
+#define FDE_READ 0x0001
+#define FDE_WRITE 0x0002
+#define FDE_ERROR 0x0004
+#define FDE_TIMEOUT 0x0008
 
 typedef void (*fd_func)(int fd, unsigned events, void *userdata);
 typedef void (*fd_func2)(struct fdevent* fde, unsigned events, void* userdata);
@@ -41,6 +44,8 @@
 
     uint16_t state = 0;
     uint16_t events = 0;
+    std::optional<std::chrono::milliseconds> timeout;
+    std::chrono::steady_clock::time_point last_active;
 
     std::variant<fd_func, fd_func2> func;
     void* arg = nullptr;
@@ -62,7 +67,11 @@
 void fdevent_add(fdevent *fde, unsigned events);
 void fdevent_del(fdevent *fde, unsigned events);
 
-void fdevent_set_timeout(fdevent *fde, int64_t  timeout_ms);
+// Set a timeout on an fdevent.
+// If no events are triggered by the timeout, an FDE_TIMEOUT will be generated.
+// Note timeouts are not defused automatically; if a timeout is set on an fdevent, it will
+// trigger repeatedly every |timeout| ms.
+void fdevent_set_timeout(fdevent* fde, std::optional<std::chrono::milliseconds> timeout);
 
 // Loop forever, handling events.
 void fdevent_loop();
diff --git a/adb/fdevent_test.cpp b/adb/fdevent_test.cpp
index a9746bb..682f061 100644
--- a/adb/fdevent_test.cpp
+++ b/adb/fdevent_test.cpp
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest.h>
 
+#include <chrono>
 #include <limits>
 #include <memory>
 #include <queue>
@@ -28,6 +29,8 @@
 #include "adb_io.h"
 #include "fdevent_test.h"
 
+using namespace std::chrono_literals;
+
 class FdHandler {
   public:
     FdHandler(int read_fd, int write_fd, bool use_new_callback)
@@ -257,3 +260,100 @@
         ASSERT_EQ(i, vec[i]);
     }
 }
+
+TEST_F(FdeventTest, timeout) {
+    fdevent_reset();
+    PrepareThread();
+
+    enum class TimeoutEvent {
+        read,
+        timeout,
+        done,
+    };
+
+    struct TimeoutTest {
+        std::vector<std::pair<TimeoutEvent, std::chrono::steady_clock::time_point>> events;
+        fdevent* fde;
+    };
+    TimeoutTest test;
+
+    int fds[2];
+    ASSERT_EQ(0, adb_socketpair(fds));
+    static constexpr auto delta = 100ms;
+    fdevent_run_on_main_thread([&]() {
+        test.fde = fdevent_create(fds[0], [](fdevent* fde, unsigned events, void* arg) {
+            auto test = static_cast<TimeoutTest*>(arg);
+            auto now = std::chrono::steady_clock::now();
+            CHECK((events & FDE_READ) ^ (events & FDE_TIMEOUT));
+            TimeoutEvent event;
+            if ((events & FDE_READ)) {
+                char buf[2];
+                ssize_t rc = adb_read(fde->fd.get(), buf, sizeof(buf));
+                if (rc == 0) {
+                    event = TimeoutEvent::done;
+                } else if (rc == 1) {
+                    event = TimeoutEvent::read;
+                } else {
+                    abort();
+                }
+            } else if ((events & FDE_TIMEOUT)) {
+                event = TimeoutEvent::timeout;
+            } else {
+                abort();
+            }
+
+            CHECK_EQ(fde, test->fde);
+            test->events.emplace_back(event, now);
+
+            if (event == TimeoutEvent::done) {
+                fdevent_destroy(fde);
+            }
+        }, &test);
+        fdevent_add(test.fde, FDE_READ);
+        fdevent_set_timeout(test.fde, delta);
+    });
+
+    ASSERT_EQ(1, adb_write(fds[1], "", 1));
+
+    // Timeout should happen here
+    std::this_thread::sleep_for(delta);
+
+    // and another.
+    std::this_thread::sleep_for(delta);
+
+    // No timeout should happen here.
+    std::this_thread::sleep_for(delta / 2);
+    adb_close(fds[1]);
+
+    TerminateThread();
+
+    ASSERT_EQ(4ULL, test.events.size());
+    ASSERT_EQ(TimeoutEvent::read, test.events[0].first);
+    ASSERT_EQ(TimeoutEvent::timeout, test.events[1].first);
+    ASSERT_EQ(TimeoutEvent::timeout, test.events[2].first);
+    ASSERT_EQ(TimeoutEvent::done, test.events[3].first);
+
+    std::vector<int> time_deltas;
+    for (size_t i = 0; i < test.events.size() - 1; ++i) {
+        auto before = test.events[i].second;
+        auto after = test.events[i + 1].second;
+        auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
+        time_deltas.push_back(diff.count());
+    }
+
+    std::vector<int> expected = {
+        delta.count(),
+        delta.count(),
+        delta.count() / 2,
+    };
+
+    std::vector<int> diff;
+    ASSERT_EQ(time_deltas.size(), expected.size());
+    for (size_t i = 0; i < time_deltas.size(); ++i) {
+        diff.push_back(std::abs(time_deltas[i] - expected[i]));
+    }
+
+    ASSERT_LT(diff[0], delta.count() * 0.5);
+    ASSERT_LT(diff[1], delta.count() * 0.5);
+    ASSERT_LT(diff[2], delta.count() * 0.5);
+}
diff --git a/adb/socket_test.cpp b/adb/socket_test.cpp
index 7908f82..5e28f76 100644
--- a/adb/socket_test.cpp
+++ b/adb/socket_test.cpp
@@ -221,6 +221,8 @@
     EXPECT_EQ(2u + GetAdditionalLocalSocketCount(), fdevent_installed_count());
     ASSERT_EQ(0, adb_close(socket_fd[0]));
 
+    std::this_thread::sleep_for(2s);
+
     WaitForFdeventLoop();
     ASSERT_EQ(GetAdditionalLocalSocketCount(), fdevent_installed_count());
     TerminateThread();
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index f7c39f0..420a6d5 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -26,6 +26,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <chrono>
 #include <mutex>
 #include <string>
 #include <vector>
@@ -41,6 +42,8 @@
 #include "transport.h"
 #include "types.h"
 
+using namespace std::chrono_literals;
+
 static std::recursive_mutex& local_socket_list_lock = *new std::recursive_mutex();
 static unsigned local_socket_next_id = 1;
 
@@ -238,16 +241,64 @@
     fdevent_add(s->fde, FDE_READ);
 }
 
+struct ClosingSocket {
+    std::chrono::steady_clock::time_point begin;
+};
+
+// The standard (RFC 1122 - 4.2.2.13) says that if we call close on a
+// socket while we have pending data, a TCP RST should be sent to the
+// other end to notify it that we didn't read all of its data. However,
+// this can result in data that we've successfully written out to be dropped
+// on the other end. To avoid this, instead of immediately closing a
+// socket, call shutdown on it instead, and then read from the file
+// descriptor until we hit EOF or an error before closing.
+static void deferred_close(unique_fd fd) {
+    // Shutdown the socket in the outgoing direction only, so that
+    // we don't have the same problem on the opposite end.
+    adb_shutdown(fd.get(), SHUT_WR);
+    auto callback = [](fdevent* fde, unsigned event, void* arg) {
+        auto socket_info = static_cast<ClosingSocket*>(arg);
+        if (event & FDE_READ) {
+            ssize_t rc;
+            char buf[BUFSIZ];
+            while ((rc = adb_read(fde->fd.get(), buf, sizeof(buf))) > 0) {
+                continue;
+            }
+
+            if (rc == -1 && errno == EAGAIN) {
+                // There's potentially more data to read.
+                auto duration = std::chrono::steady_clock::now() - socket_info->begin;
+                if (duration > 1s) {
+                    LOG(WARNING) << "timeout expired while flushing socket, closing";
+                } else {
+                    return;
+                }
+            }
+        } else if (event & FDE_TIMEOUT) {
+            LOG(WARNING) << "timeout expired while flushing socket, closing";
+        }
+
+        // Either there was an error, we hit the end of the socket, or our timeout expired.
+        fdevent_destroy(fde);
+        delete socket_info;
+    };
+
+    ClosingSocket* socket_info = new ClosingSocket{
+            .begin = std::chrono::steady_clock::now(),
+    };
+
+    fdevent* fde = fdevent_create(fd.release(), callback, socket_info);
+    fdevent_add(fde, FDE_READ);
+    fdevent_set_timeout(fde, 1s);
+}
+
 // be sure to hold the socket list lock when calling this
 static void local_socket_destroy(asocket* s) {
     int exit_on_close = s->exit_on_close;
 
     D("LS(%d): destroying fde.fd=%d", s->id, s->fd);
 
-    /* IMPORTANT: the remove closes the fd
-    ** that belongs to this socket
-    */
-    fdevent_destroy(s->fde);
+    deferred_close(fdevent_release(s->fde));
 
     remove_socket(s);
     delete s;
diff --git a/adb/sysdeps/chrono.h b/adb/sysdeps/chrono.h
index c73a638..5c5af7c 100644
--- a/adb/sysdeps/chrono.h
+++ b/adb/sysdeps/chrono.h
@@ -18,29 +18,4 @@
 
 #include <chrono>
 
-#if defined(_WIN32)
-// We don't have C++14 on Windows yet.
-// Reimplement std::chrono_literals ourselves until we do.
-
-// Silence the following warning (which gets promoted to an error):
-// error: literal operator suffixes not preceded by ‘_’ are reserved for future standardization
-#pragma GCC system_header
-
-constexpr std::chrono::seconds operator"" s(unsigned long long s) {
-    return std::chrono::seconds(s);
-}
-
-constexpr std::chrono::duration<long double> operator"" s(long double s) {
-    return std::chrono::duration<long double>(s);
-}
-
-constexpr std::chrono::milliseconds operator"" ms(unsigned long long ms) {
-    return std::chrono::milliseconds(ms);
-}
-
-constexpr std::chrono::duration<long double, std::milli> operator"" ms(long double ms) {
-    return std::chrono::duration<long double, std::milli>(ms);
-}
-#else
 using namespace std::chrono_literals;
-#endif
diff --git a/adb/test_device.py b/adb/test_device.py
index 34f8fd9..f95a5b3 100755
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -35,6 +35,8 @@
 import time
 import unittest
 
+from datetime import datetime
+
 import adb
 
 def requires_root(func):
@@ -1335,6 +1337,63 @@
             self.device.forward_remove("tcp:{}".format(local_port))
 
 
+class SocketTest(DeviceTest):
+    def test_socket_flush(self):
+        """Test that we handle socket closure properly.
+
+        If we're done writing to a socket, closing before the other end has
+        closed will send a TCP_RST if we have incoming data queued up, which
+        may result in data that we've written being discarded.
+
+        Bug: http://b/74616284
+        """
+        s = socket.create_connection(("localhost", 5037))
+
+        def adb_length_prefixed(string):
+            encoded = string.encode("utf8")
+            result = b"%04x%s" % (len(encoded), encoded)
+            return result
+
+        if "ANDROID_SERIAL" in os.environ:
+            transport_string = "host:transport:" + os.environ["ANDROID_SERIAL"]
+        else:
+            transport_string = "host:transport-any"
+
+        s.sendall(adb_length_prefixed(transport_string))
+        response = s.recv(4)
+        self.assertEquals(b"OKAY", response)
+
+        shell_string = "shell:sleep 0.5; dd if=/dev/zero bs=1m count=1 status=none; echo foo"
+        s.sendall(adb_length_prefixed(shell_string))
+
+        response = s.recv(4)
+        self.assertEquals(b"OKAY", response)
+
+        # Spawn a thread that dumps garbage into the socket until failure.
+        def spam():
+            buf = b"\0" * 16384
+            try:
+                while True:
+                    s.sendall(buf)
+            except Exception as ex:
+                print(ex)
+
+        thread = threading.Thread(target=spam)
+        thread.start()
+
+        time.sleep(1)
+
+        received = b""
+        while True:
+            read = s.recv(512)
+            if len(read) == 0:
+                break
+            received += read
+
+        self.assertEquals(1024 * 1024 + len("foo\n"), len(received))
+        thread.join()
+
+
 if sys.platform == "win32":
     # From https://stackoverflow.com/a/38749458
     import os
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index e5caf3a..e684293 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -378,7 +378,7 @@
 // Set the number of reserved filesystem blocks if needed.
 static void tune_reserved_size(const std::string& blk_device, const FstabEntry& entry,
                                const struct ext4_super_block* sb, int* fs_stat) {
-    if (!entry.fs_mgr_flags.reserved_size) {
+    if (entry.reserved_size != 0) {
         return;
     }
 
@@ -551,7 +551,7 @@
     }
 
     if (is_extfs(entry.fs_type) &&
-        (entry.fs_mgr_flags.reserved_size || entry.fs_mgr_flags.file_encryption ||
+        (entry.reserved_size != 0 || entry.fs_mgr_flags.file_encryption ||
          entry.fs_mgr_flags.fs_verity)) {
         struct ext4_super_block sb;
 
@@ -806,7 +806,7 @@
 }
 
 static bool should_use_metadata_encryption(const FstabEntry& entry) {
-    return entry.fs_mgr_flags.key_directory &&
+    return !entry.key_dir.empty() &&
            (entry.fs_mgr_flags.file_encryption || entry.fs_mgr_flags.force_fde_or_fbe);
 }
 
@@ -1374,18 +1374,6 @@
     return FS_MGR_DOMNT_FAILED;
 }
 
-int fs_mgr_do_mount(fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point) {
-    auto new_fstab = LegacyFstabToFstab(fstab);
-    return fs_mgr_do_mount_helper(&new_fstab, n_name, n_blk_device, tmp_mount_point, -1);
-}
-
-int fs_mgr_do_mount(fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point,
-                    bool needs_checkpoint) {
-    auto new_fstab = LegacyFstabToFstab(fstab);
-    return fs_mgr_do_mount_helper(&new_fstab, n_name, n_blk_device, tmp_mount_point,
-                                  needs_checkpoint);
-}
-
 int fs_mgr_do_mount(Fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point) {
     return fs_mgr_do_mount_helper(fstab, n_name, n_blk_device, tmp_mount_point, -1);
 }
@@ -1444,7 +1432,7 @@
     }
 
     // Prepare target path
-    unique_fd target_fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0664)));
+    unique_fd target_fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600)));
     if (target_fd.get() == -1) {
         PERROR << "Cannot open target path: " << loop;
         return false;
@@ -1556,48 +1544,6 @@
     return ret;
 }
 
-struct fstab_rec const* fs_mgr_get_crypt_entry(fstab const* fstab) {
-    int i;
-
-    if (!fstab) {
-        return NULL;
-    }
-
-    /* Look for the encryptable partition to find the data */
-    for (i = 0; i < fstab->num_entries; i++) {
-        /* Don't deal with vold managed enryptable partitions here */
-        if (!(fstab->recs[i].fs_mgr_flags & MF_VOLDMANAGED) &&
-            (fstab->recs[i].fs_mgr_flags &
-             (MF_CRYPT | MF_FORCECRYPT | MF_FORCEFDEORFBE | MF_FILEENCRYPTION))) {
-            return &fstab->recs[i];
-        }
-    }
-    return NULL;
-}
-
-/*
- * key_loc must be at least PROPERTY_VALUE_MAX bytes long
- *
- * real_blk_device must be at least PROPERTY_VALUE_MAX bytes long
- */
-void fs_mgr_get_crypt_info(fstab* fstab, char* key_loc, char* real_blk_device, size_t size) {
-    struct fstab_rec const* rec = fs_mgr_get_crypt_entry(fstab);
-    if (key_loc) {
-        if (rec) {
-            strlcpy(key_loc, rec->key_loc, size);
-        } else {
-            *key_loc = '\0';
-        }
-    }
-    if (real_blk_device) {
-        if (rec) {
-            strlcpy(real_blk_device, rec->blk_device, size);
-        } else {
-            *real_blk_device = '\0';
-        }
-    }
-}
-
 bool fs_mgr_load_verity_state(int* mode) {
     /* return the default mode, unless any of the verified partitions are in
      * logging mode, in which case return that */
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index ee88e0d..9a8ad56 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -175,7 +175,6 @@
 }
 
 void ParseFsMgrFlags(const std::string& flags, FstabEntry* entry) {
-    entry->fs_mgr_flags.val = 0U;
     for (const auto& flag : Split(flags, ",")) {
         std::string arg;
         if (auto equal_sign = flag.find('='); equal_sign != std::string::npos) {
@@ -239,18 +238,14 @@
             }
         } else if (StartsWith(flag, "length=")) {
             // The length flag is followed by an = and the size of the partition.
-            entry->fs_mgr_flags.length = true;
             if (!ParseInt(arg, &entry->length)) {
                 LWARNING << "Warning: length= flag malformed: " << arg;
             }
         } else if (StartsWith(flag, "swapprio=")) {
-            entry->fs_mgr_flags.swap_prio = true;
             if (!ParseInt(arg, &entry->swap_prio)) {
                 LWARNING << "Warning: length= flag malformed: " << arg;
             }
         } else if (StartsWith(flag, "zramsize=")) {
-            entry->fs_mgr_flags.zram_size = true;
-
             if (!arg.empty() && arg.back() == '%') {
                 arg.pop_back();
                 int val;
@@ -282,13 +277,11 @@
             entry->file_contents_mode = "aes-256-xts";
             entry->file_names_mode = "aes-256-cts";
         } else if (StartsWith(flag, "max_comp_streams=")) {
-            entry->fs_mgr_flags.max_comp_streams = true;
             if (!ParseInt(arg, &entry->max_comp_streams)) {
                 LWARNING << "Warning: max_comp_streams= flag malformed: " << arg;
             }
         } else if (StartsWith(flag, "reservedsize=")) {
             // The reserved flag is followed by an = and the reserved size of the partition.
-            entry->fs_mgr_flags.reserved_size = true;
             uint64_t size;
             if (!ParseByteCount(arg, &size)) {
                 LWARNING << "Warning: reservedsize= flag malformed: " << arg;
@@ -298,7 +291,6 @@
         } else if (StartsWith(flag, "eraseblk=")) {
             // The erase block size flag is followed by an = and the flash erase block size. Get it,
             // check that it is a power of 2 and at least 4096, and return it.
-            entry->fs_mgr_flags.erase_blk_size = true;
             off64_t val;
             if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) {
                 LWARNING << "Warning: eraseblk= flag malformed: " << arg;
@@ -308,7 +300,6 @@
         } else if (StartsWith(flag, "logicalblk=")) {
             // The logical block size flag is followed by an = and the flash logical block size. Get
             // it, check that it is a power of 2 and at least 4096, and return it.
-            entry->fs_mgr_flags.logical_blk_size = true;
             off64_t val;
             if (!ParseInt(arg, &val) || val < 4096 || (val & (val - 1)) != 0) {
                 LWARNING << "Warning: logicalblk= flag malformed: " << arg;
@@ -320,23 +311,18 @@
             entry->vbmeta_partition = arg;
         } else if (StartsWith(flag, "keydirectory=")) {
             // The metadata flag is followed by an = and the directory for the keys.
-            entry->fs_mgr_flags.key_directory = true;
             entry->key_dir = arg;
         } else if (StartsWith(flag, "sysfs_path=")) {
             // The path to trigger device gc by idle-maint of vold.
-            entry->fs_mgr_flags.sysfs = true;
             entry->sysfs_path = arg;
         } else if (StartsWith(flag, "zram_loopback_path=")) {
             // The path to use loopback for zram.
-            entry->fs_mgr_flags.zram_loopback_path = true;
             entry->zram_loopback_path = arg;
         } else if (StartsWith(flag, "zram_loopback_size=")) {
-            entry->fs_mgr_flags.zram_loopback_size = true;
             if (!ParseByteCount(arg, &entry->zram_loopback_size)) {
                 LWARNING << "Warning: zram_loopback_size= flag malformed: " << arg;
             }
         } else if (StartsWith(flag, "zram_backing_dev_path=")) {
-            entry->fs_mgr_flags.zram_backing_dev_path = true;
             entry->zram_backing_dev_path = arg;
         } else if (StartsWith(flag, "avb_key=")) {
             entry->avb_key = arg;
@@ -778,214 +764,3 @@
 
     return false;
 }
-
-// Everything from here down is deprecated and will be removed shortly.
-
-using android::fs_mgr::Fstab;
-using android::fs_mgr::FstabEntry;
-using android::fs_mgr::ReadDefaultFstab;
-using android::fs_mgr::ReadFstabFromFile;
-
-struct fstab* fs_mgr_read_fstab(const char* fstab_path) {
-    Fstab fstab;
-    if (!ReadFstabFromFile(fstab_path, &fstab)) {
-        return nullptr;
-    }
-
-    return FstabToLegacyFstab(fstab);
-}
-
-struct fstab* fs_mgr_read_fstab_default() {
-    Fstab fstab;
-    if (!ReadDefaultFstab(&fstab)) {
-        return nullptr;
-    }
-
-    return FstabToLegacyFstab(fstab);
-}
-
-void fs_mgr_free_fstab(struct fstab *fstab)
-{
-    int i;
-
-    if (!fstab) {
-        return;
-    }
-
-    for (i = 0; i < fstab->num_entries; i++) {
-        /* Free the pointers return by strdup(3) */
-        free(fstab->recs[i].blk_device);
-        free(fstab->recs[i].logical_partition_name);
-        free(fstab->recs[i].mount_point);
-        free(fstab->recs[i].fs_type);
-        free(fstab->recs[i].fs_options);
-        free(fstab->recs[i].key_loc);
-        free(fstab->recs[i].key_dir);
-        free(fstab->recs[i].label);
-        free(fstab->recs[i].file_contents_mode);
-        free(fstab->recs[i].file_names_mode);
-        free(fstab->recs[i].sysfs_path);
-        free(fstab->recs[i].zram_loopback_path);
-        free(fstab->recs[i].zram_backing_dev_path);
-    }
-
-    /* Free the fstab_recs array created by calloc(3) */
-    free(fstab->recs);
-
-    /* Free fstab */
-    free(fstab);
-}
-
-struct fstab_rec* fs_mgr_get_entry_for_mount_point(struct fstab* fstab, const std::string& path) {
-    if (!fstab) {
-        return nullptr;
-    }
-    for (int i = 0; i < fstab->num_entries; i++) {
-        if (fstab->recs[i].mount_point && path == fstab->recs[i].mount_point) {
-            return &fstab->recs[i];
-        }
-    }
-    return nullptr;
-}
-
-FstabEntry FstabRecToFstabEntry(const fstab_rec* fstab_rec) {
-    FstabEntry entry;
-    entry.blk_device = fstab_rec->blk_device;
-    entry.logical_partition_name = fstab_rec->logical_partition_name;
-    entry.mount_point = fstab_rec->mount_point;
-    entry.fs_type = fstab_rec->fs_type;
-    entry.flags = fstab_rec->flags;
-    entry.fs_options = fstab_rec->fs_options;
-    entry.fs_mgr_flags.val = fstab_rec->fs_mgr_flags;
-    entry.key_loc = fstab_rec->key_loc;
-    entry.key_dir = fstab_rec->key_dir;
-    entry.verity_loc = fstab_rec->verity_loc;
-    entry.length = fstab_rec->length;
-    entry.label = fstab_rec->label;
-    entry.partnum = fstab_rec->partnum;
-    entry.swap_prio = fstab_rec->swap_prio;
-    entry.max_comp_streams = fstab_rec->max_comp_streams;
-    entry.zram_size = fstab_rec->zram_size;
-    entry.reserved_size = fstab_rec->reserved_size;
-    entry.file_contents_mode = fstab_rec->file_contents_mode;
-    entry.file_names_mode = fstab_rec->file_names_mode;
-    entry.erase_blk_size = fstab_rec->erase_blk_size;
-    entry.logical_blk_size = fstab_rec->logical_blk_size;
-    entry.sysfs_path = fstab_rec->sysfs_path;
-    entry.zram_loopback_path = fstab_rec->zram_loopback_path;
-    entry.zram_loopback_size = fstab_rec->zram_loopback_size;
-    entry.zram_backing_dev_path = fstab_rec->zram_backing_dev_path;
-
-    return entry;
-}
-
-Fstab LegacyFstabToFstab(const struct fstab* legacy_fstab) {
-    Fstab fstab;
-    for (int i = 0; i < legacy_fstab->num_entries; i++) {
-        fstab.emplace_back(FstabRecToFstabEntry(&legacy_fstab->recs[i]));
-    }
-
-    return fstab;
-}
-
-fstab* FstabToLegacyFstab(const Fstab& fstab) {
-    struct fstab* legacy_fstab = static_cast<struct fstab*>(calloc(1, sizeof(struct fstab)));
-    legacy_fstab->num_entries = fstab.size();
-    legacy_fstab->recs =
-            static_cast<fstab_rec*>(calloc(legacy_fstab->num_entries, sizeof(fstab_rec)));
-
-    for (int i = 0; i < legacy_fstab->num_entries; i++) {
-        legacy_fstab->recs[i].blk_device = strdup(fstab[i].blk_device.c_str());
-        legacy_fstab->recs[i].logical_partition_name =
-                strdup(fstab[i].logical_partition_name.c_str());
-        legacy_fstab->recs[i].mount_point = strdup(fstab[i].mount_point.c_str());
-        legacy_fstab->recs[i].fs_type = strdup(fstab[i].fs_type.c_str());
-        legacy_fstab->recs[i].flags = fstab[i].flags;
-        legacy_fstab->recs[i].fs_options = strdup(fstab[i].fs_options.c_str());
-        legacy_fstab->recs[i].fs_mgr_flags = fstab[i].fs_mgr_flags.val;
-        legacy_fstab->recs[i].key_loc = strdup(fstab[i].key_loc.c_str());
-        legacy_fstab->recs[i].key_dir = strdup(fstab[i].key_dir.c_str());
-        legacy_fstab->recs[i].verity_loc = strdup(fstab[i].verity_loc.c_str());
-        legacy_fstab->recs[i].length = fstab[i].length;
-        legacy_fstab->recs[i].label = strdup(fstab[i].label.c_str());
-        legacy_fstab->recs[i].partnum = fstab[i].partnum;
-        legacy_fstab->recs[i].swap_prio = fstab[i].swap_prio;
-        legacy_fstab->recs[i].max_comp_streams = fstab[i].max_comp_streams;
-        legacy_fstab->recs[i].zram_size = fstab[i].zram_size;
-        legacy_fstab->recs[i].reserved_size = fstab[i].reserved_size;
-        legacy_fstab->recs[i].file_contents_mode = strdup(fstab[i].file_contents_mode.c_str());
-        legacy_fstab->recs[i].file_names_mode = strdup(fstab[i].file_names_mode.c_str());
-        legacy_fstab->recs[i].erase_blk_size = fstab[i].erase_blk_size;
-        legacy_fstab->recs[i].logical_blk_size = fstab[i].logical_blk_size;
-        legacy_fstab->recs[i].sysfs_path = strdup(fstab[i].sysfs_path.c_str());
-        legacy_fstab->recs[i].zram_loopback_path = strdup(fstab[i].zram_loopback_path.c_str());
-        legacy_fstab->recs[i].zram_loopback_size = fstab[i].zram_loopback_size;
-        legacy_fstab->recs[i].zram_backing_dev_path = strdup(fstab[i].zram_backing_dev_path.c_str());
-    }
-    return legacy_fstab;
-}
-
-int fs_mgr_is_voldmanaged(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_VOLDMANAGED;
-}
-
-int fs_mgr_is_nonremovable(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_NONREMOVABLE;
-}
-
-int fs_mgr_is_verified(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_VERIFY;
-}
-
-int fs_mgr_is_encryptable(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & (MF_CRYPT | MF_FORCECRYPT | MF_FORCEFDEORFBE);
-}
-
-void fs_mgr_get_file_encryption_modes(const struct fstab_rec* fstab, const char** contents_mode_ret,
-                                      const char** filenames_mode_ret) {
-    *contents_mode_ret = fstab->file_contents_mode;
-    *filenames_mode_ret = fstab->file_names_mode;
-}
-
-int fs_mgr_is_convertible_to_fbe(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_FORCEFDEORFBE;
-}
-
-int fs_mgr_is_noemulatedsd(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_NOEMULATEDSD;
-}
-
-int fs_mgr_is_notrim(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & MF_NOTRIM;
-}
-
-int fs_mgr_is_quota(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & MF_QUOTA;
-}
-
-int fs_mgr_has_sysfs_path(const struct fstab_rec *fstab)
-{
-    return fstab->fs_mgr_flags & MF_SYSFS;
-}
-
-int fs_mgr_is_logical(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & MF_LOGICAL;
-}
-
-int fs_mgr_is_checkpoint(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & (MF_CHECKPOINT_FS | MF_CHECKPOINT_BLK);
-}
-
-int fs_mgr_is_checkpoint_fs(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & MF_CHECKPOINT_FS;
-}
-
-int fs_mgr_is_checkpoint_blk(const struct fstab_rec* fstab) {
-    return fstab->fs_mgr_flags & MF_CHECKPOINT_BLK;
-}
diff --git a/fs_mgr/fs_mgr_priv.h b/fs_mgr/fs_mgr_priv.h
index 83e5d7b..166c32b 100644
--- a/fs_mgr/fs_mgr_priv.h
+++ b/fs_mgr/fs_mgr_priv.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef __CORE_FS_MGR_PRIV_H
-#define __CORE_FS_MGR_PRIV_H
+#pragma once
 
 #include <chrono>
 #include <string>
@@ -85,53 +84,6 @@
  *
  */
 
-// clang-format off
-#define MF_WAIT                  0x1
-#define MF_CHECK                 0x2
-#define MF_CRYPT                 0x4
-#define MF_NONREMOVABLE          0x8
-#define MF_VOLDMANAGED          0x10
-#define MF_LENGTH               0x20
-#define MF_RECOVERYONLY         0x40
-#define MF_SWAPPRIO             0x80
-#define MF_ZRAMSIZE            0x100
-#define MF_VERIFY              0x200
-#define MF_FORCECRYPT          0x400
-#define MF_NOEMULATEDSD        0x800 /* no emulated sdcard daemon, sd card is the only
-                                        external storage */
-#define MF_NOTRIM             0x1000
-#define MF_FILEENCRYPTION     0x2000
-#define MF_FORMATTABLE        0x4000
-#define MF_SLOTSELECT         0x8000
-#define MF_FORCEFDEORFBE     0x10000
-#define MF_LATEMOUNT         0x20000
-#define MF_NOFAIL            0x40000
-#define MF_VERIFYATBOOT      0x80000
-#define MF_MAX_COMP_STREAMS 0x100000
-#define MF_RESERVEDSIZE     0x200000
-#define MF_QUOTA            0x400000
-#define MF_ERASEBLKSIZE     0x800000
-#define MF_LOGICALBLKSIZE  0X1000000
-#define MF_AVB             0X2000000
-#define MF_KEYDIRECTORY    0X4000000
-#define MF_SYSFS           0X8000000
-#define MF_LOGICAL        0x10000000
-#define MF_CHECKPOINT_BLK 0x20000000
-#define MF_CHECKPOINT_FS  0x40000000
-#define MF_FIRST_STAGE_MOUNT \
-                          0x80000000
-#define MF_SLOTSELECT_OTHER  \
-                         0x100000000
-#define MF_ZRAM_LOOPBACK_PATH    \
-                         0x200000000
-#define MF_ZRAM_LOOPBACK_SIZE    \
-                         0x400000000
-#define MF_ZRAM_BACKING_DEV_PATH \
-                         0x800000000
-#define MF_FS_VERITY  \
-                         0x1000000000
-// clang-format on
-
 #define DM_BUF_SIZE 4096
 
 using namespace std::chrono_literals;
@@ -148,5 +100,3 @@
 const std::string& get_android_dt_dir();
 bool is_dt_compatible();
 int load_verity_state(const android::fs_mgr::FstabEntry& entry, int* mode);
-
-#endif /* __CORE_FS_MGR_PRIV_H */
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 6e9f1b0..8af80a7 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -65,9 +65,6 @@
 #define FS_MGR_DOMNT_FAILED (-1)
 #define FS_MGR_DOMNT_BUSY (-2)
 #define FS_MGR_DOMNT_SUCCESS 0
-int fs_mgr_do_mount(fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point);
-int fs_mgr_do_mount(fstab* fstab, const char* n_name, char* n_blk_device, char* tmp_mount_point,
-                    bool needs_checkpoint);
 int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
                     char* tmp_mount_point);
 int fs_mgr_do_mount(android::fs_mgr::Fstab* fstab, const char* n_name, char* n_blk_device,
@@ -75,8 +72,6 @@
 int fs_mgr_do_mount_one(const android::fs_mgr::FstabEntry& entry,
                         const std::string& mount_point = "");
 int fs_mgr_do_tmpfs_mount(const char *n_name);
-fstab_rec const* fs_mgr_get_crypt_entry(fstab const* fstab);
-void fs_mgr_get_crypt_info(fstab* fstab, char* key_loc, char* real_blk_device, size_t size);
 bool fs_mgr_load_verity_state(int* mode);
 bool fs_mgr_update_verity_state(
         std::function<void(const std::string& mount_point, int mode)> callback);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index a942d43..a3d9fdd 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -16,75 +16,13 @@
 
 #pragma once
 
-#include <linux/dm-ioctl.h>
-#include <stdbool.h>
 #include <stdint.h>
-#include <stdio.h>
 #include <sys/types.h>
 
 #include <set>
 #include <string>
 #include <vector>
 
-/*
- * The entries must be kept in the same order as they were seen in the fstab.
- * Unless explicitly requested, a lookup on mount point should always
- * return the 1st one.
- */
-struct fstab {
-    int num_entries;
-    struct fstab_rec* recs;
-};
-
-struct fstab_rec {
-    char* blk_device;
-    char* logical_partition_name;
-    char* mount_point;
-    char* fs_type;
-    unsigned long flags;
-    char* fs_options;
-    uint64_t fs_mgr_flags;
-    char* key_loc;
-    char* key_dir;
-    char* verity_loc;
-    off64_t length;
-    char* label;
-    int partnum;
-    int swap_prio;
-    int max_comp_streams;
-    off64_t zram_size;
-    off64_t reserved_size;
-    char* file_contents_mode;
-    char* file_names_mode;
-    off64_t erase_blk_size;
-    off64_t logical_blk_size;
-    char* sysfs_path;
-    char* zram_loopback_path;
-    uint64_t zram_loopback_size;
-    char* zram_backing_dev_path;
-};
-
-struct fstab* fs_mgr_read_fstab_default();
-struct fstab* fs_mgr_read_fstab(const char* fstab_path);
-void fs_mgr_free_fstab(struct fstab* fstab);
-
-struct fstab_rec* fs_mgr_get_entry_for_mount_point(struct fstab* fstab, const std::string& path);
-int fs_mgr_is_voldmanaged(const struct fstab_rec* fstab);
-int fs_mgr_is_nonremovable(const struct fstab_rec* fstab);
-int fs_mgr_is_verified(const struct fstab_rec* fstab);
-int fs_mgr_is_encryptable(const struct fstab_rec* fstab);
-void fs_mgr_get_file_encryption_modes(const struct fstab_rec* fstab, const char** contents_mode_ret,
-                                      const char** filenames_mode_ret);
-int fs_mgr_is_convertible_to_fbe(const struct fstab_rec* fstab);
-int fs_mgr_is_noemulatedsd(const struct fstab_rec* fstab);
-int fs_mgr_is_notrim(const struct fstab_rec* fstab);
-int fs_mgr_is_quota(const struct fstab_rec* fstab);
-int fs_mgr_is_logical(const struct fstab_rec* fstab);
-int fs_mgr_is_checkpoint(const struct fstab_rec* fstab);
-int fs_mgr_is_checkpoint_fs(const struct fstab_rec* fstab);
-int fs_mgr_is_checkpoint_blk(const struct fstab_rec* fstab);
-int fs_mgr_has_sysfs_path(const struct fstab_rec* fstab);
-
 std::string fs_mgr_get_slot_suffix();
 std::string fs_mgr_get_other_slot_suffix();
 
@@ -119,60 +57,34 @@
     std::string zram_backing_dev_path;
     std::string avb_key;
 
-    // TODO: Remove this union once fstab_rec is deprecated. It only serves as a
-    // convenient way to convert between fstab_rec::fs_mgr_flags and these bools.
-    union FsMgrFlags {
-        uint64_t val;
-        struct {
-            // bit 0
-            bool wait : 1;
-            bool check : 1;
-            bool crypt : 1;
-            bool nonremovable : 1;
-            bool vold_managed : 1;
-            bool length : 1;
-            bool recovery_only : 1;
-            bool swap_prio : 1;
-
-            // bit 8
-            bool zram_size : 1;
-            bool verify : 1;
-            bool force_crypt : 1;
-            bool no_emulated_sd : 1;  // No emulated sdcard daemon; sd card is the only external
-                                      // storage.
-            bool no_trim : 1;
-            bool file_encryption : 1;
-            bool formattable : 1;
-            bool slot_select : 1;
-
-            // bit 16
-            bool force_fde_or_fbe : 1;
-            bool late_mount : 1;
-            bool no_fail : 1;
-            bool verify_at_boot : 1;
-            bool max_comp_streams : 1;
-            bool reserved_size : 1;
-            bool quota : 1;
-            bool erase_blk_size : 1;
-
-            // bit 24
-            bool logical_blk_size : 1;
-            bool avb : 1;
-            bool key_directory : 1;
-            bool sysfs : 1;
-            bool logical : 1;
-            bool checkpoint_blk : 1;
-            bool checkpoint_fs : 1;
-            bool first_stage_mount : 1;
-
-            // bit 32
-            bool slot_select_other : 1;
-            bool zram_loopback_path : 1;
-            bool zram_loopback_size : 1;
-            bool zram_backing_dev_path : 1;
-            bool fs_verity : 1;
-        };
-    } fs_mgr_flags;
+    struct FsMgrFlags {
+        bool wait : 1;
+        bool check : 1;
+        bool crypt : 1;
+        bool nonremovable : 1;
+        bool vold_managed : 1;
+        bool recovery_only : 1;
+        bool verify : 1;
+        bool force_crypt : 1;
+        bool no_emulated_sd : 1;  // No emulated sdcard daemon; sd card is the only external
+                                  // storage.
+        bool no_trim : 1;
+        bool file_encryption : 1;
+        bool formattable : 1;
+        bool slot_select : 1;
+        bool force_fde_or_fbe : 1;
+        bool late_mount : 1;
+        bool no_fail : 1;
+        bool verify_at_boot : 1;
+        bool quota : 1;
+        bool avb : 1;
+        bool logical : 1;
+        bool checkpoint_blk : 1;
+        bool checkpoint_fs : 1;
+        bool first_stage_mount : 1;
+        bool slot_select_other : 1;
+        bool fs_verity : 1;
+    } fs_mgr_flags = {};
 
     bool is_encryptable() const {
         return fs_mgr_flags.crypt || fs_mgr_flags.force_crypt || fs_mgr_flags.force_fde_or_fbe;
@@ -180,6 +92,8 @@
 };
 
 // An Fstab is a collection of FstabEntry structs.
+// The entries must be kept in the same order as they were seen in the fstab.
+// Unless explicitly requested, a lookup on mount point should always return the 1st one.
 using Fstab = std::vector<FstabEntry>;
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
@@ -195,8 +109,3 @@
 
 }  // namespace fs_mgr
 }  // namespace android
-
-// Temporary conversion functions.
-android::fs_mgr::FstabEntry FstabRecToFstabEntry(const fstab_rec* fstab_rec);
-android::fs_mgr::Fstab LegacyFstabToFstab(const struct fstab* legacy_fstab);
-fstab* FstabToLegacyFstab(const android::fs_mgr::Fstab& fstab);
diff --git a/fs_mgr/libfs_avb/TEST_MAPPING b/fs_mgr/libfs_avb/TEST_MAPPING
index dc23827..b0f36d4 100644
--- a/fs_mgr/libfs_avb/TEST_MAPPING
+++ b/fs_mgr/libfs_avb/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "presubmit": [
+  "postsubmit": [
     {
       "name": "libfs_avb_test",
       "host": true
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 870c98c..1815a38 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -256,6 +256,36 @@
     EXPECT_EQ("", fstab[10].fs_options);
 }
 
+static bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
+    // clang-format off
+    return lhs.wait == rhs.wait &&
+           lhs.check == rhs.check &&
+           lhs.crypt == rhs.crypt &&
+           lhs.nonremovable == rhs.nonremovable &&
+           lhs.vold_managed == rhs.vold_managed &&
+           lhs.recovery_only == rhs.recovery_only &&
+           lhs.verify == rhs.verify &&
+           lhs.force_crypt == rhs.force_crypt &&
+           lhs.no_emulated_sd == rhs.no_emulated_sd &&
+           lhs.no_trim == rhs.no_trim &&
+           lhs.file_encryption == rhs.file_encryption &&
+           lhs.formattable == rhs.formattable &&
+           lhs.slot_select == rhs.slot_select &&
+           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
+           lhs.late_mount == rhs.late_mount &&
+           lhs.no_fail == rhs.no_fail &&
+           lhs.verify_at_boot == rhs.verify_at_boot &&
+           lhs.quota == rhs.quota &&
+           lhs.avb == rhs.avb &&
+           lhs.logical == rhs.logical &&
+           lhs.checkpoint_blk == rhs.checkpoint_blk &&
+           lhs.checkpoint_fs == rhs.checkpoint_fs &&
+           lhs.first_stage_mount == rhs.first_stage_mount &&
+           lhs.slot_select_other == rhs.slot_select_other &&
+           lhs.fs_verity == rhs.fs_verity;
+    // clang-format on
+}
+
 TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
@@ -276,62 +306,62 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.wait = true;
         flags.check = true;
         flags.nonremovable = true;
         flags.recovery_only = true;
         flags.verify_at_boot = true;
         flags.verify = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.avb = true;
         flags.no_emulated_sd = true;
         flags.no_trim = true;
         flags.formattable = true;
         flags.slot_select = true;
         flags.no_fail = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.first_stage_mount = true;
         flags.late_mount = true;
         flags.quota = true;
         flags.logical = true;
         flags.slot_select_other = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_blk = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     entry++;
 
     EXPECT_EQ("none4", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_fs = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     entry++;
 
     EXPECT_EQ("none5", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        FstabEntry::FsMgrFlags flags = {};
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
 }
 
@@ -355,8 +385,8 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        FstabEntry::FsMgrFlags flags = {};
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     EXPECT_EQ("", entry->key_loc);
     EXPECT_EQ("", entry->key_dir);
@@ -380,25 +410,13 @@
 
     EXPECT_EQ("none1", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.crypt = true;
         flags.force_crypt = true;
         flags.file_encryption = true;
-        flags.key_directory = true;
-        flags.length = true;
-        flags.swap_prio = true;
-        flags.zram_size = true;
-        flags.max_comp_streams = true;
         flags.verify = true;
         flags.avb = true;
-        flags.reserved_size = true;
-        flags.erase_blk_size = true;
-        flags.logical_blk_size = true;
-        flags.sysfs = true;
-        flags.zram_loopback_path = true;
-        flags.zram_loopback_size = true;
-        flags.zram_backing_dev_path = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     EXPECT_EQ("", entry->key_loc);
     EXPECT_EQ("", entry->key_dir);
@@ -423,9 +441,9 @@
     // forcefdeorfbe sets file_contents_mode and file_names_mode by default, so test it separately.
     EXPECT_EQ("none2", entry->mount_point);
     {
-        FstabEntry::FsMgrFlags flags = {0};
+        FstabEntry::FsMgrFlags flags = {};
         flags.force_fde_or_fbe = true;
-        EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     EXPECT_EQ("aes-256-xts", entry->file_contents_mode);
     EXPECT_EQ("aes-256-cts", entry->file_names_mode);
@@ -444,12 +462,12 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(1U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.crypt = true;
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("/dir/key", entry->key_loc);
 }
 
@@ -468,30 +486,30 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(4U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.vold_managed = true;
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_TRUE(entry->label.empty());
     EXPECT_EQ(-1, entry->partnum);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_TRUE(entry->label.empty());
     EXPECT_EQ(-1, entry->partnum);
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("sdcard", entry->label);
     EXPECT_EQ(3, entry->partnum);
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("sdcard", entry->label);
     EXPECT_EQ(-1, entry->partnum);
 }
@@ -509,17 +527,16 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(2U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.length = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->length);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(123456, entry->length);
 }
 
@@ -536,17 +553,16 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(2U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.swap_prio = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(-1, entry->swap_prio);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(123456, entry->swap_prio);
 }
 
@@ -567,37 +583,36 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(6U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.zram_size = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->zram_size);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(123456, entry->zram_size);
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->zram_size);
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_NE(0, entry->zram_size);
     entry++;
 
     EXPECT_EQ("none4", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->zram_size);
     entry++;
 
     EXPECT_EQ("none5", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->zram_size);
 }
 
@@ -617,9 +632,9 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.verify = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("/dir/key", entry->verity_loc);
 }
@@ -640,9 +655,9 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.force_crypt = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("/dir/key", entry->key_loc);
 }
@@ -663,9 +678,9 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.force_fde_or_fbe = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("/dir/key", entry->key_loc);
     EXPECT_EQ("aes-256-xts", entry->file_contents_mode);
@@ -695,72 +710,72 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(11U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.file_encryption = true;
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("", entry->file_contents_mode);
     EXPECT_EQ("", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("aes-256-xts", entry->file_contents_mode);
     EXPECT_EQ("aes-256-cts", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("aes-256-xts", entry->file_contents_mode);
     EXPECT_EQ("aes-256-cts", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("adiantum", entry->file_contents_mode);
     EXPECT_EQ("adiantum", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none4", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("adiantum", entry->file_contents_mode);
     EXPECT_EQ("aes-256-heh", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none5", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("ice", entry->file_contents_mode);
     EXPECT_EQ("aes-256-cts", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none6", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("ice", entry->file_contents_mode);
     EXPECT_EQ("", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none7", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("ice", entry->file_contents_mode);
     EXPECT_EQ("aes-256-cts", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none8", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("ice", entry->file_contents_mode);
     EXPECT_EQ("aes-256-heh", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none9", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("ice", entry->file_contents_mode);
     EXPECT_EQ("adiantum", entry->file_names_mode);
 
     entry++;
     EXPECT_EQ("none10", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ("", entry->file_contents_mode);
     EXPECT_EQ("", entry->file_names_mode);
 }
@@ -778,17 +793,16 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(2U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.max_comp_streams = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->max_comp_streams);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(123456, entry->max_comp_streams);
 }
 
@@ -807,27 +821,26 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(4U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.reserved_size = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->reserved_size);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(2, entry->reserved_size);
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(1024, entry->reserved_size);
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(2 * 1024 * 1024, entry->reserved_size);
 }
 
@@ -846,27 +859,26 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(4U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.erase_blk_size = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->erase_blk_size);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->erase_blk_size);
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->erase_blk_size);
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(8192, entry->erase_blk_size);
 }
 
@@ -885,27 +897,26 @@
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
     ASSERT_EQ(4U, fstab.size());
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.logical_blk_size = true;
+    FstabEntry::FsMgrFlags flags = {};
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->logical_blk_size);
     entry++;
 
     EXPECT_EQ("none1", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->logical_blk_size);
     entry++;
 
     EXPECT_EQ("none2", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(0, entry->logical_blk_size);
     entry++;
 
     EXPECT_EQ("none3", entry->mount_point);
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     EXPECT_EQ(8192, entry->logical_blk_size);
 }
 
@@ -925,9 +936,9 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
+    FstabEntry::FsMgrFlags flags = {};
     flags.avb = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("vbmeta_partition", entry->vbmeta_partition);
 }
@@ -948,9 +959,8 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.key_directory = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    FstabEntry::FsMgrFlags flags = {};
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("/dir/key", entry->key_dir);
 }
@@ -971,9 +981,8 @@
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
 
-    FstabEntry::FsMgrFlags flags = {0};
-    flags.sysfs = true;
-    EXPECT_EQ(flags.val, entry->fs_mgr_flags.val);
+    FstabEntry::FsMgrFlags flags = {};
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
 
     EXPECT_EQ("/sys/device", entry->sysfs_path);
 }
diff --git a/healthd/Android.mk b/healthd/Android.mk
index 2127b96..823ed06 100644
--- a/healthd/Android.mk
+++ b/healthd/Android.mk
@@ -109,6 +109,7 @@
     libbase \
     libutils \
     libcutils \
+    libjsoncpp \
     libprocessgroup \
     liblog \
     libm \
diff --git a/init/Android.bp b/init/Android.bp
index 67688f2..639d8d1 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -60,7 +60,6 @@
     },
     static_libs: [
         "libseccomp_policy",
-        "libprocessgroup",
         "libavb",
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
@@ -82,6 +81,7 @@
         "liblog",
         "liblogwrap",
         "liblp",
+        "libprocessgroup",
         "libselinux",
         "libutils",
     ],
diff --git a/init/init.cpp b/init/init.cpp
index 4f4a15f..a8924f2 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -42,6 +42,7 @@
 #include <fs_mgr_vendor_overlay.h>
 #include <keyutils.h>
 #include <libavb/libavb.h>
+#include <processgroup/processgroup.h>
 #include <selinux/android.h>
 
 #ifndef RECOVERY
@@ -191,7 +192,8 @@
 
     if (waiting_for_prop) {
         if (wait_prop_name == name && wait_prop_value == value) {
-            LOG(INFO) << "Wait for property took " << *waiting_for_prop;
+            LOG(INFO) << "Wait for property '" << wait_prop_name << "=" << wait_prop_value
+                      << "' took " << *waiting_for_prop;
             ResetWaitForProp();
         }
     }
@@ -347,6 +349,17 @@
     return Success();
 }
 
+static Result<Success> SetupCgroupsAction(const BuiltinArguments&) {
+    // Have to create <CGROUPS_RC_DIR> using make_dir function
+    // for appropriate sepolicy to be set for it
+    make_dir(CGROUPS_RC_DIR, 0711);
+    if (!CgroupSetupCgroups()) {
+        return ErrnoError() << "Failed to setup cgroups";
+    }
+
+    return Success();
+}
+
 static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
     if (key.empty()) return;
 
@@ -682,6 +695,8 @@
     // Nexus 9 boot time, so it's disabled by default.
     if (false) DumpState();
 
+    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
+
     am.QueueEventTrigger("early-init");
 
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
diff --git a/init/service.cpp b/init/service.cpp
index a6eb7f7..d64f2f3 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -375,7 +375,7 @@
 
     // If we crash > 4 times in 4 minutes, reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
-    if (((flags_ & SVC_CRITICAL) || classnames_.count("updatable")) && !(flags_ & SVC_RESTART)) {
+    if (((flags_ & SVC_CRITICAL) || !pre_apexd_) && !(flags_ & SVC_RESTART)) {
         if (now < time_crashed_ + 4min) {
             if (++crash_count_ > 4) {
                 if (flags_ & SVC_CRITICAL) {
@@ -991,27 +991,33 @@
         std::for_each(descriptors_.begin(), descriptors_.end(),
                       std::bind(&DescriptorInfo::CreateAndPublish, std::placeholders::_1, scon));
 
-        // See if there were "writepid" instructions to write to files under /dev/cpuset/.
-        auto cpuset_predicate = [](const std::string& path) {
-            return StartsWith(path, "/dev/cpuset/");
-        };
-        auto iter = std::find_if(writepid_files_.begin(), writepid_files_.end(), cpuset_predicate);
-        if (iter == writepid_files_.end()) {
-            // There were no "writepid" instructions for cpusets, check if the system default
-            // cpuset is specified to be used for the process.
-            std::string default_cpuset = GetProperty("ro.cpuset.default", "");
-            if (!default_cpuset.empty()) {
-                // Make sure the cpuset name starts and ends with '/'.
-                // A single '/' means the 'root' cpuset.
-                if (default_cpuset.front() != '/') {
-                    default_cpuset.insert(0, 1, '/');
+        // See if there were "writepid" instructions to write to files under cpuset path.
+        std::string cpuset_path;
+        if (CgroupGetControllerPath("cpuset", &cpuset_path)) {
+            auto cpuset_predicate = [&cpuset_path](const std::string& path) {
+                return StartsWith(path, cpuset_path + "/");
+            };
+            auto iter =
+                    std::find_if(writepid_files_.begin(), writepid_files_.end(), cpuset_predicate);
+            if (iter == writepid_files_.end()) {
+                // There were no "writepid" instructions for cpusets, check if the system default
+                // cpuset is specified to be used for the process.
+                std::string default_cpuset = GetProperty("ro.cpuset.default", "");
+                if (!default_cpuset.empty()) {
+                    // Make sure the cpuset name starts and ends with '/'.
+                    // A single '/' means the 'root' cpuset.
+                    if (default_cpuset.front() != '/') {
+                        default_cpuset.insert(0, 1, '/');
+                    }
+                    if (default_cpuset.back() != '/') {
+                        default_cpuset.push_back('/');
+                    }
+                    writepid_files_.push_back(
+                            StringPrintf("%s%stasks", cpuset_path.c_str(), default_cpuset.c_str()));
                 }
-                if (default_cpuset.back() != '/') {
-                    default_cpuset.push_back('/');
-                }
-                writepid_files_.push_back(
-                    StringPrintf("/dev/cpuset%stasks", default_cpuset.c_str()));
             }
+        } else {
+            LOG(ERROR) << "cpuset cgroup controller is not mounted!";
         }
         std::string pid_str = std::to_string(getpid());
         for (const auto& file : writepid_files_) {
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index 0cc4fc0..f65dc8b 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -23,6 +23,9 @@
  */
 #define LOG_TAG "ashmem"
 
+#ifndef __ANDROID_VNDK__
+#include <dlfcn.h>
+#endif
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/ashmem.h>
@@ -45,13 +48,46 @@
  */
 static pthread_mutex_t __ashmem_lock = PTHREAD_MUTEX_INITIALIZER;
 
+/*
+ * We use ashmemd to enforce that apps don't open /dev/ashmem directly. Vendor
+ * code can't access system aidl services per Treble requirements. So we limit
+ * ashmemd access to the system variant of libcutils.
+ */
+#ifndef __ANDROID_VNDK__
+using openFdType = int (*)();
+
+openFdType initOpenAshmemFd() {
+    openFdType openFd = nullptr;
+    void* handle = dlopen("libashmemd_client.so", RTLD_NOW);
+    if (!handle) {
+        ALOGE("Failed to dlopen() libashmemd_client.so: %s", dlerror());
+        return openFd;
+    }
+
+    openFd = reinterpret_cast<openFdType>(dlsym(handle, "openAshmemdFd"));
+    if (!openFd) {
+        ALOGE("Failed to dlsym() openAshmemdFd() function: %s", dlerror());
+    }
+    return openFd;
+}
+#endif
+
 /* logistics of getting file descriptor for ashmem */
 static int __ashmem_open_locked()
 {
     int ret;
     struct stat st;
 
-    int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC));
+    int fd = -1;
+#ifndef __ANDROID_VNDK__
+    static auto openFd = initOpenAshmemFd();
+    if (openFd) {
+        fd = openFd();
+    }
+#endif
+    if (fd < 0) {
+        fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC));
+    }
     if (fd < 0) {
         return fd;
     }
diff --git a/libcutils/tests/Android.bp b/libcutils/tests/Android.bp
index 72ae559..fb9bbdd 100644
--- a/libcutils/tests/Android.bp
+++ b/libcutils/tests/Android.bp
@@ -59,6 +59,7 @@
     "libcutils",
     "liblog",
     "libbase",
+    "libjsoncpp",
     "libprocessgroup",
 ]
 
diff --git a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
index 95aa2c7..eb53e57 100644
--- a/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
+++ b/libmeminfo/libdmabufinfo/dmabufinfo_test.cpp
@@ -14,14 +14,17 @@
  */
 
 #include <gtest/gtest.h>
+#include <inttypes.h>
 #include <linux/dma-buf.h>
+#include <poll.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <fstream>
 #include <string>
-#include <vector>
 #include <unordered_map>
+#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -50,6 +53,115 @@
 #define DMA_BUF_SET_NAME _IOW(DMA_BUF_BASE, 5, const char*)
 #endif
 
+class fd_sharer {
+  public:
+    fd_sharer();
+    ~fd_sharer() { kill(); }
+
+    bool ok() const { return child_pid > 0; }
+    bool sendfd(int fd);
+    bool kill();
+    pid_t pid() const { return child_pid; }
+
+  private:
+    unique_fd parent_fd, child_fd;
+    pid_t child_pid;
+
+    void run();
+};
+
+fd_sharer::fd_sharer() : parent_fd{}, child_fd{}, child_pid{-1} {
+    bool sp_ok = android::base::Socketpair(SOCK_STREAM, &parent_fd, &child_fd);
+    if (!sp_ok) return;
+
+    child_pid = fork();
+    if (child_pid < 0) return;
+
+    if (child_pid == 0) run();
+}
+
+bool fd_sharer::kill() {
+    int err = ::kill(child_pid, SIGKILL);
+    if (err < 0) return false;
+
+    return ::waitpid(child_pid, nullptr, 0) == child_pid;
+}
+
+void fd_sharer::run() {
+    while (true) {
+        int fd;
+        char unused = 0;
+
+        iovec iov{};
+        iov.iov_base = &unused;
+        iov.iov_len = sizeof(unused);
+
+        msghdr msg{};
+        msg.msg_iov = &iov;
+        msg.msg_iovlen = 1;
+
+        char cmsg_buf[CMSG_SPACE(sizeof(fd))];
+        msg.msg_control = cmsg_buf;
+        msg.msg_controllen = sizeof(cmsg_buf);
+
+        cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
+        ssize_t s = TEMP_FAILURE_RETRY(recvmsg(child_fd, &msg, 0));
+        if (s == -1) break;
+
+        s = TEMP_FAILURE_RETRY(write(child_fd, &unused, sizeof(unused)));
+        if (s == -1) break;
+    }
+}
+
+bool fd_sharer::sendfd(int fd) {
+    char unused = 0;
+
+    iovec iov{};
+    iov.iov_base = &unused;
+    iov.iov_len = sizeof(unused);
+
+    msghdr msg{};
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    char cmsg_buf[CMSG_SPACE(sizeof(fd))];
+    msg.msg_control = cmsg_buf;
+    msg.msg_controllen = sizeof(cmsg_buf);
+
+    cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+    cmsg->cmsg_level = SOL_SOCKET;
+    cmsg->cmsg_type = SCM_RIGHTS;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
+    int* fd_buf = reinterpret_cast<int*>(CMSG_DATA(cmsg));
+    *fd_buf = fd;
+
+    ssize_t s = TEMP_FAILURE_RETRY(sendmsg(parent_fd, &msg, 0));
+    if (s == -1) return false;
+
+    // The target process installs the fd into its fd table during recvmsg().
+    // So if we return now, there's a brief window between sendfd() finishing
+    // and libmemoryinfo actually seeing that the buffer has been shared.  This
+    // window is just large enough to break tests.
+    //
+    // To work around this, wait for the target process to respond with a dummy
+    // byte, with a timeout of 1 s.
+    pollfd p{};
+    p.fd = parent_fd;
+    p.events = POLL_IN;
+    int ready = poll(&p, 1, 1000);
+    if (ready != 1) return false;
+
+    s = TEMP_FAILURE_RETRY(read(parent_fd, &unused, sizeof(unused)));
+    if (s == -1) return false;
+
+    return true;
+}
+
 #define EXPECT_ONE_BUF_EQ(_bufptr, _name, _fdrefs, _maprefs, _expname, _count, _size) \
     do {                                                                              \
         EXPECT_EQ(_bufptr->name(), _name);                                            \
@@ -140,6 +252,24 @@
         return unique_fd{fd};
     }
 
+    void readAndCheckDmaBuffer(std::vector<DmaBuffer>* dmabufs, pid_t pid, const std::string name,
+                               size_t fdrefs_size, size_t maprefs_size, const std::string exporter,
+                               size_t refcount, uint64_t buf_size, bool expectFdrefs,
+                               bool expectMapRefs) {
+        EXPECT_TRUE(ReadDmaBufInfo(pid, dmabufs));
+        EXPECT_EQ(dmabufs->size(), 1UL);
+        EXPECT_ONE_BUF_EQ(dmabufs->begin(), name, fdrefs_size, maprefs_size, exporter, refcount,
+                          buf_size);
+        // Make sure the buffer has the right pid too.
+        EXPECT_PID_IN_FDREFS(dmabufs->begin(), pid, expectFdrefs);
+        EXPECT_PID_IN_MAPREFS(dmabufs->begin(), pid, expectMapRefs);
+    }
+
+    bool checkPidRef(DmaBuffer& dmabuf, pid_t pid, int expectFdrefs) {
+        int fdrefs = dmabuf.fdrefs().find(pid)->second;
+        return fdrefs == expectFdrefs;
+    }
+
   private:
     int get_ion_heap_mask() {
         if (ion_fd < 0) {
@@ -196,7 +326,7 @@
         EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL);
 
         // Make sure the buffer has the right pid too.
-        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false);
+        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true);
     }
 
     // Now make sure the buffer has disappeared
@@ -222,8 +352,8 @@
         EXPECT_ONE_BUF_EQ(dmabufs.begin(), "dmabuftester-4k", 1UL, 1UL, "ion", 2UL, 4096ULL);
 
         // Make sure the buffer has the right pid too.
-        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false);
-        EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false);
+        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true);
+        EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true);
 
         // close the file descriptor and re-read the stats
         buf.reset(-1);
@@ -232,8 +362,8 @@
         EXPECT_EQ(dmabufs.size(), 1UL);
         EXPECT_ONE_BUF_EQ(dmabufs.begin(), "<unknown>", 0UL, 1UL, "<unknown>", 0UL, 4096ULL);
 
-        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, true);
-        EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, false);
+        EXPECT_PID_IN_FDREFS(dmabufs.begin(), pid, false);
+        EXPECT_PID_IN_MAPREFS(dmabufs.begin(), pid, true);
 
         // unmap the bufer and lose all references
         munmap(ptr, 4096);
@@ -244,6 +374,109 @@
     EXPECT_TRUE(dmabufs.empty());
 }
 
+TEST_F(DmaBufTester, TestSharedfd) {
+    // Each time a shared buffer is received over a socket, the remote process
+    // will take an extra reference on it.
+
+    ASSERT_TRUE(is_valid());
+
+    pid_t pid = getpid();
+    std::vector<DmaBuffer> dmabufs;
+    {
+        fd_sharer sharer{};
+        ASSERT_TRUE(sharer.ok());
+        // Allocate one buffer and make sure the library can see it
+        unique_fd buf = allocate(4096, "dmabuftester-4k");
+        ASSERT_GT(buf, 0) << "Allocated buffer is invalid";
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+
+        ASSERT_TRUE(sharer.sendfd(buf));
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true,
+                              false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1));
+        readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 2UL,
+                              4096ULL, true, false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], sharer.pid(), 1));
+
+        ASSERT_TRUE(sharer.sendfd(buf));
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 3UL, 4096ULL, true,
+                              false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1));
+        readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 3UL,
+                              4096ULL, true, false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], sharer.pid(), 2));
+
+        ASSERT_TRUE(sharer.kill());
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+    }
+
+    // Now make sure the buffer has disappeared
+    ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs));
+    EXPECT_TRUE(dmabufs.empty());
+}
+
+TEST_F(DmaBufTester, DupFdTest) {
+    // dup()ing an fd will make this process take an extra reference on the
+    // shared buffer.
+
+    ASSERT_TRUE(is_valid());
+
+    pid_t pid = getpid();
+    std::vector<DmaBuffer> dmabufs;
+    {
+        // Allocate one buffer and make sure the library can see it
+        unique_fd buf = allocate(4096, "dmabuftester-4k");
+        ASSERT_GT(buf, 0) << "Allocated buffer is invalid";
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+
+        unique_fd buf2{dup(buf)};
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true,
+                              false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 2));
+
+        close(buf2.release());
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+        EXPECT_TRUE(checkPidRef(dmabufs[0], pid, 1));
+    }
+
+    // Now make sure the buffer has disappeared
+    ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs));
+    EXPECT_TRUE(dmabufs.empty());
+}
+
+TEST_F(DmaBufTester, ForkTest) {
+    // fork()ing a child will cause the child to automatically take a reference
+    // on any existing shared buffers.
+    ASSERT_TRUE(is_valid());
+
+    pid_t pid = getpid();
+    std::vector<DmaBuffer> dmabufs;
+    {
+        // Allocate one buffer and make sure the library can see it
+        unique_fd buf = allocate(4096, "dmabuftester-4k");
+        ASSERT_GT(buf, 0) << "Allocated buffer is invalid";
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+        fd_sharer sharer{};
+        ASSERT_TRUE(sharer.ok());
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 2UL, 4096ULL, true,
+                              false);
+        readAndCheckDmaBuffer(&dmabufs, sharer.pid(), "dmabuftester-4k", 1UL, 0UL, "ion", 2UL,
+                              4096ULL, true, false);
+        ASSERT_TRUE(sharer.kill());
+        readAndCheckDmaBuffer(&dmabufs, pid, "dmabuftester-4k", 1UL, 0UL, "ion", 1UL, 4096ULL, true,
+                              false);
+    }
+
+    // Now make sure the buffer has disappeared
+    ASSERT_TRUE(ReadDmaBufInfo(pid, &dmabufs));
+    EXPECT_TRUE(dmabufs.empty());
+}
+
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ::android::base::InitLogging(argv, android::base::StderrLogger);
diff --git a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
index 74eff3c..e3be320 100644
--- a/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
+++ b/libmeminfo/libdmabufinfo/include/dmabufinfo/dmabufinfo.h
@@ -44,7 +44,7 @@
     }
 
     // Getters for each property
-    uint64_t size() { return size_; }
+    uint64_t size() const { return size_; }
     const std::unordered_map<pid_t, int>& fdrefs() const { return fdrefs_; }
     const std::unordered_map<pid_t, int>& maprefs() const { return maprefs_; }
     ino_t inode() const { return inode_; }
@@ -56,6 +56,11 @@
     void SetExporter(const std::string& exporter) { exporter_ = exporter; }
     void SetCount(uint64_t count) { count_ = count; }
 
+    bool operator==(const DmaBuffer& rhs) {
+        return (inode_ == rhs.inode()) && (size_ == rhs.size()) && (name_ == rhs.name()) &&
+               (exporter_ == rhs.exporter());
+    }
+
   private:
     ino_t inode_;
     uint64_t size_;
diff --git a/libnativebridge/include/nativebridge/native_bridge.h b/libnativebridge/include/nativebridge/native_bridge.h
index 5aea967..e9c9500 100644
--- a/libnativebridge/include/nativebridge/native_bridge.h
+++ b/libnativebridge/include/nativebridge/native_bridge.h
@@ -164,8 +164,9 @@
 void* NativeBridgeLoadLibraryExt(const char* libpath, int flag,
                                  struct native_bridge_namespace_t* ns);
 
-// Returns vendor namespace if it is enabled for the device and null otherwise
-struct native_bridge_namespace_t* NativeBridgeGetVendorNamespace();
+// Returns exported namespace by the name. This is a reflection of
+// android_get_exported_namespace function. Introduced in v5.
+struct native_bridge_namespace_t* NativeBridgeGetExportedNamespace(const char* name);
 
 // Native bridge interfaces to runtime.
 struct NativeBridgeCallbacks {
@@ -362,7 +363,17 @@
   //
   // Returns:
   //   vendor namespace or null if it was not set up for the device
+  //
+  // Starting with v5 (Android Q) this function is no longer used.
+  // Use getExportedNamespace() below.
   struct native_bridge_namespace_t* (*getVendorNamespace)();
+
+  // Get native bridge version of exported namespace. Peer of
+  // android_get_exported_namespace(const char*) function.
+  //
+  // Returns:
+  //   exported namespace or null if it was not set up for the device
+  struct native_bridge_namespace_t* (*getExportedNamespace)(const char* name);
 };
 
 // Runtime interfaces to native bridge.
diff --git a/libnativebridge/libnativebridge.map.txt b/libnativebridge/libnativebridge.map.txt
index a616b85..a6841a3 100644
--- a/libnativebridge/libnativebridge.map.txt
+++ b/libnativebridge/libnativebridge.map.txt
@@ -24,7 +24,7 @@
     NativeBridgeGetError;
     NativeBridgeIsPathSupported;
     NativeBridgeCreateNamespace;
-    NativeBridgeGetVendorNamespace;
+    NativeBridgeGetExportedNamespace;
     NativeBridgeLinkNamespaces;
     NativeBridgeLoadLibraryExt;
     NativeBridgeInitAnonymousNamespace;
diff --git a/libnativebridge/native_bridge.cc b/libnativebridge/native_bridge.cc
index a2d8d81..9adba9a 100644
--- a/libnativebridge/native_bridge.cc
+++ b/libnativebridge/native_bridge.cc
@@ -101,6 +101,8 @@
   NAMESPACE_VERSION = 3,
   // The version with vendor namespaces
   VENDOR_NAMESPACE_VERSION = 4,
+  // The version with runtime namespaces
+  RUNTIME_NAMESPACE_VERSION = 5,
 };
 
 // Whether we had an error at some point.
@@ -610,12 +612,22 @@
   return false;
 }
 
-native_bridge_namespace_t* NativeBridgeGetVendorNamespace() {
-  if (!NativeBridgeInitialized() || !isCompatibleWith(VENDOR_NAMESPACE_VERSION)) {
+native_bridge_namespace_t* NativeBridgeGetExportedNamespace(const char* name) {
+  if (!NativeBridgeInitialized()) {
     return nullptr;
   }
 
-  return callbacks->getVendorNamespace();
+  if (isCompatibleWith(RUNTIME_NAMESPACE_VERSION)) {
+    return callbacks->getExportedNamespace(name);
+  }
+
+  // sphal is vendor namespace name -> use v4 callback in the case NB callbacks
+  // are not compatible with v5
+  if (isCompatibleWith(VENDOR_NAMESPACE_VERSION) && name != nullptr && strcmp("sphal", name) == 0) {
+    return callbacks->getVendorNamespace();
+  }
+
+  return nullptr;
 }
 
 void* NativeBridgeLoadLibraryExt(const char* libpath, int flag, native_bridge_namespace_t* ns) {
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp
index 1ec21e9..2802d36 100644
--- a/libnativeloader/Android.bp
+++ b/libnativeloader/Android.bp
@@ -23,10 +23,6 @@
         "llndk.libraries.txt",
         "vndksp.libraries.txt",
     ],
-    stubs: {
-        symbol_file: "libnativeloader.map.txt",
-        versions: ["1"],
-    },
 }
 
 cc_library_headers {
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index ad967db..a4e00bd 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -103,6 +103,11 @@
 static constexpr const char kVndkspNativeLibrariesSystemConfigPathFromRoot[] =
     "/etc/vndksp.libraries.txt";
 
+static const std::vector<const std::string> kRuntimePublicLibraries = {
+    "libicuuc.so",
+    "libicui18n.so",
+};
+
 // The device may be configured to have the vendor libraries loaded to a separate namespace.
 // For historical reasons this namespace was named sphal but effectively it is intended
 // to use to load vendor libraries to separate namespace with controlled interface between
@@ -111,6 +116,8 @@
 
 static constexpr const char* kVndkNamespaceName = "vndk";
 
+static constexpr const char* kRuntimeNamespaceName = "runtime";
+
 static constexpr const char* kClassloaderNamespaceName = "classloader-namespace";
 static constexpr const char* kVendorClassloaderNamespaceName = "vendor-classloader-namespace";
 
@@ -245,6 +252,8 @@
       }
     }
 
+    std::string runtime_exposed_libraries = base::Join(kRuntimePublicLibraries, ":");
+
     NativeLoaderNamespace native_loader_ns;
     if (!is_native_bridge) {
       android_namespace_t* android_parent_ns =
@@ -265,11 +274,21 @@
       // which is expected behavior in this case.
       android_namespace_t* vendor_ns = android_get_exported_namespace(kVendorNamespaceName);
 
+      android_namespace_t* runtime_ns = android_get_exported_namespace(kRuntimeNamespaceName);
+
       if (!android_link_namespaces(ns, nullptr, system_exposed_libraries.c_str())) {
         *error_msg = dlerror();
         return nullptr;
       }
 
+      // Runtime apex does not exist in host, and under certain build conditions.
+      if (runtime_ns != nullptr) {
+        if (!android_link_namespaces(ns, runtime_ns, runtime_exposed_libraries.c_str())) {
+          *error_msg = dlerror();
+          return nullptr;
+        }
+      }
+
       if (vndk_ns != nullptr && !system_vndksp_libraries_.empty()) {
         // vendor apks are allowed to use VNDK-SP libraries.
         if (!android_link_namespaces(ns, vndk_ns, system_vndksp_libraries_.c_str())) {
@@ -300,13 +319,22 @@
         return nullptr;
       }
 
-      native_bridge_namespace_t* vendor_ns = NativeBridgeGetVendorNamespace();
+      native_bridge_namespace_t* vendor_ns = NativeBridgeGetExportedNamespace(kVendorNamespaceName);
+      native_bridge_namespace_t* runtime_ns =
+          NativeBridgeGetExportedNamespace(kRuntimeNamespaceName);
 
       if (!NativeBridgeLinkNamespaces(ns, nullptr, system_exposed_libraries.c_str())) {
         *error_msg = NativeBridgeGetError();
         return nullptr;
       }
 
+      // Runtime apex does not exist in host, and under certain build conditions.
+      if (runtime_ns != nullptr) {
+        if (!NativeBridgeLinkNamespaces(ns, runtime_ns, runtime_exposed_libraries.c_str())) {
+          *error_msg = NativeBridgeGetError();
+          return nullptr;
+        }
+      }
       if (!vendor_public_libraries_.empty()) {
         if (!NativeBridgeLinkNamespaces(ns, vendor_ns, vendor_public_libraries_.c_str())) {
           *error_msg = NativeBridgeGetError();
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index d04a79a..d97f09f 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -16,8 +16,10 @@
 
 cc_library {
     srcs: [
+        "cgroup_map.cpp",
         "processgroup.cpp",
         "sched_policy.cpp",
+        "task_profiles.cpp",
     ],
     name: "libprocessgroup",
     host_supported: true,
@@ -29,7 +31,7 @@
     },
     shared_libs: [
         "libbase",
-        "liblog",
+        "libjsoncpp",
     ],
     // for cutils/android_filesystem_config.h
     header_libs: [
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
new file mode 100644
index 0000000..12cfb7e
--- /dev/null
+++ b/libprocessgroup/cgroup_map.cpp
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "libprocessgroup"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <regex>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <cgroup_map.h>
+#include <json/reader.h>
+#include <json/value.h>
+#include <processgroup/processgroup.h>
+
+using android::base::GetBoolProperty;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
+
+static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
+static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
+static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.tasks";
+
+static bool Mkdir(const std::string& path, mode_t mode, const std::string& uid,
+                  const std::string& gid) {
+    if (mode == 0) {
+        mode = 0755;
+    }
+
+    if (mkdir(path.c_str(), mode) != 0) {
+        /* chmod in case the directory already exists */
+        if (errno == EEXIST) {
+            if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+                // /acct is a special case when the directory already exists
+                // TODO: check if file mode is already what we want instead of using EROFS
+                if (errno != EROFS) {
+                    PLOG(ERROR) << "fchmodat() failed for " << path;
+                    return false;
+                }
+            }
+        } else {
+            PLOG(ERROR) << "mkdir() failed for " << path;
+            return false;
+        }
+    }
+
+    passwd* uid_pwd = nullptr;
+    passwd* gid_pwd = nullptr;
+
+    if (!uid.empty()) {
+        uid_pwd = getpwnam(uid.c_str());
+        if (!uid_pwd) {
+            PLOG(ERROR) << "Unable to decode UID for '" << uid << "'";
+            return false;
+        }
+
+        if (!gid.empty()) {
+            gid_pwd = getpwnam(gid.c_str());
+            if (!gid_pwd) {
+                PLOG(ERROR) << "Unable to decode GID for '" << gid << "'";
+                return false;
+            }
+        }
+    }
+
+    if (uid_pwd && lchown(path.c_str(), uid_pwd->pw_uid, gid_pwd ? gid_pwd->pw_uid : -1) < 0) {
+        PLOG(ERROR) << "lchown() failed for " << path;
+        return false;
+    }
+
+    /* chown may have cleared S_ISUID and S_ISGID, chmod again */
+    if (mode & (S_ISUID | S_ISGID)) {
+        if (fchmodat(AT_FDCWD, path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+            PLOG(ERROR) << "fchmodat() failed for " << path;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
+    std::vector<CgroupDescriptor> result;
+    std::string json_doc;
+
+    if (!android::base::ReadFileToString(CGROUPS_DESC_FILE, &json_doc)) {
+        LOG(ERROR) << "Failed to read task profiles from " << CGROUPS_DESC_FILE;
+        return false;
+    }
+
+    Json::Reader reader;
+    Json::Value root;
+    if (!reader.parse(json_doc, root)) {
+        LOG(ERROR) << "Failed to parse cgroups description: " << reader.getFormattedErrorMessages();
+        return false;
+    }
+
+    Json::Value cgroups = root["Cgroups"];
+    for (Json::Value::ArrayIndex i = 0; i < cgroups.size(); ++i) {
+        std::string name = cgroups[i]["Controller"].asString();
+        descriptors->emplace(std::make_pair(
+                name,
+                CgroupDescriptor(1, name, cgroups[i]["Path"].asString(), cgroups[i]["Mode"].asInt(),
+                                 cgroups[i]["UID"].asString(), cgroups[i]["GID"].asString())));
+    }
+
+    Json::Value cgroups2 = root["Cgroups2"];
+    descriptors->emplace(std::make_pair(
+            CGROUPV2_CONTROLLER_NAME,
+            CgroupDescriptor(2, CGROUPV2_CONTROLLER_NAME, cgroups2["Path"].asString(),
+                             cgroups2["Mode"].asInt(), cgroups2["UID"].asString(),
+                             cgroups2["GID"].asString())));
+
+    return true;
+}
+
+// To avoid issues in sdk_mac build
+#if defined(__ANDROID__)
+
+static bool SetupCgroup(const CgroupDescriptor& descriptor) {
+    const CgroupController* controller = descriptor.controller();
+
+    // mkdir <path> [mode] [owner] [group]
+    if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
+        PLOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
+        return false;
+    }
+
+    int result;
+    if (controller->version() == 2) {
+        result = mount("none", controller->path(), "cgroup2", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                       nullptr);
+    } else {
+        // Unfortunately historically cpuset controller was mounted using a mount command
+        // different from all other controllers. This results in controller attributes not
+        // to be prepended with controller name. For example this way instead of
+        // /dev/cpuset/cpuset.cpus the attribute becomes /dev/cpuset/cpus which is what
+        // the system currently expects.
+        if (!strcmp(controller->name(), "cpuset")) {
+            // mount cpuset none /dev/cpuset nodev noexec nosuid
+            result = mount("none", controller->path(), controller->name(),
+                           MS_NODEV | MS_NOEXEC | MS_NOSUID, nullptr);
+        } else {
+            // mount cgroup none <path> nodev noexec nosuid <controller>
+            result = mount("none", controller->path(), "cgroup", MS_NODEV | MS_NOEXEC | MS_NOSUID,
+                           controller->name());
+        }
+    }
+
+    if (result < 0) {
+        PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+        return false;
+    }
+
+    return true;
+}
+
+#else
+
+// Stubs for non-Android targets.
+static bool SetupCgroup(const CgroupDescriptor&) {
+    return false;
+}
+
+#endif
+
+static bool WriteRcFile(const std::map<std::string, CgroupDescriptor>& descriptors) {
+    std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CgroupMap::CGROUPS_RC_FILE);
+    unique_fd fd(TEMP_FAILURE_RETRY(open(cgroup_rc_path.c_str(),
+                                         O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
+                                         S_IRUSR | S_IRGRP | S_IROTH)));
+    if (fd < 0) {
+        PLOG(ERROR) << "open() failed for " << cgroup_rc_path;
+        return false;
+    }
+
+    CgroupFile fl;
+    fl.version_ = CgroupFile::FILE_CURR_VERSION;
+    fl.controller_count_ = descriptors.size();
+    int ret = TEMP_FAILURE_RETRY(write(fd, &fl, sizeof(fl)));
+    if (ret < 0) {
+        PLOG(ERROR) << "write() failed for " << cgroup_rc_path;
+        return false;
+    }
+
+    for (const auto& [name, descriptor] : descriptors) {
+        ret = TEMP_FAILURE_RETRY(write(fd, descriptor.controller(), sizeof(CgroupController)));
+        if (ret < 0) {
+            PLOG(ERROR) << "write() failed for " << cgroup_rc_path;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+CgroupController::CgroupController(uint32_t version, const std::string& name,
+                                   const std::string& path) {
+    version_ = version;
+    strncpy(name_, name.c_str(), sizeof(name_) - 1);
+    name_[sizeof(name_) - 1] = '\0';
+    strncpy(path_, path.c_str(), sizeof(path_) - 1);
+    path_[sizeof(path_) - 1] = '\0';
+}
+
+std::string CgroupController::GetTasksFilePath(const std::string& path) const {
+    std::string tasks_path = path_;
+
+    if (!path.empty()) {
+        tasks_path += "/" + path;
+    }
+    return (version_ == 1) ? tasks_path + CGROUP_TASKS_FILE : tasks_path + CGROUP_TASKS_FILE_V2;
+}
+
+std::string CgroupController::GetProcsFilePath(const std::string& path, uid_t uid,
+                                               pid_t pid) const {
+    std::string proc_path(path_);
+    proc_path.append("/").append(path);
+    proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
+    proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
+
+    return proc_path.append(CGROUP_PROCS_FILE);
+}
+
+bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
+    std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
+    std::string content;
+    if (!android::base::ReadFileToString(file_name, &content)) {
+        LOG(ERROR) << "Failed to read " << file_name;
+        return false;
+    }
+
+    // if group is null and tid exists return early because
+    // user is not interested in cgroup membership
+    if (group == nullptr) {
+        return true;
+    }
+
+    std::string cg_tag = StringPrintf(":%s:", name_);
+    size_t start_pos = content.find(cg_tag);
+    if (start_pos == std::string::npos) {
+        return false;
+    }
+
+    start_pos += cg_tag.length() + 1;  // skip '/'
+    size_t end_pos = content.find('\n', start_pos);
+    if (end_pos == std::string::npos) {
+        *group = content.substr(start_pos, std::string::npos);
+    } else {
+        *group = content.substr(start_pos, end_pos - start_pos);
+    }
+
+    return true;
+}
+
+CgroupDescriptor::CgroupDescriptor(uint32_t version, const std::string& name,
+                                   const std::string& path, mode_t mode, const std::string& uid,
+                                   const std::string& gid)
+    : controller_(version, name, path), mode_(mode), uid_(uid), gid_(gid) {}
+
+CgroupMap::CgroupMap() : cg_file_data_(nullptr), cg_file_size_(0) {
+    if (!LoadRcFile()) {
+        PLOG(ERROR) << "CgroupMap::LoadRcFile called for [" << getpid() << "] failed";
+    }
+}
+
+CgroupMap::~CgroupMap() {
+    if (cg_file_data_) {
+        munmap(cg_file_data_, cg_file_size_);
+        cg_file_data_ = nullptr;
+        cg_file_size_ = 0;
+    }
+}
+
+CgroupMap& CgroupMap::GetInstance() {
+    static CgroupMap instance;
+    return instance;
+}
+
+bool CgroupMap::LoadRcFile() {
+    struct stat sb;
+
+    if (cg_file_data_) {
+        // Data already initialized
+        return true;
+    }
+
+    std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CGROUPS_RC_FILE);
+    unique_fd fd(TEMP_FAILURE_RETRY(open(cgroup_rc_path.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        PLOG(ERROR) << "open() failed for " << cgroup_rc_path;
+        return false;
+    }
+
+    if (fstat(fd, &sb) < 0) {
+        PLOG(ERROR) << "fstat() failed for " << cgroup_rc_path;
+        return false;
+    }
+
+    cg_file_size_ = sb.st_size;
+    if (cg_file_size_ < sizeof(CgroupFile)) {
+        PLOG(ERROR) << "Invalid file format " << cgroup_rc_path;
+        return false;
+    }
+
+    cg_file_data_ = (CgroupFile*)mmap(nullptr, cg_file_size_, PROT_READ, MAP_SHARED, fd, 0);
+    if (cg_file_data_ == MAP_FAILED) {
+        PLOG(ERROR) << "Failed to mmap " << cgroup_rc_path;
+        return false;
+    }
+
+    if (cg_file_data_->version_ != CgroupFile::FILE_CURR_VERSION) {
+        PLOG(ERROR) << cgroup_rc_path << " file version mismatch";
+        return false;
+    }
+
+    return true;
+}
+
+void CgroupMap::Print() {
+    LOG(INFO) << "File version = " << cg_file_data_->version_;
+    LOG(INFO) << "File controller count = " << cg_file_data_->controller_count_;
+
+    LOG(INFO) << "Mounted cgroups:";
+    CgroupController* controller = (CgroupController*)(cg_file_data_ + 1);
+    for (int i = 0; i < cg_file_data_->controller_count_; i++, controller++) {
+        LOG(INFO) << "\t" << controller->name() << " ver " << controller->version() << " path "
+                  << controller->path();
+    }
+}
+
+bool CgroupMap::SetupCgroups() {
+    std::map<std::string, CgroupDescriptor> descriptors;
+
+    // load cgroups.json file
+    if (!ReadDescriptors(&descriptors)) {
+        PLOG(ERROR) << "Failed to load cgroup description file";
+        return false;
+    }
+
+    // setup cgroups
+    for (const auto& [name, descriptor] : descriptors) {
+        if (!SetupCgroup(descriptor)) {
+            // issue a warning and proceed with the next cgroup
+            // TODO: mark the descriptor as invalid and skip it in WriteRcFile()
+            LOG(WARNING) << "Failed to setup " << name << " cgroup";
+        }
+    }
+
+    // mkdir <CGROUPS_RC_DIR> 0711 system system
+    if (!Mkdir(CGROUPS_RC_DIR, 0711, "system", "system")) {
+        PLOG(ERROR) << "Failed to create directory for <CGROUPS_RC_FILE> file";
+        return false;
+    }
+
+    // Generate <CGROUPS_RC_FILE> file which can be directly mmapped into
+    // process memory. This optimizes performance, memory usage
+    // and limits infrormation shared with unprivileged processes
+    // to the minimum subset of information from cgroups.json
+    if (!WriteRcFile(descriptors)) {
+        LOG(ERROR) << "Failed to write " << CGROUPS_RC_FILE << " file";
+        return false;
+    }
+
+    std::string cgroup_rc_path = StringPrintf("%s/%s", CGROUPS_RC_DIR, CGROUPS_RC_FILE);
+    // chmod 0644 <cgroup_rc_path>
+    if (fchmodat(AT_FDCWD, cgroup_rc_path.c_str(), 0644, AT_SYMLINK_NOFOLLOW) < 0) {
+        LOG(ERROR) << "fchmodat() failed";
+        return false;
+    }
+
+    return true;
+}
+
+const CgroupController* CgroupMap::FindController(const std::string& name) const {
+    if (!cg_file_data_) {
+        return nullptr;
+    }
+
+    // skip the file header to get to the first controller
+    CgroupController* controller = (CgroupController*)(cg_file_data_ + 1);
+    for (int i = 0; i < cg_file_data_->controller_count_; i++, controller++) {
+        if (name == controller->name()) {
+            return controller;
+        }
+    }
+
+    return nullptr;
+}
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
new file mode 100644
index 0000000..ba2caf7
--- /dev/null
+++ b/libprocessgroup/cgroup_map.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#include <map>
+#include <mutex>
+#include <string>
+
+// Minimal controller description to be mmapped into process address space
+class CgroupController {
+  public:
+    CgroupController() {}
+    CgroupController(uint32_t version, const std::string& name, const std::string& path);
+
+    uint32_t version() const { return version_; }
+    const char* name() const { return name_; }
+    const char* path() const { return path_; }
+
+    std::string GetTasksFilePath(const std::string& path) const;
+    std::string GetProcsFilePath(const std::string& path, uid_t uid, pid_t pid) const;
+    bool GetTaskGroup(int tid, std::string* group) const;
+
+  private:
+    static constexpr size_t CGROUP_NAME_BUF_SZ = 16;
+    static constexpr size_t CGROUP_PATH_BUF_SZ = 32;
+
+    uint32_t version_;
+    char name_[CGROUP_NAME_BUF_SZ];
+    char path_[CGROUP_PATH_BUF_SZ];
+};
+
+// Complete controller description for mounting cgroups
+class CgroupDescriptor {
+  public:
+    CgroupDescriptor(uint32_t version, const std::string& name, const std::string& path,
+                     mode_t mode, const std::string& uid, const std::string& gid);
+
+    const CgroupController* controller() const { return &controller_; }
+    mode_t mode() const { return mode_; }
+    std::string uid() const { return uid_; }
+    std::string gid() const { return gid_; }
+
+  private:
+    CgroupController controller_;
+    mode_t mode_;
+    std::string uid_;
+    std::string gid_;
+};
+
+struct CgroupFile {
+    static constexpr uint32_t FILE_VERSION_1 = 1;
+    static constexpr uint32_t FILE_CURR_VERSION = FILE_VERSION_1;
+
+    uint32_t version_;
+    uint32_t controller_count_;
+    CgroupController controllers_[];
+};
+
+class CgroupMap {
+  public:
+    static constexpr const char* CGROUPS_RC_FILE = "cgroup.rc";
+
+    // Selinux policy ensures only init process can successfully use this function
+    static bool SetupCgroups();
+
+    static CgroupMap& GetInstance();
+
+    const CgroupController* FindController(const std::string& name) const;
+
+  private:
+    struct CgroupFile* cg_file_data_;
+    size_t cg_file_size_;
+
+    CgroupMap();
+    ~CgroupMap();
+
+    bool LoadRcFile();
+    void Print();
+};
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 2412f3c..6f973b8 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -14,14 +14,28 @@
  *  limitations under the License.
  */
 
-#ifndef _PROCESSGROUP_H_
-#define _PROCESSGROUP_H_
+#pragma once
 
 #include <sys/cdefs.h>
 #include <sys/types.h>
+#include <string>
+#include <vector>
 
 __BEGIN_DECLS
 
+static constexpr const char* CGROUPV2_CONTROLLER_NAME = "cgroup2";
+static constexpr const char* CGROUPS_RC_DIR = "/dev/cgroup_info";
+
+bool CgroupSetupCgroups();
+bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path);
+bool CgroupGetAttributePath(const std::string& attr_name, std::string* path);
+bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
+
+bool UsePerAppMemcg();
+
+bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles);
+bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
+
 // Return 0 and removes the cgroup if there are no longer any processes in it.
 // Returns -1 in the case of an error occurring or if there are processes still running
 // even after retrying for up to 200ms.
@@ -42,5 +56,3 @@
 void removeAllProcessGroups(void);
 
 __END_DECLS
-
-#endif
diff --git a/libprocessgroup/include/processgroup/sched_policy.h b/libprocessgroup/include/processgroup/sched_policy.h
index 79a32fd..3c498da 100644
--- a/libprocessgroup/include/processgroup/sched_policy.h
+++ b/libprocessgroup/include/processgroup/sched_policy.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -67,13 +67,13 @@
  * On platforms which support gettid(), zero tid means current thread.
  * Return value: 0 for success, or -1 for error and set errno.
  */
-extern int get_sched_policy(int tid, SchedPolicy *policy);
+extern int get_sched_policy(int tid, SchedPolicy* policy);
 
 /* Return a displayable string corresponding to policy.
  * Return value: non-NULL NUL-terminated name of unspecified length;
  * the caller is responsible for displaying the useful part of the string.
  */
-extern const char *get_sched_policy_name(SchedPolicy policy);
+extern const char* get_sched_policy_name(SchedPolicy policy);
 
 #ifdef __cplusplus
 }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 8d2ac3d..e9dec12 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -25,12 +25,12 @@
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <chrono>
+#include <map>
 #include <memory>
 #include <mutex>
 #include <set>
@@ -43,8 +43,8 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <cutils/android_filesystem_config.h>
-
 #include <processgroup/processgroup.h>
+#include <task_profiles.h>
 
 using android::base::GetBoolProperty;
 using android::base::StartsWith;
@@ -53,16 +53,103 @@
 
 using namespace std::chrono_literals;
 
-static const char kCpuacctCgroup[] = "/acct";
-static const char kMemoryCgroup[] = "/dev/memcg/apps";
-
 #define PROCESSGROUP_CGROUP_PROCS_FILE "/cgroup.procs"
 
+bool CgroupSetupCgroups() {
+    return CgroupMap::SetupCgroups();
+}
+
+bool CgroupGetControllerPath(const std::string& cgroup_name, std::string* path) {
+    const CgroupController* controller = CgroupMap::GetInstance().FindController(cgroup_name);
+
+    if (controller == nullptr) {
+        return false;
+    }
+
+    if (path) {
+        *path = controller->path();
+    }
+
+    return true;
+}
+
+bool CgroupGetAttributePath(const std::string& attr_name, std::string* path) {
+    const TaskProfiles& tp = TaskProfiles::GetInstance();
+    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+
+    if (attr == nullptr) {
+        return false;
+    }
+
+    if (path) {
+        *path = StringPrintf("%s/%s", attr->controller()->path(), attr->file_name().c_str());
+    }
+
+    return true;
+}
+
+bool CgroupGetAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
+    const TaskProfiles& tp = TaskProfiles::GetInstance();
+    const ProfileAttribute* attr = tp.GetAttribute(attr_name);
+
+    if (attr == nullptr) {
+        return false;
+    }
+
+    if (!attr->GetPathForTask(tid, path)) {
+        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        return false;
+    }
+
+    return true;
+}
+
+bool UsePerAppMemcg() {
+    bool low_ram_device = GetBoolProperty("ro.config.low_ram", false);
+    return GetBoolProperty("ro.config.per_app_memcg", low_ram_device);
+}
+
 static bool isMemoryCgroupSupported() {
-    static bool memcg_supported = !access("/dev/memcg/memory.limit_in_bytes", F_OK);
+    std::string cgroup_name;
+    static bool memcg_supported = (CgroupMap::GetInstance().FindController("memory") != nullptr);
+
     return memcg_supported;
 }
 
+bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
+    const TaskProfiles& tp = TaskProfiles::GetInstance();
+
+    for (const auto& name : profiles) {
+        const TaskProfile* profile = tp.GetProfile(name);
+        if (profile != nullptr) {
+            if (!profile->ExecuteForProcess(uid, pid)) {
+                PLOG(WARNING) << "Failed to apply " << name << " process profile";
+            }
+        } else {
+            PLOG(WARNING) << "Failed to find " << name << "process profile";
+        }
+    }
+
+    return true;
+}
+
+bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles) {
+    const TaskProfiles& tp = TaskProfiles::GetInstance();
+
+    for (const auto& name : profiles) {
+        const TaskProfile* profile = tp.GetProfile(name);
+        if (profile != nullptr) {
+            if (!profile->ExecuteForTask(tid)) {
+                PLOG(WARNING) << "Failed to apply " << name << " task profile";
+            }
+        } else {
+            PLOG(WARNING) << "Failed to find " << name << "task profile";
+        }
+    }
+
+    return true;
+}
+
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
     return StringPrintf("%s/uid_%d", cgroup, uid);
 }
@@ -103,11 +190,21 @@
     }
 }
 
-void removeAllProcessGroups()
-{
+void removeAllProcessGroups() {
     LOG(VERBOSE) << "removeAllProcessGroups()";
-    for (const char* cgroup_root_path : {kCpuacctCgroup, kMemoryCgroup}) {
-        std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path), closedir);
+
+    std::vector<std::string> cgroups;
+    std::string path;
+
+    if (CgroupGetControllerPath("cpuacct", &path)) {
+        cgroups.push_back(path);
+    }
+    if (CgroupGetControllerPath("memory", &path)) {
+        cgroups.push_back(path);
+    }
+
+    for (std::string cgroup_root_path : cgroups) {
+        std::unique_ptr<DIR, decltype(&closedir)> root(opendir(cgroup_root_path.c_str()), closedir);
         if (root == NULL) {
             PLOG(ERROR) << "Failed to open " << cgroup_root_path;
         } else {
@@ -121,7 +218,7 @@
                     continue;
                 }
 
-                auto path = StringPrintf("%s/%s", cgroup_root_path, dir->d_name);
+                auto path = StringPrintf("%s/%s", cgroup_root_path.c_str(), dir->d_name);
                 RemoveUidProcessGroups(path);
                 LOG(VERBOSE) << "Removing " << path;
                 if (rmdir(path.c_str()) == -1) PLOG(WARNING) << "Failed to remove " << path;
@@ -130,6 +227,21 @@
     }
 }
 
+static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
+    if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
+        return false;
+    }
+
+    if (chown(path.c_str(), uid, gid) == -1) {
+        int saved_errno = errno;
+        rmdir(path.c_str());
+        errno = saved_errno;
+        return false;
+    }
+
+    return true;
+}
+
 // Returns number of processes killed on success
 // Returns 0 if there are no processes in the process cgroup left to kill
 // Returns -1 on error
@@ -200,10 +312,16 @@
 }
 
 static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries) {
+    std::string cpuacct_path;
+    std::string memory_path;
+
+    CgroupGetControllerPath("cpuacct", &cpuacct_path);
+    CgroupGetControllerPath("memory", &memory_path);
+
     const char* cgroup =
-            (!access(ConvertUidPidToPath(kCpuacctCgroup, uid, initialPid).c_str(), F_OK))
-                    ? kCpuacctCgroup
-                    : kMemoryCgroup;
+            (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
+                    ? cpuacct_path.c_str()
+                    : memory_path.c_str();
 
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
@@ -258,44 +376,22 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/);
 }
 
-static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
-    if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
-        return false;
-    }
-
-    if (chown(path.c_str(), uid, gid) == -1) {
-        int saved_errno = errno;
-        rmdir(path.c_str());
-        errno = saved_errno;
-        return false;
-    }
-
-    return true;
-}
-
-static bool isPerAppMemcgEnabled() {
-    static bool per_app_memcg =
-            GetBoolProperty("ro.config.per_app_memcg", GetBoolProperty("ro.config.low_ram", false));
-    return per_app_memcg;
-}
-
-int createProcessGroup(uid_t uid, int initialPid, bool memControl)
-{
-    const char* cgroup;
-    if (isMemoryCgroupSupported() && (memControl || isPerAppMemcgEnabled())) {
-        cgroup = kMemoryCgroup;
+int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
+    std::string cgroup;
+    if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
+        CgroupGetControllerPath("memory", &cgroup);
     } else {
-        cgroup = kCpuacctCgroup;
+        CgroupGetControllerPath("cpuacct", &cgroup);
     }
 
-    auto uid_path = ConvertUidToPath(cgroup, uid);
+    auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
     if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
         PLOG(ERROR) << "Failed to make and chown " << uid_path;
         return -errno;
     }
 
-    auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, initialPid);
+    auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid);
 
     if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
         PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
@@ -313,13 +409,17 @@
     return ret;
 }
 
-static bool SetProcessGroupValue(uid_t uid, int pid, const std::string& file_name, int64_t value) {
+static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
         PLOG(ERROR) << "Memcg is not mounted.";
         return false;
     }
 
-    auto path = ConvertUidPidToPath(kMemoryCgroup, uid, pid) + file_name;
+    std::string path;
+    if (!CgroupGetAttributePathForTask(attr_name, tid, &path)) {
+        PLOG(ERROR) << "Failed to find attribute '" << attr_name << "'";
+        return false;
+    }
 
     if (!WriteStringToFile(std::to_string(value), path)) {
         PLOG(ERROR) << "Failed to write '" << value << "' to " << path;
@@ -328,14 +428,14 @@
     return true;
 }
 
-bool setProcessGroupSwappiness(uid_t uid, int pid, int swappiness) {
-    return SetProcessGroupValue(uid, pid, "/memory.swappiness", swappiness);
+bool setProcessGroupSwappiness(uid_t, int pid, int swappiness) {
+    return SetProcessGroupValue(pid, "MemSwappiness", swappiness);
 }
 
-bool setProcessGroupSoftLimit(uid_t uid, int pid, int64_t soft_limit_in_bytes) {
-    return SetProcessGroupValue(uid, pid, "/memory.soft_limit_in_bytes", soft_limit_in_bytes);
+bool setProcessGroupSoftLimit(uid_t, int pid, int64_t soft_limit_in_bytes) {
+    return SetProcessGroupValue(pid, "MemSoftLimit", soft_limit_in_bytes);
 }
 
-bool setProcessGroupLimit(uid_t uid, int pid, int64_t limit_in_bytes) {
-    return SetProcessGroupValue(uid, pid, "/memory.limit_in_bytes", limit_in_bytes);
+bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) {
+    return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes);
 }
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index f95d7e4..337b032 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -1,372 +1,83 @@
 /*
-** Copyright 2007, 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.
-*/
+ * Copyright (C) 2019 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 <processgroup/sched_policy.h>
 
 #define LOG_TAG "SchedPolicy"
 
 #include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 #include <unistd.h>
 
-#include <android-base/macros.h>
-#include <log/log.h>
+#include <android-base/logging.h>
+#include <android-base/threads.h>
+#include <cgroup_map.h>
+#include <processgroup/processgroup.h>
+
+using android::base::GetThreadId;
 
 /* Re-map SP_DEFAULT to the system default policy, and leave other values unchanged.
  * Call this any place a SchedPolicy is used as an input parameter.
  * Returns the possibly re-mapped policy.
  */
-static inline SchedPolicy _policy(SchedPolicy p)
-{
-   return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
+static inline SchedPolicy _policy(SchedPolicy p) {
+    return p == SP_DEFAULT ? SP_SYSTEM_DEFAULT : p;
 }
 
 #if defined(__ANDROID__)
 
-#include <pthread.h>
-#include <sched.h>
-#include <sys/prctl.h>
-
-#define POLICY_DEBUG 0
-
-// timer slack value in nS enforced when the thread moves to background
-#define TIMER_SLACK_BG 40000000
-#define TIMER_SLACK_FG 50000
-
-static pthread_once_t the_once = PTHREAD_ONCE_INIT;
-
-static int __sys_supports_timerslack = -1;
-
-// File descriptors open to /dev/cpuset/../tasks, setup by initialize, or -1 on error
-static int system_bg_cpuset_fd = -1;
-static int bg_cpuset_fd = -1;
-static int fg_cpuset_fd = -1;
-static int ta_cpuset_fd = -1; // special cpuset for top app
-static int rs_cpuset_fd = -1;  // special cpuset for screen off restrictions
-
-// File descriptors open to /dev/stune/../tasks, setup by initialize, or -1 on error
-static int bg_schedboost_fd = -1;
-static int fg_schedboost_fd = -1;
-static int ta_schedboost_fd = -1;
-static int rt_schedboost_fd = -1;
-
-/* Add tid to the scheduling group defined by the policy */
-static int add_tid_to_cgroup(int tid, int fd)
-{
-    if (fd < 0) {
-        SLOGE("add_tid_to_cgroup failed; fd=%d\n", fd);
-        errno = EINVAL;
-        return -1;
-    }
-
-    // specialized itoa -- works for tid > 0
-    char text[22];
-    char *end = text + sizeof(text) - 1;
-    char *ptr = end;
-    *ptr = '\0';
-    while (tid > 0) {
-        *--ptr = '0' + (tid % 10);
-        tid = tid / 10;
-    }
-
-    if (write(fd, ptr, end - ptr) < 0) {
-        /*
-         * If the thread is in the process of exiting,
-         * don't flag an error
-         */
-        if (errno == ESRCH)
-                return 0;
-        SLOGW("add_tid_to_cgroup failed to write '%s' (%s); fd=%d\n",
-              ptr, strerror(errno), fd);
-        errno = EINVAL;
-        return -1;
-    }
-
-    return 0;
-}
-
-/*
-    If CONFIG_CPUSETS for Linux kernel is set, "tasks" can be found under
-    /dev/cpuset mounted in init.rc; otherwise, that file does not exist
-    even though the directory, /dev/cpuset, is still created (by init.rc).
-
-    A couple of other candidates (under cpuset mount directory):
-        notify_on_release
-        release_agent
-
-    Yet another way to decide if cpuset is enabled is to parse
-    /proc/self/status and search for lines begin with "Mems_allowed".
-
-    If CONFIG_PROC_PID_CPUSET is set, the existence "/proc/self/cpuset" can
-    be used to decide if CONFIG_CPUSETS is set, so we don't have a dependency
-    on where init.rc mounts cpuset. That's why we'd better require this
-    configuration be set if CONFIG_CPUSETS is set.
-
-    In older releases, this was controlled by build-time configuration.
- */
-bool cpusets_enabled() {
-    static bool enabled = (access("/dev/cpuset/tasks", F_OK) == 0);
-
-    return enabled;
-}
-
-/*
-    Similar to CONFIG_CPUSETS above, but with a different configuration
-    CONFIG_CGROUP_SCHEDTUNE that's in Android common Linux kernel and Linaro
-    Stable Kernel (LSK), but not in mainline Linux as of v4.9.
-
-    In older releases, this was controlled by build-time configuration.
- */
-bool schedboost_enabled() {
-    static bool enabled = (access("/dev/stune/tasks", F_OK) == 0);
-
-    return enabled;
-}
-
-static void __initialize() {
-    const char* filename;
-
-    if (cpusets_enabled()) {
-        if (!access("/dev/cpuset/tasks", W_OK)) {
-
-            filename = "/dev/cpuset/foreground/tasks";
-            fg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
-            filename = "/dev/cpuset/background/tasks";
-            bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
-            filename = "/dev/cpuset/system-background/tasks";
-            system_bg_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
-            filename = "/dev/cpuset/top-app/tasks";
-            ta_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
-            filename = "/dev/cpuset/restricted/tasks";
-            rs_cpuset_fd = open(filename, O_WRONLY | O_CLOEXEC);
-
-            if (schedboost_enabled()) {
-                filename = "/dev/stune/top-app/tasks";
-                ta_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
-                filename = "/dev/stune/foreground/tasks";
-                fg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
-                filename = "/dev/stune/background/tasks";
-                bg_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
-                filename = "/dev/stune/rt/tasks";
-                rt_schedboost_fd = open(filename, O_WRONLY | O_CLOEXEC);
-            }
-        }
-    }
-
-    char buf[64];
-    snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", getpid());
-    __sys_supports_timerslack = !access(buf, W_OK);
-}
-
-/*
- * Returns the path under the requested cgroup subsystem (if it exists)
- *
- * The data from /proc/<pid>/cgroup looks (something) like:
- *  2:cpu:/bg_non_interactive
- *  1:cpuacct:/
- *
- * We return the part after the "/", which will be an empty string for
- * the default cgroup.  If the string is longer than "bufLen", the string
- * will be truncated.
- */
-static int getCGroupSubsys(int tid, const char* subsys, char* buf, size_t bufLen)
-{
-#if defined(__ANDROID__)
-    char pathBuf[32];
-    char lineBuf[256];
-    FILE *fp;
-
-    snprintf(pathBuf, sizeof(pathBuf), "/proc/%d/cgroup", tid);
-    if (!(fp = fopen(pathBuf, "re"))) {
-        return -1;
-    }
-
-    while(fgets(lineBuf, sizeof(lineBuf) -1, fp)) {
-        char *next = lineBuf;
-        char *found_subsys;
-        char *grp;
-        size_t len;
-
-        /* Junk the first field */
-        if (!strsep(&next, ":")) {
-            goto out_bad_data;
-        }
-
-        if (!(found_subsys = strsep(&next, ":"))) {
-            goto out_bad_data;
-        }
-
-        if (strcmp(found_subsys, subsys)) {
-            /* Not the subsys we're looking for */
-            continue;
-        }
-
-        if (!(grp = strsep(&next, ":"))) {
-            goto out_bad_data;
-        }
-        grp++; /* Drop the leading '/' */
-        len = strlen(grp);
-        grp[len-1] = '\0'; /* Drop the trailing '\n' */
-
-        if (bufLen <= len) {
-            len = bufLen - 1;
-        }
-        strncpy(buf, grp, len);
-        buf[len] = '\0';
-        fclose(fp);
-        return 0;
-    }
-
-    SLOGE("Failed to find subsys %s", subsys);
-    fclose(fp);
-    return -1;
- out_bad_data:
-    SLOGE("Bad cgroup data {%s}", lineBuf);
-    fclose(fp);
-    return -1;
-#else
-    errno = ENOSYS;
-    return -1;
-#endif
-}
-
-int get_sched_policy(int tid, SchedPolicy *policy)
-{
+int set_cpuset_policy(int tid, SchedPolicy policy) {
     if (tid == 0) {
-        tid = gettid();
-    }
-    pthread_once(&the_once, __initialize);
-
-    char grpBuf[32];
-
-    grpBuf[0] = '\0';
-    if (schedboost_enabled()) {
-        if (getCGroupSubsys(tid, "schedtune", grpBuf, sizeof(grpBuf)) < 0) return -1;
-    }
-    if ((grpBuf[0] == '\0') && cpusets_enabled()) {
-        if (getCGroupSubsys(tid, "cpuset", grpBuf, sizeof(grpBuf)) < 0) return -1;
-    }
-    if (grpBuf[0] == '\0') {
-        *policy = SP_FOREGROUND;
-    } else if (!strcmp(grpBuf, "foreground")) {
-        *policy = SP_FOREGROUND;
-    } else if (!strcmp(grpBuf, "system-background")) {
-        *policy = SP_SYSTEM;
-    } else if (!strcmp(grpBuf, "background")) {
-        *policy = SP_BACKGROUND;
-    } else if (!strcmp(grpBuf, "top-app")) {
-        *policy = SP_TOP_APP;
-    } else {
-        errno = ERANGE;
-        return -1;
-    }
-    return 0;
-}
-
-int set_cpuset_policy(int tid, SchedPolicy policy)
-{
-    // in the absence of cpusets, use the old sched policy
-    if (!cpusets_enabled()) {
-        return set_sched_policy(tid, policy);
-    }
-
-    if (tid == 0) {
-        tid = gettid();
+        tid = GetThreadId();
     }
     policy = _policy(policy);
-    pthread_once(&the_once, __initialize);
 
-    int fd = -1;
-    int boost_fd = -1;
     switch (policy) {
-    case SP_BACKGROUND:
-        fd = bg_cpuset_fd;
-        boost_fd = bg_schedboost_fd;
-        break;
-    case SP_FOREGROUND:
-    case SP_AUDIO_APP:
-    case SP_AUDIO_SYS:
-        fd = fg_cpuset_fd;
-        boost_fd = fg_schedboost_fd;
-        break;
-    case SP_TOP_APP :
-        fd = ta_cpuset_fd;
-        boost_fd = ta_schedboost_fd;
-        break;
-    case SP_SYSTEM:
-        fd = system_bg_cpuset_fd;
-        break;
-    case SP_RESTRICTED:
-        fd = rs_cpuset_fd;
-        break;
-    default:
-        boost_fd = fd = -1;
-        break;
-    }
-
-    if (add_tid_to_cgroup(tid, fd) != 0) {
-        if (errno != ESRCH && errno != ENOENT)
-            return -errno;
-    }
-
-    if (schedboost_enabled()) {
-        if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
-            if (errno != ESRCH && errno != ENOENT)
-                return -errno;
-        }
+        case SP_BACKGROUND:
+            return SetTaskProfiles(tid,
+                                   {"HighEnergySaving", "ProcessCapacityLow", "TimerSlackHigh"})
+                           ? 0
+                           : -1;
+        case SP_FOREGROUND:
+        case SP_AUDIO_APP:
+        case SP_AUDIO_SYS:
+            return SetTaskProfiles(tid,
+                                   {"HighPerformance", "ProcessCapacityHigh", "TimerSlackNormal"})
+                           ? 0
+                           : -1;
+        case SP_TOP_APP:
+            return SetTaskProfiles(tid,
+                                   {"MaxPerformance", "ProcessCapacityMax", "TimerSlackNormal"})
+                           ? 0
+                           : -1;
+        case SP_SYSTEM:
+            return SetTaskProfiles(tid, {"ServiceCapacityLow", "TimerSlackNormal"}) ? 0 : -1;
+        case SP_RESTRICTED:
+            return SetTaskProfiles(tid, {"ServiceCapacityRestricted", "TimerSlackNormal"}) ? 0 : -1;
+        default:
+            break;
     }
 
     return 0;
 }
 
-static void set_timerslack_ns(int tid, unsigned long slack) {
-    // v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
-    // TODO: once we've backported this, log if the open(2) fails.
-    if (__sys_supports_timerslack) {
-        char buf[64];
-        snprintf(buf, sizeof(buf), "/proc/%d/timerslack_ns", tid);
-        int fd = open(buf, O_WRONLY | O_CLOEXEC);
-        if (fd != -1) {
-            int len = snprintf(buf, sizeof(buf), "%lu", slack);
-            if (write(fd, buf, len) != len) {
-                SLOGE("set_timerslack_ns write failed: %s\n", strerror(errno));
-            }
-            close(fd);
-            return;
-        }
-    }
-
-    // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
-    if ((tid == 0) || (tid == gettid())) {
-        if (prctl(PR_SET_TIMERSLACK, slack) == -1) {
-            SLOGE("set_timerslack_ns prctl failed: %s\n", strerror(errno));
-        }
-    }
-}
-
-int set_sched_policy(int tid, SchedPolicy policy)
-{
+int set_sched_policy(int tid, SchedPolicy policy) {
     if (tid == 0) {
-        tid = gettid();
+        tid = GetThreadId();
     }
     policy = _policy(policy);
-    pthread_once(&the_once, __initialize);
 
 #if POLICY_DEBUG
     char statfile[64];
@@ -376,73 +87,113 @@
     snprintf(statfile, sizeof(statfile), "/proc/%d/stat", tid);
     memset(thread_name, 0, sizeof(thread_name));
 
-    int fd = open(statfile, O_RDONLY | O_CLOEXEC);
+    unique_fd fd(TEMP_FAILURE_RETRY(open(statfile, O_RDONLY | O_CLOEXEC)));
     if (fd >= 0) {
         int rc = read(fd, statline, 1023);
-        close(fd);
         statline[rc] = 0;
-        char *p = statline;
-        char *q;
+        char* p = statline;
+        char* q;
 
-        for (p = statline; *p != '('; p++);
+        for (p = statline; *p != '('; p++)
+            ;
         p++;
-        for (q = p; *q != ')'; q++);
+        for (q = p; *q != ')'; q++)
+            ;
 
-        strncpy(thread_name, p, (q-p));
+        strncpy(thread_name, p, (q - p));
     }
     switch (policy) {
-    case SP_BACKGROUND:
-        SLOGD("vvv tid %d (%s)", tid, thread_name);
-        break;
-    case SP_FOREGROUND:
-    case SP_AUDIO_APP:
-    case SP_AUDIO_SYS:
-    case SP_TOP_APP:
-        SLOGD("^^^ tid %d (%s)", tid, thread_name);
-        break;
-    case SP_SYSTEM:
-        SLOGD("/// tid %d (%s)", tid, thread_name);
-        break;
-    case SP_RT_APP:
-	SLOGD("RT  tid %d (%s)", tid, thread_name);
-	break;
-    default:
-        SLOGD("??? tid %d (%s)", tid, thread_name);
-        break;
-    }
-#endif
-
-    if (schedboost_enabled()) {
-        int boost_fd = -1;
-        switch (policy) {
         case SP_BACKGROUND:
-            boost_fd = bg_schedboost_fd;
+            SLOGD("vvv tid %d (%s)", tid, thread_name);
             break;
         case SP_FOREGROUND:
         case SP_AUDIO_APP:
         case SP_AUDIO_SYS:
-            boost_fd = fg_schedboost_fd;
-            break;
         case SP_TOP_APP:
-            boost_fd = ta_schedboost_fd;
+            SLOGD("^^^ tid %d (%s)", tid, thread_name);
+            break;
+        case SP_SYSTEM:
+            SLOGD("/// tid %d (%s)", tid, thread_name);
             break;
         case SP_RT_APP:
-	    boost_fd = rt_schedboost_fd;
-	    break;
-        default:
-            boost_fd = -1;
+            SLOGD("RT  tid %d (%s)", tid, thread_name);
             break;
-        }
+        default:
+            SLOGD("??? tid %d (%s)", tid, thread_name);
+            break;
+    }
+#endif
 
-        if (boost_fd > 0 && add_tid_to_cgroup(tid, boost_fd) != 0) {
-            if (errno != ESRCH && errno != ENOENT)
-                return -errno;
-        }
-
+    switch (policy) {
+        case SP_BACKGROUND:
+            return SetTaskProfiles(tid, {"HighEnergySaving", "TimerSlackHigh"}) ? 0 : -1;
+        case SP_FOREGROUND:
+        case SP_AUDIO_APP:
+        case SP_AUDIO_SYS:
+            return SetTaskProfiles(tid, {"HighPerformance", "TimerSlackNormal"}) ? 0 : -1;
+        case SP_TOP_APP:
+            return SetTaskProfiles(tid, {"MaxPerformance", "TimerSlackNormal"}) ? 0 : -1;
+        case SP_RT_APP:
+            return SetTaskProfiles(tid, {"RealtimePerformance", "TimerSlackNormal"}) ? 0 : -1;
+        default:
+            return SetTaskProfiles(tid, {"TimerSlackNormal"}) ? 0 : -1;
     }
 
-    set_timerslack_ns(tid, policy == SP_BACKGROUND ? TIMER_SLACK_BG : TIMER_SLACK_FG);
+    return 0;
+}
 
+bool cpusets_enabled() {
+    static bool enabled = (CgroupMap::GetInstance().FindController("cpuset") != nullptr);
+    return enabled;
+}
+
+bool schedboost_enabled() {
+    static bool enabled = (CgroupMap::GetInstance().FindController("schedtune") != nullptr);
+    return enabled;
+}
+
+static int getCGroupSubsys(int tid, const char* subsys, std::string& subgroup) {
+    const CgroupController* controller = CgroupMap::GetInstance().FindController(subsys);
+
+    if (!controller) return -1;
+
+    if (!controller->GetTaskGroup(tid, &subgroup)) {
+        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        return -1;
+    }
+    return 0;
+}
+
+int get_sched_policy(int tid, SchedPolicy* policy) {
+    if (tid == 0) {
+        tid = GetThreadId();
+    }
+
+    std::string group;
+    if (schedboost_enabled()) {
+        if (getCGroupSubsys(tid, "schedtune", group) < 0) return -1;
+    }
+    if (group.empty() && cpusets_enabled()) {
+        if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
+    }
+
+    // TODO: replace hardcoded directories
+    if (group.empty()) {
+        *policy = SP_FOREGROUND;
+    } else if (group == "foreground") {
+        *policy = SP_FOREGROUND;
+    } else if (group == "system-background") {
+        *policy = SP_SYSTEM;
+    } else if (group == "background") {
+        *policy = SP_BACKGROUND;
+    } else if (group == "top-app") {
+        *policy = SP_TOP_APP;
+    } else if (group == "restricted") {
+        *policy = SP_RESTRICTED;
+    } else {
+        errno = ERANGE;
+        return -1;
+    }
     return 0;
 }
 
@@ -450,11 +201,11 @@
 
 /* Stubs for non-Android targets. */
 
-int set_sched_policy(int /*tid*/, SchedPolicy /*policy*/) {
+int set_sched_policy(int, SchedPolicy) {
     return 0;
 }
 
-int get_sched_policy(int /*tid*/, SchedPolicy* policy) {
+int get_sched_policy(int, SchedPolicy* policy) {
     *policy = SP_SYSTEM_DEFAULT;
     return 0;
 }
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
new file mode 100644
index 0000000..447852d
--- /dev/null
+++ b/libprocessgroup/task_profiles.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 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_NDEBUG 0
+#define LOG_TAG "libprocessgroup"
+
+#include <fcntl.h>
+#include <task_profiles.h>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/threads.h>
+
+#include <cutils/android_filesystem_config.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+// To avoid issues in sdk_mac build
+#if defined(__ANDROID__)
+#include <sys/prctl.h>
+#endif
+
+using android::base::GetThreadId;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::base::WriteStringToFile;
+
+#define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
+
+bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
+    std::string subgroup;
+    if (!controller_->GetTaskGroup(tid, &subgroup)) {
+        return false;
+    }
+
+    if (path == nullptr) {
+        return true;
+    }
+
+    if (subgroup.empty()) {
+        *path = StringPrintf("%s/%s", controller_->path(), file_name_.c_str());
+    } else {
+        *path = StringPrintf("%s/%s/%s", controller_->path(), subgroup.c_str(), file_name_.c_str());
+    }
+    return true;
+}
+
+bool SetClampsAction::ExecuteForProcess(uid_t, pid_t) const {
+    // TODO: add support when kernel supports util_clamp
+    LOG(WARNING) << "SetClampsAction::ExecuteForProcess is not supported";
+    return false;
+}
+
+bool SetClampsAction::ExecuteForTask(int) const {
+    // TODO: add support when kernel supports util_clamp
+    LOG(WARNING) << "SetClampsAction::ExecuteForTask is not supported";
+    return false;
+}
+
+// To avoid issues in sdk_mac build
+#if defined(__ANDROID__)
+
+bool SetTimerSlackAction::IsTimerSlackSupported(int tid) {
+    auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
+
+    return (access(file.c_str(), W_OK) == 0);
+}
+
+bool SetTimerSlackAction::ExecuteForTask(int tid) const {
+    static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
+
+    // v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
+    // TODO: once we've backported this, log if the open(2) fails.
+    if (sys_supports_timerslack) {
+        auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
+        if (!WriteStringToFile(std::to_string(slack_), file)) {
+            PLOG(ERROR) << "set_timerslack_ns write failed";
+        }
+    }
+
+    // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
+    if (tid == 0 || tid == GetThreadId()) {
+        if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
+            PLOG(ERROR) << "set_timerslack_ns prctl failed";
+        }
+    }
+
+    return true;
+}
+
+#endif
+
+bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
+    return ExecuteForTask(pid);
+}
+
+bool SetAttributeAction::ExecuteForTask(int tid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForTask(tid, &path)) {
+        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        return false;
+    }
+
+    if (!WriteStringToFile(value_, path)) {
+        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
+        return false;
+    }
+
+    return true;
+}
+
+bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
+    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
+SetCgroupAction::SetCgroupAction(const CgroupController* c, const std::string& p)
+    : controller_(c), path_(p) {
+#ifdef CACHE_FILE_DESCRIPTORS
+    // cache file descriptor only if path is app independent
+    if (IsAppDependentPath(path_)) {
+        // file descriptor is not cached
+        fd_.reset(-2);
+        return;
+    }
+
+    std::string tasks_path = c->GetTasksFilePath(p.c_str());
+
+    if (access(tasks_path.c_str(), W_OK) != 0) {
+        // file is not accessible
+        fd_.reset(-1);
+        return;
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        PLOG(ERROR) << "Failed to cache fd '" << tasks_path << "'";
+        fd_.reset(-1);
+        return;
+    }
+
+    fd_ = std::move(fd);
+#endif
+}
+
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+    if (tid <= 0) {
+        return true;
+    }
+
+    std::string value = std::to_string(tid);
+
+    if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
+        // If the thread is in the process of exiting, don't flag an error
+        if (errno != ESRCH) {
+            PLOG(ERROR) << "JoinGroup failed to write '" << value << "'; fd=" << fd;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+#ifdef CACHE_FILE_DESCRIPTORS
+    if (fd_ >= 0) {
+        // fd is cached, reuse it
+        if (!AddTidToCgroup(pid, fd_)) {
+            PLOG(ERROR) << "Failed to add task into cgroup";
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == -1) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    // this is app-dependent path, file descriptor is not cached
+    std::string procs_path = controller_->GetProcsFilePath(path_.c_str(), uid, pid);
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (tmp_fd < 0) {
+        PLOG(WARNING) << "Failed to open " << procs_path << ": " << strerror(errno);
+        return false;
+    }
+    if (!AddTidToCgroup(pid, tmp_fd)) {
+        PLOG(ERROR) << "Failed to add task into cgroup";
+        return false;
+    }
+
+    return true;
+#else
+    std::string procs_path = controller_->GetProcsFilePath(path_.c_str(), uid, pid);
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (tmp_fd < 0) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+    if (!AddTidToCgroup(pid, tmp_fd)) {
+        PLOG(ERROR) << "Failed to add task into cgroup";
+        return false;
+    }
+
+    return true;
+#endif
+}
+
+bool SetCgroupAction::ExecuteForTask(int tid) const {
+#ifdef CACHE_FILE_DESCRIPTORS
+    if (fd_ >= 0) {
+        // fd is cached, reuse it
+        if (!AddTidToCgroup(tid, fd_)) {
+            PLOG(ERROR) << "Failed to add task into cgroup";
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == -1) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    // application-dependent path can't be used with tid
+    PLOG(ERROR) << "Application profile can't be applied to a thread";
+    return false;
+#else
+    std::string tasks_path = controller_->GetTasksFilePath(path_.c_str());
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (tmp_fd < 0) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+    if (!AddTidToCgroup(tid, tmp_fd)) {
+        PLOG(ERROR) << "Failed to add task into cgroup";
+        return false;
+    }
+
+    return true;
+#endif
+}
+
+bool TaskProfile::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForProcess(uid, pid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool TaskProfile::ExecuteForTask(int tid) const {
+    if (tid == 0) {
+        tid = GetThreadId();
+    }
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForTask(tid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+TaskProfiles& TaskProfiles::GetInstance() {
+    static TaskProfiles instance;
+    return instance;
+}
+
+TaskProfiles::TaskProfiles() {
+    if (!Load(CgroupMap::GetInstance())) {
+        LOG(ERROR) << "TaskProfiles::Load for [" << getpid() << "] failed";
+    }
+}
+
+bool TaskProfiles::Load(const CgroupMap& cg_map) {
+    std::string json_doc;
+
+    if (!android::base::ReadFileToString(TASK_PROFILE_DB_FILE, &json_doc)) {
+        LOG(ERROR) << "Failed to read task profiles from " << TASK_PROFILE_DB_FILE;
+        return false;
+    }
+
+    Json::Reader reader;
+    Json::Value root;
+    if (!reader.parse(json_doc, root)) {
+        LOG(ERROR) << "Failed to parse task profiles: " << reader.getFormattedErrorMessages();
+        return false;
+    }
+
+    Json::Value attr = root["Attributes"];
+    for (Json::Value::ArrayIndex i = 0; i < attr.size(); ++i) {
+        std::string name = attr[i]["Name"].asString();
+        std::string ctrlName = attr[i]["Controller"].asString();
+        std::string file_name = attr[i]["File"].asString();
+
+        if (attributes_.find(name) == attributes_.end()) {
+            const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
+            if (controller) {
+                attributes_[name] = std::make_unique<ProfileAttribute>(controller, file_name);
+            } else {
+                LOG(WARNING) << "Controller " << ctrlName << " is not found";
+            }
+        } else {
+            LOG(WARNING) << "Attribute " << name << " is already defined";
+        }
+    }
+
+    std::map<std::string, std::string> params;
+
+    Json::Value profilesVal = root["Profiles"];
+    for (Json::Value::ArrayIndex i = 0; i < profilesVal.size(); ++i) {
+        Json::Value profileVal = profilesVal[i];
+
+        std::string profileName = profileVal["Name"].asString();
+        Json::Value actions = profileVal["Actions"];
+        auto profile = std::make_unique<TaskProfile>();
+
+        for (Json::Value::ArrayIndex actIdx = 0; actIdx < actions.size(); ++actIdx) {
+            Json::Value actionVal = actions[actIdx];
+            std::string actionName = actionVal["Name"].asString();
+            Json::Value paramsVal = actionVal["Params"];
+            if (actionName == "JoinCgroup") {
+                std::string ctrlName = paramsVal["Controller"].asString();
+                std::string path = paramsVal["Path"].asString();
+
+                const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
+                if (controller) {
+                    profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                } else {
+                    LOG(WARNING) << "JoinCgroup: controller " << ctrlName << " is not found";
+                }
+            } else if (actionName == "SetTimerSlack") {
+                std::string slackValue = paramsVal["Slack"].asString();
+                char* end;
+                unsigned long slack;
+
+                slack = strtoul(slackValue.c_str(), &end, 10);
+                if (end > slackValue.c_str()) {
+                    profile->Add(std::make_unique<SetTimerSlackAction>(slack));
+                } else {
+                    LOG(WARNING) << "SetTimerSlack: invalid parameter: " << slackValue;
+                }
+            } else if (actionName == "SetAttribute") {
+                std::string attrName = paramsVal["Name"].asString();
+                std::string attrValue = paramsVal["Value"].asString();
+
+                auto iter = attributes_.find(attrName);
+                if (iter != attributes_.end()) {
+                    profile->Add(
+                            std::make_unique<SetAttributeAction>(iter->second.get(), attrValue));
+                } else {
+                    LOG(WARNING) << "SetAttribute: unknown attribute: " << attrName;
+                }
+            } else if (actionName == "SetClamps") {
+                std::string boostValue = paramsVal["Boost"].asString();
+                std::string clampValue = paramsVal["Clamp"].asString();
+                char* end;
+                unsigned long boost;
+
+                boost = strtoul(boostValue.c_str(), &end, 10);
+                if (end > boostValue.c_str()) {
+                    unsigned long clamp = strtoul(clampValue.c_str(), &end, 10);
+                    if (end > clampValue.c_str()) {
+                        profile->Add(std::make_unique<SetClampsAction>(boost, clamp));
+                    } else {
+                        LOG(WARNING) << "SetClamps: invalid parameter " << clampValue;
+                    }
+                } else {
+                    LOG(WARNING) << "SetClamps: invalid parameter: " << boostValue;
+                }
+            } else {
+                LOG(WARNING) << "Unknown profile action: " << actionName;
+            }
+        }
+        profiles_[profileName] = std::move(profile);
+    }
+
+    return true;
+}
+
+const TaskProfile* TaskProfiles::GetProfile(const std::string& name) const {
+    auto iter = profiles_.find(name);
+
+    if (iter != profiles_.end()) {
+        return iter->second.get();
+    }
+    return nullptr;
+}
+
+const ProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
+    auto iter = attributes_.find(name);
+
+    if (iter != attributes_.end()) {
+        return iter->second.get();
+    }
+    return nullptr;
+}
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
new file mode 100644
index 0000000..b2e39f9
--- /dev/null
+++ b/libprocessgroup/task_profiles.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <cgroup_map.h>
+
+class ProfileAttribute {
+  public:
+    ProfileAttribute(const CgroupController* controller, const std::string& file_name)
+        : controller_(controller), file_name_(file_name) {}
+
+    const CgroupController* controller() const { return controller_; }
+    const std::string& file_name() const { return file_name_; }
+
+    bool GetPathForTask(int tid, std::string* path) const;
+
+  private:
+    const CgroupController* controller_;
+    std::string file_name_;
+};
+
+// Abstract profile element
+class ProfileAction {
+  public:
+    virtual ~ProfileAction() {}
+
+    // Default implementations will fail
+    virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
+    virtual bool ExecuteForTask(int) const { return false; };
+};
+
+// Profile actions
+class SetClampsAction : public ProfileAction {
+  public:
+    SetClampsAction(int boost, int clamp) noexcept : boost_(boost), clamp_(clamp) {}
+
+    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    virtual bool ExecuteForTask(int tid) const;
+
+  protected:
+    int boost_;
+    int clamp_;
+};
+
+// To avoid issues in sdk_mac build
+#if defined(__ANDROID__)
+
+class SetTimerSlackAction : public ProfileAction {
+  public:
+    SetTimerSlackAction(unsigned long slack) noexcept : slack_(slack) {}
+
+    virtual bool ExecuteForTask(int tid) const;
+
+  private:
+    unsigned long slack_;
+
+    static bool IsTimerSlackSupported(int tid);
+};
+
+#else
+
+class SetTimerSlackAction : public ProfileAction {
+  public:
+    SetTimerSlackAction(unsigned long) noexcept {}
+
+    virtual bool ExecuteForTask(int) const { return true; }
+};
+
+#endif
+
+// Set attribute profile element
+class SetAttributeAction : public ProfileAction {
+  public:
+    SetAttributeAction(const ProfileAttribute* attribute, const std::string& value)
+        : attribute_(attribute), value_(value) {}
+
+    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    virtual bool ExecuteForTask(int tid) const;
+
+  private:
+    const ProfileAttribute* attribute_;
+    std::string value_;
+};
+
+// Set cgroup profile element
+class SetCgroupAction : public ProfileAction {
+  public:
+    SetCgroupAction(const CgroupController* c, const std::string& p);
+
+    virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    virtual bool ExecuteForTask(int tid) const;
+
+    const CgroupController* controller() const { return controller_; }
+    std::string path() const { return path_; }
+
+  private:
+    const CgroupController* controller_;
+    std::string path_;
+#ifdef CACHE_FILE_DESCRIPTORS
+    android::base::unique_fd fd_;
+#endif
+
+    static bool IsAppDependentPath(const std::string& path);
+    static bool AddTidToCgroup(int tid, int fd);
+};
+
+class TaskProfile {
+  public:
+    TaskProfile() {}
+
+    void Add(std::unique_ptr<ProfileAction> e) { elements_.push_back(std::move(e)); }
+
+    bool ExecuteForProcess(uid_t uid, pid_t pid) const;
+    bool ExecuteForTask(int tid) const;
+
+  private:
+    std::vector<std::unique_ptr<ProfileAction>> elements_;
+};
+
+class TaskProfiles {
+  public:
+    // Should be used by all users
+    static TaskProfiles& GetInstance();
+
+    const TaskProfile* GetProfile(const std::string& name) const;
+    const ProfileAttribute* GetAttribute(const std::string& name) const;
+
+  private:
+    std::map<std::string, std::unique_ptr<TaskProfile>> profiles_;
+    std::map<std::string, std::unique_ptr<ProfileAttribute>> attributes_;
+
+    TaskProfiles();
+
+    bool Load(const CgroupMap& cg_map);
+};
diff --git a/lmkd/Android.bp b/lmkd/Android.bp
index f9ed57c..4c5ca03 100644
--- a/lmkd/Android.bp
+++ b/lmkd/Android.bp
@@ -6,6 +6,7 @@
         "libcutils",
         "liblog",
         "libprocessgroup",
+        "libpsi",
     ],
     static_libs: [
         "libstatslogc",
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index ca78c38..562e578 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -44,6 +44,7 @@
 #include <log/log.h>
 #include <log/log_event_list.h>
 #include <log/log_time.h>
+#include <psi/psi.h>
 #include <system/thread_defs.h>
 
 #ifdef LMKD_LOG_STATS
@@ -93,6 +94,7 @@
 #define TARGET_UPDATE_MIN_INTERVAL_MS 1000
 
 #define NS_PER_MS (NS_PER_SEC / MS_PER_SEC)
+#define US_PER_MS (US_PER_SEC / MS_PER_SEC)
 
 /* Defined as ProcessList.SYSTEM_ADJ in ProcessList.java */
 #define SYSTEM_ADJ (-900)
@@ -100,6 +102,18 @@
 #define STRINGIFY(x) STRINGIFY_INTERNAL(x)
 #define STRINGIFY_INTERNAL(x) #x
 
+/*
+ * PSI monitor tracking window size.
+ * PSI monitor generates events at most once per window,
+ * therefore we poll memory state for the duration of
+ * PSI_WINDOW_SIZE_MS after the event happens.
+ */
+#define PSI_WINDOW_SIZE_MS 1000
+/* Polling period after initial PSI signal */
+#define PSI_POLL_PERIOD_MS 200
+/* Poll for the duration of one window after initial PSI signal */
+#define PSI_POLL_COUNT (PSI_WINDOW_SIZE_MS / PSI_POLL_PERIOD_MS)
+
 #define min(a, b) (((a) < (b)) ? (a) : (b))
 
 #define FAIL_REPORT_RLIMIT_MS 1000
@@ -127,6 +141,11 @@
     int64_t max_nr_free_pages;
 } low_pressure_mem = { -1, -1 };
 
+struct psi_threshold {
+    enum psi_stall_type stall_type;
+    int threshold_ms;
+};
+
 static int level_oomadj[VMPRESS_LEVEL_COUNT];
 static int mpevfd[VMPRESS_LEVEL_COUNT] = { -1, -1, -1 };
 static bool debug_process_killing;
@@ -139,6 +158,12 @@
 static bool use_minfree_levels;
 static bool per_app_memcg;
 static int swap_free_low_percentage;
+static bool use_psi_monitors = false;
+static struct psi_threshold psi_thresholds[VMPRESS_LEVEL_COUNT] = {
+    { PSI_SOME, 70 },    /* 70ms out of 1sec for partial stall */
+    { PSI_SOME, 100 },   /* 100ms out of 1sec for partial stall */
+    { PSI_FULL, 70 },    /* 70ms out of 1sec for complete stall */
+};
 
 static android_log_context ctx;
 
@@ -1524,17 +1549,23 @@
         .fd = -1,
     };
 
-    /*
-     * Check all event counters from low to critical
-     * and upgrade to the highest priority one. By reading
-     * eventfd we also reset the event counters.
-     */
-    for (lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) {
-        if (mpevfd[lvl] != -1 &&
-            TEMP_FAILURE_RETRY(read(mpevfd[lvl],
-                               &evcount, sizeof(evcount))) > 0 &&
-            evcount > 0 && lvl > level) {
-            level = lvl;
+    if (debug_process_killing) {
+        ALOGI("%s memory pressure event is triggered", level_name[level]);
+    }
+
+    if (!use_psi_monitors) {
+        /*
+         * Check all event counters from low to critical
+         * and upgrade to the highest priority one. By reading
+         * eventfd we also reset the event counters.
+         */
+        for (lvl = VMPRESS_LEVEL_LOW; lvl < VMPRESS_LEVEL_COUNT; lvl++) {
+            if (mpevfd[lvl] != -1 &&
+                TEMP_FAILURE_RETRY(read(mpevfd[lvl],
+                                   &evcount, sizeof(evcount))) > 0 &&
+                evcount > 0 && lvl > level) {
+                level = lvl;
+            }
         }
     }
 
@@ -1722,6 +1753,54 @@
     }
 }
 
+static bool init_mp_psi(enum vmpressure_level level) {
+    int fd = init_psi_monitor(psi_thresholds[level].stall_type,
+        psi_thresholds[level].threshold_ms * US_PER_MS,
+        PSI_WINDOW_SIZE_MS * US_PER_MS);
+
+    if (fd < 0) {
+        return false;
+    }
+
+    vmpressure_hinfo[level].handler = mp_event_common;
+    vmpressure_hinfo[level].data = level;
+    if (register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level]) < 0) {
+        destroy_psi_monitor(fd);
+        return false;
+    }
+    maxevents++;
+    mpevfd[level] = fd;
+
+    return true;
+}
+
+static void destroy_mp_psi(enum vmpressure_level level) {
+    int fd = mpevfd[level];
+
+    if (unregister_psi_monitor(epollfd, fd) < 0) {
+        ALOGE("Failed to unregister psi monitor for %s memory pressure; errno=%d",
+            level_name[level], errno);
+    }
+    destroy_psi_monitor(fd);
+    mpevfd[level] = -1;
+}
+
+static bool init_psi_monitors() {
+    if (!init_mp_psi(VMPRESS_LEVEL_LOW)) {
+        return false;
+    }
+    if (!init_mp_psi(VMPRESS_LEVEL_MEDIUM)) {
+        destroy_mp_psi(VMPRESS_LEVEL_LOW);
+        return false;
+    }
+    if (!init_mp_psi(VMPRESS_LEVEL_CRITICAL)) {
+        destroy_mp_psi(VMPRESS_LEVEL_MEDIUM);
+        destroy_mp_psi(VMPRESS_LEVEL_LOW);
+        return false;
+    }
+    return true;
+}
+
 static bool init_mp_common(enum vmpressure_level level) {
     int mpfd;
     int evfd;
@@ -1837,12 +1916,22 @@
     if (use_inkernel_interface) {
         ALOGI("Using in-kernel low memory killer interface");
     } else {
-        if (!init_mp_common(VMPRESS_LEVEL_LOW) ||
+        /* Try to use psi monitor first if kernel has it */
+        use_psi_monitors = property_get_bool("ro.lmk.use_psi", true) &&
+            init_psi_monitors();
+        /* Fall back to vmpressure */
+        if (!use_psi_monitors &&
+            (!init_mp_common(VMPRESS_LEVEL_LOW) ||
             !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||
-            !init_mp_common(VMPRESS_LEVEL_CRITICAL)) {
+            !init_mp_common(VMPRESS_LEVEL_CRITICAL))) {
             ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
             return -1;
         }
+        if (use_psi_monitors) {
+            ALOGI("Using psi monitors for memory pressure detection");
+        } else {
+            ALOGI("Using vmpressure for memory pressure detection");
+        }
     }
 
     for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
@@ -1857,14 +1946,37 @@
 
 static void mainloop(void) {
     struct event_handler_info* handler_info;
+    struct event_handler_info* poll_handler = NULL;
+    struct timespec last_report_tm, curr_tm;
     struct epoll_event *evt;
+    long delay = -1;
+    int polling = 0;
 
     while (1) {
         struct epoll_event events[maxevents];
         int nevents;
         int i;
 
-        nevents = epoll_wait(epollfd, events, maxevents, -1);
+        if (polling) {
+            /* Calculate next timeout */
+            clock_gettime(CLOCK_MONOTONIC_COARSE, &curr_tm);
+            delay = get_time_diff_ms(&last_report_tm, &curr_tm);
+            delay = (delay < PSI_POLL_PERIOD_MS) ?
+                PSI_POLL_PERIOD_MS - delay : PSI_POLL_PERIOD_MS;
+
+            /* Wait for events until the next polling timeout */
+            nevents = epoll_wait(epollfd, events, maxevents, delay);
+
+            clock_gettime(CLOCK_MONOTONIC_COARSE, &curr_tm);
+            if (get_time_diff_ms(&last_report_tm, &curr_tm) >= PSI_POLL_PERIOD_MS) {
+                polling--;
+                poll_handler->handler(poll_handler->data, 0);
+                last_report_tm = curr_tm;
+            }
+        } else {
+            /* Wait for events with no timeout */
+            nevents = epoll_wait(epollfd, events, maxevents, -1);
+        }
 
         if (nevents == -1) {
             if (errno == EINTR)
@@ -1899,6 +2011,17 @@
             if (evt->data.ptr) {
                 handler_info = (struct event_handler_info*)evt->data.ptr;
                 handler_info->handler(handler_info->data, evt->events);
+
+                if (use_psi_monitors && handler_info->handler == mp_event_common) {
+                    /*
+                     * Poll for the duration of PSI_WINDOW_SIZE_MS after the
+                     * initial PSI event because psi events are rate-limited
+                     * at one per sec.
+                     */
+                    polling = PSI_POLL_COUNT;
+                    poll_handler = handler_info;
+                    clock_gettime(CLOCK_MONOTONIC_COARSE, &last_report_tm);
+                }
             }
         }
     }
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 5a6f41b..cbbc710 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -47,6 +47,28 @@
 include $(BUILD_PREBUILT)
 
 #######################################
+# cgroups.json
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := cgroups.json
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
+
+include $(BUILD_PREBUILT)
+
+#######################################
+# task_profiles.json
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := task_profiles.json
+LOCAL_SRC_FILES := $(LOCAL_MODULE)
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
+
+include $(BUILD_PREBUILT)
+
+#######################################
 # asan.options
 ifneq ($(filter address,$(SANITIZE_TARGET)),)
 
diff --git a/rootdir/cgroups.json b/rootdir/cgroups.json
new file mode 100644
index 0000000..6eb88c9
--- /dev/null
+++ b/rootdir/cgroups.json
@@ -0,0 +1,43 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "cpu",
+      "Path": "/dev/cpuctl",
+      "Mode": 0755,
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpuacct",
+      "Path": "/acct",
+      "Mode": 0555
+    },
+    {
+      "Controller": "cpuset",
+      "Path": "/dev/cpuset",
+      "Mode": 0755,
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "memory",
+      "Path": "/dev/memcg",
+      "Mode": 0700,
+      "UID": "root",
+      "GID": "system"
+    },
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": 0755,
+      "UID": "system",
+      "GID": "system"
+    }
+  ],
+  "Cgroups2": {
+    "Path": "/dev/cg2_bpf",
+    "Mode": 0600,
+    "UID": "root",
+    "GID": "root"
+  }
+}
diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt
index 9bf9058..7aa097d 100644
--- a/rootdir/etc/ld.config.txt
+++ b/rootdir/etc/ld.config.txt
@@ -24,6 +24,9 @@
 dir.system = /data/nativetest64
 dir.system = /data/benchmarktest
 dir.system = /data/benchmarktest64
+# TODO(b/123864775): Ensure tests are run from one of the directories above and
+# remove this.
+dir.system = /data/local/tmp
 
 dir.postinstall = /postinstall
 
@@ -78,9 +81,9 @@
 
 namespace.default.asan.search.paths  = /data/asan/system/${LIB}
 namespace.default.asan.search.paths +=           /system/${LIB}
-namespace.default.asan.search.paths += /data/asan/product/${LIB}
+namespace.default.asan.search.paths += /data/asan/%PRODUCT%/${LIB}
 namespace.default.asan.search.paths +=           /%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/product_services/${LIB}
+namespace.default.asan.search.paths += /data/asan/%PRODUCT_SERVICES%/${LIB}
 namespace.default.asan.search.paths +=           /%PRODUCT_SERVICES%/${LIB}
 
 namespace.default.asan.permitted.paths  = /data
diff --git a/rootdir/etc/ld.config.vndk_lite.txt b/rootdir/etc/ld.config.vndk_lite.txt
index ca180c0..1904445 100644
--- a/rootdir/etc/ld.config.vndk_lite.txt
+++ b/rootdir/etc/ld.config.vndk_lite.txt
@@ -50,9 +50,9 @@
 namespace.default.asan.search.paths +=           /odm/${LIB}
 namespace.default.asan.search.paths += /data/asan/vendor/${LIB}
 namespace.default.asan.search.paths +=           /vendor/${LIB}
-namespace.default.asan.search.paths += /data/asan/product/${LIB}
+namespace.default.asan.search.paths += /data/asan/%PRODUCT%/${LIB}
 namespace.default.asan.search.paths +=           /%PRODUCT%/${LIB}
-namespace.default.asan.search.paths += /data/asan/product_services/${LIB}
+namespace.default.asan.search.paths += /data/asan/%PRODUCT_SERVICES%/${LIB}
 namespace.default.asan.search.paths +=           /%PRODUCT_SERVICES%/${LIB}
 
 # Keep in sync with the platform namespace in the com.android.runtime APEX
diff --git a/rootdir/init.rc b/rootdir/init.rc
index f1a7984..59ed47a 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -11,6 +11,7 @@
 import /init.usb.configfs.rc
 import /init.${ro.zygote}.rc
 
+# Cgroups are mounted right before early-init using list from /etc/cgroups.json
 on early-init
     # Mount shared so changes propagate into child namespaces
     # Do this before other processes are started from init. Otherwise,
@@ -30,14 +31,8 @@
     # Set the security context of /postinstall if present.
     restorecon /postinstall
 
-    # Mount cgroup mount point for cpu accounting
-    mount cgroup none /acct nodev noexec nosuid cpuacct
-    chmod 0555 /acct
     mkdir /acct/uid
 
-    # root memory control cgroup, used by lmkd
-    mkdir /dev/memcg 0700 root system
-    mount cgroup none /dev/memcg nodev noexec nosuid memory
     # memory.pressure_level used by lmkd
     chown root system /dev/memcg/memory.pressure_level
     chmod 0040 /dev/memcg/memory.pressure_level
@@ -69,8 +64,6 @@
     symlink /system/vendor /vendor
 
     # Create energy-aware scheduler tuning nodes
-    mkdir /dev/stune
-    mount cgroup none /dev/stune nodev noexec nosuid schedtune
     mkdir /dev/stune/foreground
     mkdir /dev/stune/background
     mkdir /dev/stune/top-app
@@ -164,8 +157,6 @@
     chmod 0400 /proc/net/fib_trie
 
     # Create cgroup mount points for process groups
-    mkdir /dev/cpuctl
-    mount cgroup none /dev/cpuctl nodev noexec nosuid cpu
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/tasks
     chmod 0666 /dev/cpuctl/tasks
@@ -173,9 +164,6 @@
     write /dev/cpuctl/cpu.rt_runtime_us 950000
 
     # sets up initial cpusets for ActivityManager
-    mkdir /dev/cpuset
-    mount cpuset none /dev/cpuset nodev noexec nosuid
-
     # this ensures that the cpusets are present and usable, but the device's
     # init.rc must actually set the correct cpus
     mkdir /dev/cpuset/foreground
@@ -237,8 +225,6 @@
     # This is needed by any process that uses socket tagging.
     chmod 0644 /dev/xt_qtaguid
 
-    mkdir /dev/cg2_bpf
-    mount cgroup2 cg2_bpf /dev/cg2_bpf nodev noexec nosuid
     chown root root /dev/cg2_bpf
     chmod 0600 /dev/cg2_bpf
     mount bpf bpf /sys/fs/bpf nodev noexec nosuid
diff --git a/rootdir/task_profiles.json b/rootdir/task_profiles.json
new file mode 100644
index 0000000..5a090c5
--- /dev/null
+++ b/rootdir/task_profiles.json
@@ -0,0 +1,445 @@
+{
+  "Attributes": [
+    {
+      "Name": "LowCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "background/cpus"
+    },
+    {
+      "Name": "HighCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "foreground/cpus"
+    },
+    {
+      "Name": "MaxCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "top-app/cpus"
+    },
+
+    {
+      "Name": "MemLimit",
+      "Controller": "memory",
+      "File": "memory.limit_in_bytes"
+    },
+    {
+      "Name": "MemSoftLimit",
+      "Controller": "memory",
+      "File": "memory.soft_limit_in_bytes"
+    },
+    {
+      "Name": "MemSwappiness",
+      "Controller": "memory",
+      "File": "memory.swappiness"
+    },
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    },
+    {
+      "Name": "UClampMin",
+      "Controller": "cpu",
+      "File": "cpu.util.min"
+    },
+    {
+      "Name": "UClampMax",
+      "Controller": "cpu",
+      "File": "cpu.util.max"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions" : [
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "STunePreferIdle",
+            "Value" : "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions" : [
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "STunePreferIdle",
+            "Value" : "0"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "VrKernelCapacity",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityLow",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "system/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityNormal",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityHigh",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "system/performance"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityLow",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "application/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityNormal",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "application"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityHigh",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "application/performance"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ProcessCapacityLow",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityNormal",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityHigh",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityMax",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ServiceCapacityLow",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ServiceCapacityRestricted",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "restricted"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CameraServiceCapacity",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "cpuset",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "TimerSlackHigh",
+      "Actions" : [
+        {
+          "Name" : "SetTimerSlack",
+          "Params" :
+          {
+            "Slack": "40000000"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "TimerSlackNormal",
+      "Actions" : [
+        {
+          "Name" : "SetTimerSlack",
+          "Params" :
+          {
+            "Slack": "50000"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "PerfBoost",
+      "Actions" : [
+        {
+          "Name" : "SetClamps",
+          "Params" :
+          {
+            "Boost" : "50%",
+            "Clamp" : "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "PerfClamp",
+      "Actions" : [
+        {
+          "Name" : "SetClamps",
+          "Params" :
+          {
+            "Boost" : "0",
+            "Clamp" : "30%"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowMemoryUsage",
+      "Actions" : [
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "MemSoftLimit",
+            "Value" : "16MB"
+          }
+        },
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "MemSwappiness",
+            "Value" : "150"
+
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighMemoryUsage",
+      "Actions" : [
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "MemSoftLimit",
+            "Value" : "512MB"
+          }
+        },
+        {
+          "Name" : "SetAttribute",
+          "Params" :
+          {
+            "Name" : "MemSwappiness",
+            "Value" : "100"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "SystemMemoryProcess",
+      "Actions" : [
+        {
+          "Name" : "JoinCgroup",
+          "Params" :
+          {
+            "Controller": "memory",
+            "Path": "system"
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/rootdir/update_and_install_ld_config.mk b/rootdir/update_and_install_ld_config.mk
index 450be66..852e234 100644
--- a/rootdir/update_and_install_ld_config.mk
+++ b/rootdir/update_and_install_ld_config.mk
@@ -118,7 +118,12 @@
 	$(hide) sed -i.bak -e "s?%SANITIZER_RUNTIME_LIBRARIES%?$(PRIVATE_SANITIZER_RUNTIME_LIBRARIES)?g" $@
 	$(hide) sed -i.bak -e "s?%VNDK_VER%?$(PRIVATE_VNDK_VERSION_SUFFIX)?g" $@
 	$(hide) sed -i.bak -e "s?%PRODUCT%?$(TARGET_COPY_OUT_PRODUCT)?g" $@
+ifeq ($(TARGET_COPY_OUT_PRODUCT),$(TARGET_COPY_OUT_PRODUCT_SERVICES))
+	# Remove lines containing %PRODUCT_SERVICES% (identical to the %PRODUCT% ones)
+	$(hide) sed -i.bak -e "\?%PRODUCT_SERVICES%?d" $@
+else
 	$(hide) sed -i.bak -e "s?%PRODUCT_SERVICES%?$(TARGET_COPY_OUT_PRODUCT_SERVICES)?g" $@
+endif
 	$(hide) sed -i.bak -e "s?^$(PRIVATE_VNDK_VERSION_TAG)??g" $@
 	$(hide) sed -i.bak "/^\#VNDK[0-9]\{2\}\#.*$$/d" $@
 	$(hide) rm -f $@.bak