Merge changes I73e06f6d,I39cf223d,I93417795

* changes:
  Support transforms in file path
  Add JNI methods to support multiple nodes with same name
  Add FUSE node fields to support multiple nodes with same name
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 1b9163c..dfb989d 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -383,15 +383,12 @@
     }
 }
 
-static double get_timeout(struct fuse* fuse, const string& path, bool should_inval) {
-    string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
-    if (should_inval || path.find(media_path, 0) == 0 || is_package_owned_path(path, fuse->path)) {
+static double get_attr_timeout(const string& path, bool should_inval, node* node,
+                               struct fuse* fuse) {
+    if (should_inval || is_package_owned_path(path, fuse->path)) {
         // We set dentry timeout to 0 for the following reasons:
         // 1. Case-insensitive lookups need to invalidate other case-insensitive dentry matches
-        // 2. Installd might delete Android/media/<package> dirs when app data is cleared.
-        // This can leave a stale entry in the kernel dcache, and break subsequent creation of the
-        // dir via FUSE.
-        // 3. With app data isolation enabled, app A should not guess existence of app B from the
+        // 2. With app data isolation enabled, app A should not guess existence of app B from the
         // Android/{data,obb}/<package> paths, hence we prevent the kernel from caching that
         // information.
         return 0;
@@ -399,6 +396,23 @@
     return std::numeric_limits<double>::max();
 }
 
+static double get_entry_timeout(const string& path, bool should_inval, node* node,
+                                struct fuse* fuse) {
+    string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
+    if (path.find(media_path, 0) == 0) {
+        // Installd might delete Android/media/<package> dirs when app data is cleared.
+        // This can leave a stale entry in the kernel dcache, and break subsequent creation of the
+        // dir via FUSE.
+        return 0;
+    }
+    return get_attr_timeout(path, should_inval, node, fuse);
+}
+
+static std::string get_path(node* node) {
+    const string& io_path = node->GetIoPath();
+    return io_path.empty() ? node->BuildPath() : io_path;
+}
+
 static node* make_node_entry(fuse_req_t req, node* parent, const string& name, const string& path,
                              struct fuse_entry_param* e, int* error_code) {
     struct fuse* fuse = get_fuse(req);
@@ -412,11 +426,49 @@
     }
 
     bool should_inval = false;
+    bool transforms_complete = true;
+    int transforms = 0;
+    string io_path;
+
+    if (S_ISREG(e->attr.st_mode)) {
+        // Handle potential file transforms
+        transforms = fuse->mp->GetTransforms(path, req->ctx.uid);
+
+        if (transforms < 0) {
+            // Fail lookup if we can't fetch supported transforms for path
+            LOG(WARNING) << "Failed to fetch transforms for " << name;
+            *error_code = ENOENT;
+            return NULL;
+        }
+
+        // TODO(b/169412244): Improve JNI interaction
+        // Invalidate if there are any transforms so that we always get a lookup into userspace
+        should_inval = should_inval || transforms;
+        if (transforms) {
+            // If there are any transforms, fetch IO path
+            io_path = fuse->mp->GetIoPath(path, req->ctx.uid);
+            if (io_path.empty()) {
+                *error_code = EFAULT;
+                return NULL;
+            }
+
+            if (io_path != path) {
+                // Update size with io_path size
+                if (lstat(io_path.c_str(), &e->attr) < 0) {
+                    *error_code = errno;
+                    return NULL;
+                }
+                transforms_complete = false;
+            }
+        }
+    }
+
     node = parent->LookupChildByName(name, true /* acquire */);
     if (!node) {
-        node = ::node::Create(parent, name, &fuse->lock, &fuse->tracker);
+        node = ::node::Create(parent, name, io_path, transforms_complete, transforms, &fuse->lock,
+                              &fuse->tracker);
     } else if (!mediaprovider::fuse::containsMount(path, std::to_string(getuid() / PER_USER_RANGE))) {
-        should_inval = node->HasCaseInsensitiveMatch();
+        should_inval = should_inval || node->HasCaseInsensitiveMatch();
         // Only invalidate a path if it does not contain mount.
         // Invalidate both names to ensure there's no dentry left in the kernel after the following
         // operations:
@@ -450,11 +502,8 @@
     // reuse inode numbers.
     e->generation = 0;
     e->ino = fuse->ToInode(node);
-    e->entry_timeout = get_timeout(fuse, path, should_inval);
-    e->attr_timeout = is_package_owned_path(path, fuse->path) || should_inval
-                              ? 0
-                              : std::numeric_limits<double>::max();
-
+    e->entry_timeout = get_entry_timeout(path, should_inval, node, fuse);
+    e->attr_timeout = get_attr_timeout(path, should_inval, node, fuse);
     return node;
 }
 
@@ -607,7 +656,7 @@
         fuse_reply_err(req, ENOENT);
         return;
     }
-    string path = node->BuildPath();
+    const string& path = get_path(node);
     if (!is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
         fuse_reply_err(req, ENOENT);
         return;
@@ -619,8 +668,10 @@
     if (lstat(path.c_str(), &s) < 0) {
         fuse_reply_err(req, errno);
     } else {
-        fuse_reply_attr(req, &s, is_package_owned_path(path, fuse->path) ?
-                0 : std::numeric_limits<double>::max());
+        fuse_reply_attr(
+                req, &s,
+                get_attr_timeout(path, node->GetTransforms() || node->HasCaseInsensitiveMatch(),
+                                 node, fuse));
     }
 }
 
@@ -636,7 +687,7 @@
         fuse_reply_err(req, ENOENT);
         return;
     }
-    string path = node->BuildPath();
+    const string& path = get_path(node);
     if (!is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
         fuse_reply_err(req, ENOENT);
         return;
@@ -715,15 +766,16 @@
     }
 
     lstat(path.c_str(), attr);
-    fuse_reply_attr(req, attr, is_package_owned_path(path, fuse->path) ?
-            0 : std::numeric_limits<double>::max());
+    fuse_reply_attr(req, attr,
+                    get_attr_timeout(path, node->GetTransforms() || node->HasCaseInsensitiveMatch(),
+                                     node, fuse));
 }
 
 static void pf_canonical_path(fuse_req_t req, fuse_ino_t ino)
 {
     struct fuse* fuse = get_fuse(req);
     node* node = fuse->FromInode(ino);
-    string path = node ? node->BuildPath() : "";
+    const string& path = node ? get_path(node) : "";
 
     if (node && is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
         // TODO(b/147482155): Check that uid has access to |path| and its contents
@@ -995,7 +1047,7 @@
         return;
     }
     const struct fuse_ctx* ctx = fuse_req_ctx(req);
-    const string path = node->BuildPath();
+    const string& path = get_path(node);
     if (!is_app_accessible_path(fuse->mp, path, ctx->uid)) {
         fuse_reply_err(req, ENOENT);
         return;
@@ -1008,6 +1060,7 @@
         fi->direct_io = true;
     }
 
+    // TODO: If transform, disallow write
     int status = fuse->mp->IsOpenAllowed(path, ctx->uid, is_requesting_write(fi->flags));
     if (status) {
         fuse_reply_err(req, status);
@@ -1161,6 +1214,17 @@
     handle* h = reinterpret_cast<handle*>(fi->fh);
     struct fuse* fuse = get_fuse(req);
 
+    node* node = fuse->FromInode(ino);
+
+    if (!node->IsTransformsComplete()) {
+        if (!fuse->mp->Transform(node->BuildPath(), node->GetIoPath(), node->GetTransforms(),
+                                 req->ctx.uid)) {
+            fuse_reply_err(req, EFAULT);
+            return;
+        }
+        node->SetTransformsComplete();
+    }
+
     fuse->fadviser.Record(h->fd, size);
 
     if (h->ri->isRedactionNeeded()) {
diff --git a/jni/MediaProviderWrapper.cpp b/jni/MediaProviderWrapper.cpp
index 66ce429..0e3d3fa 100644
--- a/jni/MediaProviderWrapper.cpp
+++ b/jni/MediaProviderWrapper.cpp
@@ -281,6 +281,12 @@
                               /*is_static*/ false);
     mid_on_file_created_ = CacheMethod(env, "onFileCreated", "(Ljava/lang/String;)V",
                                        /*is_static*/ false);
+    mid_get_io_path_ = CacheMethod(env, "getIoPath", "(Ljava/lang/String;I)Ljava/lang/String;",
+                                   /*is_static*/ false);
+    mid_get_transforms_ = CacheMethod(env, "getTransforms", "(Ljava/lang/String;I)I",
+                                      /*is_static*/ false);
+    mid_transform_ = CacheMethod(env, "transform", "(Ljava/lang/String;Ljava/lang/String;II)Z",
+                                 /*is_static*/ false);
 }
 
 MediaProviderWrapper::~MediaProviderWrapper() {
@@ -424,6 +430,50 @@
     return onFileCreatedInternal(env, media_provider_object_, mid_on_file_created_, path);
 }
 
+std::string MediaProviderWrapper::GetIoPath(const std::string& path, uid_t uid) {
+    JNIEnv* env = MaybeAttachCurrentThread();
+
+    ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
+    ScopedLocalRef<jstring> j_res_path(
+            env, static_cast<jstring>(env->CallObjectMethod(media_provider_object_,
+                                                            mid_get_io_path_, j_path.get(), uid)));
+    ScopedUtfChars j_res_utf(env, j_res_path.get());
+    if (CheckForJniException(env)) {
+        return "";
+    }
+
+    return string(j_res_utf.c_str());
+}
+
+int MediaProviderWrapper::GetTransforms(const std::string& path, uid_t uid) {
+    JNIEnv* env = MaybeAttachCurrentThread();
+
+    ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
+    int res = env->CallIntMethod(media_provider_object_, mid_get_transforms_, j_path.get(), uid);
+
+    if (CheckForJniException(env)) {
+        return -1;
+    }
+
+    return res;
+}
+
+bool MediaProviderWrapper::Transform(const std::string& src, const std::string& dst, int transforms,
+                                     uid_t uid) {
+    JNIEnv* env = MaybeAttachCurrentThread();
+
+    ScopedLocalRef<jstring> j_src(env, env->NewStringUTF(src.c_str()));
+    ScopedLocalRef<jstring> j_dst(env, env->NewStringUTF(dst.c_str()));
+    bool res = env->CallBooleanMethod(media_provider_object_, mid_transform_, j_src.get(),
+                                      j_dst.get(), uid);
+
+    if (CheckForJniException(env)) {
+        return false;
+    }
+
+    return res;
+}
+
 /*****************************************************************************************/
 /******************************** Private member functions *******************************/
 /*****************************************************************************************/
diff --git a/jni/MediaProviderWrapper.h b/jni/MediaProviderWrapper.h
index 8ada86d..9c8d778 100644
--- a/jni/MediaProviderWrapper.h
+++ b/jni/MediaProviderWrapper.h
@@ -165,6 +165,15 @@
      */
     void OnFileCreated(const std::string& path);
 
+    /** Get path for actual I/O */
+    std::string GetIoPath(const std::string& path, uid_t uid);
+
+    /** Get supported transformations for path and transform actions for uid on path. */
+    int GetTransforms(const std::string& path, uid_t uid);
+
+    /** Transforms from src to dst file */
+    bool Transform(const std::string& src, const std::string& dst, int transforms, uid_t uid);
+
     /**
      * Initializes per-process static variables associated with the lifetime of
      * a managed runtime.
@@ -189,6 +198,9 @@
     jmethodID mid_rename_;
     jmethodID mid_is_uid_for_package_;
     jmethodID mid_on_file_created_;
+    jmethodID mid_get_io_path_;
+    jmethodID mid_get_transforms_;
+    jmethodID mid_transform_;
 
     /**
      * Auxiliary for caching MediaProvider methods.
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 13df5a3..e5344cc 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -19,6 +19,7 @@
 
 #include <android-base/logging.h>
 
+#include <atomic>
 #include <cstdint>
 #include <limits>
 #include <list>
@@ -114,13 +115,14 @@
 class node {
   public:
     // Creates a new node with the specified parent, name and lock.
-    static node* Create(node* parent, const std::string& name, std::recursive_mutex* lock,
+    static node* Create(node* parent, const std::string& name, const std::string& io_path,
+                        bool transforms_complete, const int transforms, std::recursive_mutex* lock,
                         NodeTracker* tracker) {
         // Place the entire constructor under a critical section to make sure
         // node creation, tracking (if enabled) and the addition to a parent are
         // atomic.
         std::lock_guard<std::recursive_mutex> guard(*lock);
-        return new node(parent, name, lock, tracker);
+        return new node(parent, name, io_path, transforms_complete, transforms, lock, tracker);
     }
 
     // Creates a new root node. Root nodes have no parents by definition
@@ -128,7 +130,7 @@
     static node* CreateRoot(const std::string& path, std::recursive_mutex* lock,
                             NodeTracker* tracker) {
         std::lock_guard<std::recursive_mutex> guard(*lock);
-        node* root = new node(nullptr, path, lock, tracker);
+        node* root = new node(nullptr, path, path, true, 0, lock, tracker);
 
         // The root always has one extra reference to avoid it being
         // accidentally collected.
@@ -176,12 +178,15 @@
 
     // Looks up a direct descendant of this node by name. If |acquire| is true,
     // also Acquire the node before returning a reference to it.
-    node* LookupChildByName(const std::string& name, bool acquire) const {
-        return ForChild(name, [acquire](node* child) {
-            if (acquire) {
-                child->Acquire();
+    node* LookupChildByName(const std::string& name, bool acquire, const int transforms = 0) const {
+        return ForChild(name, [acquire, transforms](node* child) {
+            if (child->transforms_ == transforms) {
+                if (acquire) {
+                    child->Acquire();
+                }
+                return true;
             }
-            return true;
+            return false;
         });
     }
 
@@ -253,6 +258,16 @@
         return name_;
     }
 
+    const std::string& GetIoPath() const { return io_path_; }
+
+    int GetTransforms() const { return transforms_; }
+
+    bool IsTransformsComplete() const {
+        return transforms_complete_.load(std::memory_order_acquire);
+    }
+
+    void SetTransformsComplete() { transforms_complete_.store(true, std::memory_order_release); }
+
     node* GetParent() const {
         std::lock_guard<std::recursive_mutex> guard(*lock_);
         return parent_;
@@ -326,8 +341,12 @@
     static const node* LookupAbsolutePath(const node* root, const std::string& absolute_path);
 
   private:
-    node(node* parent, const std::string& name, std::recursive_mutex* lock, NodeTracker* tracker)
+    node(node* parent, const std::string& name, const std::string& io_path, bool transforms_complete,
+         const int transforms, std::recursive_mutex* lock, NodeTracker* tracker)
         : name_(name),
+          io_path_(io_path),
+          transforms_complete_(transforms_complete),
+          transforms_(transforms),
           refcount_(0),
           parent_(nullptr),
           has_redacted_cache_(false),
@@ -454,6 +473,15 @@
 
     // The name of this node. Non-const because it can change during renames.
     std::string name_;
+    // Filesystem path that will be used for IO (if it is non-empty) instead of node->BuildPath
+    const std::string io_path_;
+    // Whether any transforms required on |io_path_| are complete.
+    // If false, might need to call a node transform function with |transforms| below
+    std::atomic_bool transforms_complete_;
+    // Opaque flags that determine the 'supported' and 'required' transforms to perform on node
+    // before IO. These flags should not be interpreted in native but should be passed as part
+    // of a transform function and if successful, |transforms_complete_| should be set to true
+    const int transforms_;
     // The reference count for this node. Guarded by |lock_|.
     uint32_t refcount_;
     // Set of children of this node. All of them contain a back reference
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 90f0894..d2ba399 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -31,8 +31,9 @@
 
     typedef std::unique_ptr<node, decltype(&NodeTest::destroy)> unique_node_ptr;
 
-    unique_node_ptr CreateNode(node* parent, const std::string& path) {
-        return unique_node_ptr(node::Create(parent, path, &lock_, &tracker_), &NodeTest::destroy);
+    unique_node_ptr CreateNode(node* parent, const std::string& path, const int transforms = 0) {
+        return unique_node_ptr(node::Create(parent, path, "", true, transforms, &lock_, &tracker_),
+                               &NodeTest::destroy);
     }
 
     static class node* ForChild(class node* node, const std::string& name,
@@ -66,7 +67,7 @@
 }
 
 TEST_F(NodeTest, TestRelease) {
-    node* node = node::Create(nullptr, "/path", &lock_, &tracker_);
+    node* node = node::Create(nullptr, "/path", "", true, 0, &lock_, &tracker_);
     acquire(node);
     acquire(node);
     ASSERT_EQ(3, GetRefCount(node));
@@ -141,57 +142,96 @@
 TEST_F(NodeTest, TestRenameNameForChild) {
     unique_node_ptr parent = CreateNode(nullptr, "/path");
 
-    unique_node_ptr child = CreateNode(parent.get(), "subdir");
-    ASSERT_EQ(2, GetRefCount(parent.get()));
-    ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
+    unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+    unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
+    ASSERT_EQ(3, GetRefCount(parent.get()));
+    ASSERT_EQ(child0.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 
     parent->RenameChild("subdir", "subdir_new", parent.get());
 
-    ASSERT_EQ(2, GetRefCount(parent.get()));
-    ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
-    ASSERT_EQ(child.get(), parent->LookupChildByName("subdir_new", false /* acquire */));
+    ASSERT_EQ(3, GetRefCount(parent.get()));
+    ASSERT_EQ(nullptr,
+              parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+    ASSERT_EQ(child0.get(),
+              parent->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
 
-    ASSERT_EQ("/path/subdir_new", child->BuildPath());
-    ASSERT_EQ(1, GetRefCount(child.get()));
+    ASSERT_EQ("/path/subdir_new", child0->BuildPath());
+    ASSERT_EQ("/path/subdir_new", child1->BuildPath());
+    ASSERT_EQ(1, GetRefCount(child0.get()));
+    ASSERT_EQ(1, GetRefCount(child1.get()));
 }
 
 TEST_F(NodeTest, TestRenameParentForChild) {
     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
 
-    unique_node_ptr child = CreateNode(parent1.get(), "subdir");
-    ASSERT_EQ(2, GetRefCount(parent1.get()));
-    ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
+    unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
+    unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
+    ASSERT_EQ(3, GetRefCount(parent1.get()));
+    ASSERT_EQ(child0.get(),
+              parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 
     parent1->RenameChild("subdir", "subdir", parent2.get());
     ASSERT_EQ(1, GetRefCount(parent1.get()));
-    ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 
-    ASSERT_EQ(2, GetRefCount(parent2.get()));
-    ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir", false /* acquire */));
+    ASSERT_EQ(3, GetRefCount(parent2.get()));
+    ASSERT_EQ(child0.get(),
+              parent2->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent2->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 
-    ASSERT_EQ("/path2/subdir", child->BuildPath());
-    ASSERT_EQ(1, GetRefCount(child.get()));
+    ASSERT_EQ("/path2/subdir", child0->BuildPath());
+    ASSERT_EQ("/path2/subdir", child1->BuildPath());
+    ASSERT_EQ(1, GetRefCount(child0.get()));
+    ASSERT_EQ(1, GetRefCount(child1.get()));
 }
 
 TEST_F(NodeTest, TestRenameNameAndParentForChild) {
     unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
     unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
 
-    unique_node_ptr child = CreateNode(parent1.get(), "subdir");
-    ASSERT_EQ(2, GetRefCount(parent1.get()));
-    ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
+    unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
+    unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
+    ASSERT_EQ(3, GetRefCount(parent1.get()));
+    ASSERT_EQ(child0.get(),
+              parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 
     parent1->RenameChild("subdir", "subdir_new", parent2.get());
     ASSERT_EQ(1, GetRefCount(parent1.get()));
-    ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
-    ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir_new", false /* acquire */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
 
-    ASSERT_EQ(2, GetRefCount(parent2.get()));
-    ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir_new", false /* acquire */));
+    ASSERT_EQ(3, GetRefCount(parent2.get()));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
 
-    ASSERT_EQ("/path2/subdir_new", child->BuildPath());
-    ASSERT_EQ(1, GetRefCount(child.get()));
+    ASSERT_EQ("/path2/subdir_new", child0->BuildPath());
+    ASSERT_EQ("/path2/subdir_new", child1->BuildPath());
+    ASSERT_EQ(1, GetRefCount(child0.get()));
+    ASSERT_EQ(1, GetRefCount(child1.get()));
 }
 
 TEST_F(NodeTest, TestBuildPath) {
@@ -219,21 +259,28 @@
 
 TEST_F(NodeTest, TestSetDeletedForChild) {
     unique_node_ptr parent = CreateNode(nullptr, "/path");
-    unique_node_ptr child = CreateNode(parent.get(), "subdir");
+    unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+    unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
 
-    ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
+    ASSERT_EQ(child0.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
     parent->SetDeletedForChild("subdir");
-    ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
+    ASSERT_EQ(nullptr,
+              parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
 }
 
 TEST_F(NodeTest, DeleteTree) {
     unique_node_ptr parent = CreateNode(nullptr, "/path");
 
     // This is the tree that we intend to delete.
-    node* child = node::Create(parent.get(), "subdir", &lock_, &tracker_);
-    node::Create(child, "s1", &lock_, &tracker_);
-    node* subchild2 = node::Create(child, "s2", &lock_, &tracker_);
-    node::Create(subchild2, "sc2", &lock_, &tracker_);
+    node* child = node::Create(parent.get(), "subdir", "", true, 0, &lock_, &tracker_);
+    node::Create(child, "s1", "", true, 0, &lock_, &tracker_);
+    node* subchild2 = node::Create(child, "s2", "", true, 0, &lock_, &tracker_);
+    node::Create(subchild2, "sc2", "", true, 0, &lock_, &tracker_);
 
     ASSERT_EQ(child, parent->LookupChildByName("subdir", false /* acquire */));
     node::DeleteTree(child);
@@ -248,6 +295,20 @@
     ASSERT_EQ(nullptr, parent->LookupChildByName("", false /* acquire */));
 }
 
+TEST_F(NodeTest, LookupChildByName_transforms) {
+    unique_node_ptr parent = CreateNode(nullptr, "/path");
+    unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+    unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
+
+    ASSERT_EQ(child0.get(), parent->LookupChildByName("subdir", false /* acquire */));
+    ASSERT_EQ(child0.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+    ASSERT_EQ(child1.get(),
+              parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+    ASSERT_EQ(nullptr,
+              parent->LookupChildByName("subdir", false /* acquire */, 2 /* transforms */));
+}
+
 TEST_F(NodeTest, LookupChildByName_refcounts) {
     unique_node_ptr parent = CreateNode(nullptr, "/path");
     unique_node_ptr child = CreateNode(parent.get(), "subdir");
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 3ad143f..717786d 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -1144,6 +1144,65 @@
     }
 
     /**
+     * Called from FUSE to transform a file
+     *
+     * A transform can change the file contents for {@code uid} from {@code src} to {@code dst}
+     * depending on {@code flags}. This allows the FUSE daemon serve different file contents for
+     * the same file to different apps.
+     *
+     * The only supported transform for now is transcoding which re-encodes a file taken in a modern
+     * format like HEVC to a legacy format like AVC.
+     *
+     * @param src file path to transform
+     * @param dst file path to save transformed file
+     * @param flags determines the kind of transform
+     * @param uid app requesting transform
+     *
+     * Called from JNI in jni/MediaProviderWrapper.cpp
+     */
+    @Keep
+    public boolean transformForFuse(String src, String dst, int flags, int uid) {
+        // TODO: Add logic
+        return true;
+    }
+
+    /**
+     * Called from FUSE to get IO path for {@code uid}
+     *
+     * IO path is the actual path to be used on the lower fs for IO via FUSE. For some file
+     * transforms, this path might be different from the path the app is requesting IO on.
+     *
+     * @param path file path to get an IO path for
+     * @param uid app requesting IO
+     *
+     * Called from JNI in jni/MediaProviderWrapper.cpp
+     */
+    @Keep
+    public String getIoPathForFuse(String path, int uid) {
+        // TODO: Add logic
+        return "";
+    }
+
+    /**
+     * Called from FUSE to get transforms for {@code uid}
+     *
+     * If transforms are not supported for {@code path}, {@code 0} will be returned. Otherwise,
+     * a bitwise OR of supported transforms for {@code path} and actual transforms to perform for
+     * {@code uid} will be returned.
+     *
+     * @param path file path to get transforms for
+     * @param uid app requesting IO
+     *
+     * Called from JNI in jni/MediaProviderWrapper.cpp
+     * @see {@link transformForFuse}
+     */
+    @Keep
+    public int getTransformsForFuse(String path, int uid) {
+        // TODO: Add logic
+        return 0;
+    }
+
+    /**
      * Returns true if the app denoted by the given {@code uid} and {@code packageName} is allowed
      * to clear other apps' cache directories.
      */