Adding FUSE loop thread in app side.

The CL adds FuseAppLoop internal class to the framework. It parses FUSE commands
from the proxy in the system service and invokes a callback provided by
application.

Bug: 29970149
Test: None, will be tested with CTS for StorageManager#openProxyFileDescriptor.
Change-Id: I10b2add4c2743fb91eae3cb194f55843a74fb668
diff --git a/core/java/android/os/IProxyFileDescriptorCallback.java b/core/java/android/os/IProxyFileDescriptorCallback.java
new file mode 100644
index 0000000..e41e194
--- /dev/null
+++ b/core/java/android/os/IProxyFileDescriptorCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.system.ErrnoException;
+
+/**
+ * Callback that handles file system requests from ProxyFileDescriptor.
+ * @hide
+ */
+public interface IProxyFileDescriptorCallback {
+    /**
+     * Returns size of bytes provided by the file descriptor.
+     * @return Size of bytes
+     * @throws ErrnoException
+     */
+    long onGetSize() throws ErrnoException;
+
+    /**
+     * Provides bytes read from file descriptor.
+     * It needs to return exact requested size of bytes unless it reaches file end.
+     * @param offset Where to read bytes from.
+     * @param size Size for read bytes.
+     * @param data Byte array to store read bytes.
+     * @return Size of bytes returned by the function.
+     * @throws ErrnoException
+     */
+    int onRead(long offset, int size, byte[] data) throws ErrnoException;
+
+    /**
+     * Handles bytes written to file descriptor.
+     * @param offset Where to write bytes to.
+     * @param size Size for write bytes.
+     * @param data Byte array to be written to somewhere.
+     * @return Size of bytes processed by the function.
+     * @throws ErrnoException
+     */
+    int onWrite(long offset, int size, byte[] data) throws ErrnoException;
+
+    /**
+     * Processes fsync request.
+     * @throws ErrnoException
+     */
+    void onFsync() throws ErrnoException;
+}
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
new file mode 100644
index 0000000..34253ce
--- /dev/null
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IProxyFileDescriptorCallback;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class FuseAppLoop {
+    private static final String TAG = "FuseAppLoop";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    public static final int ROOT_INODE = 1;
+    private static final int MIN_INODE = 2;
+
+    private final Object mLock = new Object();
+    private final File mParent;
+
+    @GuardedBy("mLock")
+    private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private boolean mActive = true;
+
+    /**
+     * Sequential number can be used as file name and inode in AppFuse.
+     * 0 is regarded as an error, 1 is mount point. So we start the number from 2.
+     */
+    @GuardedBy("mLock")
+    private int mNextInode = MIN_INODE;
+
+    private FuseAppLoop(@NonNull File parent) {
+        mParent = parent;
+    }
+
+    public static @NonNull FuseAppLoop open(
+            @NonNull File parent, @NonNull ParcelFileDescriptor fd) {
+        Preconditions.checkNotNull(parent);
+        Preconditions.checkNotNull(fd);
+        final FuseAppLoop bridge = new FuseAppLoop(parent);
+        final int rawFd = fd.detachFd();
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                bridge.native_start_loop(rawFd);
+            }
+        }, TAG).start();
+        return bridge;
+    }
+
+    public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback)
+            throws UnmountedException, IOException {
+        int id;
+        synchronized (mLock) {
+            if (!mActive) {
+                throw new UnmountedException();
+            }
+            if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
+                throw new IOException("Too many opened files.");
+            }
+            while (true) {
+                id = mNextInode;
+                mNextInode++;
+                if (mNextInode < 0) {
+                    mNextInode = MIN_INODE;
+                }
+                if (mCallbackMap.get(id) == null) {
+                    break;
+                }
+            }
+
+            // Register callback after we succeed to create pfd.
+            mCallbackMap.put(id, new CallbackEntry(callback));
+        }
+        try {
+            return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode);
+        } catch (FileNotFoundException error) {
+            synchronized (mLock) {
+                mCallbackMap.remove(id);
+            }
+            throw error;
+        }
+    }
+
+    public @Nullable File getMountPoint() {
+        synchronized (mLock) {
+            return mActive ? mParent : null;
+        }
+    }
+
+    private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
+        final CallbackEntry entry = mCallbackMap.get(checkInode(inode));
+        if (entry != null) {
+            return entry;
+        } else {
+            throw new ErrnoException("getCallbackEntry", OsConstants.ENOENT);
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private long onGetSize(long inode) {
+        synchronized(mLock) {
+            try {
+                return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
+            } catch (ErrnoException exp) {
+                return -exp.errno;
+            }
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private int onOpen(long inode) {
+        synchronized(mLock) {
+            try {
+                final CallbackEntry entry = getCallbackEntryOrThrowLocked(inode);
+                if (entry.opened) {
+                    throw new ErrnoException("onOpen", OsConstants.EMFILE);
+                }
+                entry.opened = true;
+                // Use inode as file handle. It's OK because AppFuse does not allow to open the same
+                // file twice.
+                return (int) inode;
+            } catch (ErrnoException exp) {
+                return -exp.errno;
+            }
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private int onFsync(long inode) {
+        synchronized(mLock) {
+            try {
+                getCallbackEntryOrThrowLocked(inode).callback.onFsync();
+                return 0;
+            } catch (ErrnoException exp) {
+                return -exp.errno;
+            }
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private int onRelease(long inode) {
+        synchronized(mLock) {
+            mCallbackMap.remove(checkInode(inode));
+            if (mCallbackMap.size() == 0) {
+                mActive = false;
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private int onRead(long inode, long offset, int size, byte[] bytes) {
+        synchronized(mLock) {
+            try {
+                return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
+            } catch (ErrnoException exp) {
+                return -exp.errno;
+            }
+        }
+    }
+
+    // Called by JNI.
+    @SuppressWarnings("unused")
+    private int onWrite(long inode, long offset, int size, byte[] bytes) {
+        synchronized(mLock) {
+            try {
+                return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
+            } catch (ErrnoException exp) {
+                return -exp.errno;
+            }
+        }
+    }
+
+    native boolean native_start_loop(int fd);
+
+    private static int checkInode(long inode) {
+        Preconditions.checkArgumentInRange(inode, MIN_INODE, Integer.MAX_VALUE, "checkInode");
+        return (int) inode;
+    }
+
+    public static class UnmountedException extends Exception {}
+
+    private static class CallbackEntry {
+        final IProxyFileDescriptorCallback callback;
+        boolean opened;
+        CallbackEntry(IProxyFileDescriptorCallback callback) {
+            Preconditions.checkNotNull(callback);
+            this.callback = callback;
+        }
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index a4e9576..be2e404 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -183,6 +183,7 @@
     android_content_res_Configuration.cpp \
     android_animation_PropertyValuesHolder.cpp \
     com_android_internal_net_NetworkStatsFactory.cpp \
+    com_android_internal_os_FuseAppLoop.cpp \
     com_android_internal_os_PathClassLoaderFactory.cpp \
     com_android_internal_os_Zygote.cpp \
     com_android_internal_util_VirtualRefBasePtr.cpp \
@@ -201,6 +202,7 @@
     $(TOP)/frameworks/base/media/jni \
     $(TOP)/system/core/base/include \
     $(TOP)/system/core/include \
+    $(TOP)/system/core/libappfuse/include \
     $(TOP)/system/media/camera/include \
     $(TOP)/system/netd/include \
     external/giflib \
@@ -230,6 +232,7 @@
 LOCAL_SHARED_LIBRARIES := \
     libmemtrack \
     libandroidfw \
+    libappfuse \
     libbase \
     libexpat \
     libnativehelper \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index cdaa4dc..1f810ac 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -201,6 +201,7 @@
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
+extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
 extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1419,7 +1420,7 @@
     REG_JNI(register_android_animation_PropertyValuesHolder),
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
     REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
-
+    REG_JNI(register_com_android_internal_os_FuseAppLoop),
 };
 
 /*
diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp
new file mode 100644
index 0000000..92a6934
--- /dev/null
+++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FuseAppLoopJNI"
+#define LOG_NDEBUG 0
+
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include <android_runtime/Log.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <jni.h>
+#include <libappfuse/FuseAppLoop.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+namespace {
+
+constexpr const char* CLASS_NAME = "com/android/internal/os/FuseAppLoop";
+
+jclass gFuseAppLoopClass;
+jmethodID gOnGetSizeMethod;
+jmethodID gOnOpenMethod;
+jmethodID gOnFsyncMethod;
+jmethodID gOnReleaseMethod;
+jmethodID gOnReadMethod;
+jmethodID gOnWriteMethod;
+
+class Callback : public fuse::FuseAppLoopCallback {
+private:
+    static constexpr size_t kBufferSize = std::max(fuse::kFuseMaxWrite, fuse::kFuseMaxRead);
+    static_assert(kBufferSize <= INT32_MAX, "kBufferSize should be fit in int32_t.");
+
+    JNIEnv* const mEnv;
+    jobject const mSelf;
+    ScopedLocalRef<jbyteArray> mJniBuffer;
+    bool mActive;
+
+    template <typename T>
+    T checkException(T result) const {
+        if (mEnv->ExceptionCheck()) {
+            LOGE_EX(mEnv, nullptr);
+            mEnv->ExceptionClear();
+            return -EIO;
+        }
+        return result;
+    }
+
+public:
+    Callback(JNIEnv* env, jobject self) :
+        mEnv(env),
+        mSelf(self),
+        mJniBuffer(env, nullptr),
+        mActive(true) {}
+
+    bool Init() {
+        mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
+        return mJniBuffer.get();
+    }
+
+    bool IsActive() override {
+        return mActive;
+    }
+
+    int64_t OnGetSize(uint64_t inode) override {
+        return checkException(mEnv->CallLongMethod(mSelf, gOnGetSizeMethod, inode));
+    }
+
+    int32_t OnOpen(uint64_t inode) override {
+        return checkException(mEnv->CallIntMethod(mSelf, gOnOpenMethod, inode));
+    }
+
+    int32_t OnFsync(uint64_t inode) override {
+        return checkException(mEnv->CallIntMethod(mSelf, gOnFsyncMethod, inode));
+    }
+
+    int32_t OnRelease(uint64_t inode) override {
+        if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) {
+            mActive = false;
+        }
+        return fuse::kFuseSuccess;
+    }
+
+    int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
+        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
+        const int32_t result = checkException(mEnv->CallIntMethod(
+                mSelf, gOnReadMethod, inode, offset, size, mJniBuffer.get()));
+        if (result <= 0) {
+            return result;
+        }
+        if (result > static_cast<int32_t>(size)) {
+            LOG(ERROR) << "Returned size is too large.";
+            return -EIO;
+        }
+
+        mEnv->GetByteArrayRegion(mJniBuffer.get(), 0, result, static_cast<jbyte*>(buffer));
+        CHECK(!mEnv->ExceptionCheck());
+
+        return checkException(result);
+    }
+
+    int32_t OnWrite(uint64_t inode, uint64_t offset, uint32_t size, const void* buffer) override {
+        CHECK_LE(size, static_cast<uint32_t>(kBufferSize));
+
+        mEnv->SetByteArrayRegion(mJniBuffer.get(), 0, size, static_cast<const jbyte*>(buffer));
+        CHECK(!mEnv->ExceptionCheck());
+
+        return checkException(mEnv->CallIntMethod(
+                mSelf, gOnWriteMethod, inode, offset, size, mJniBuffer.get()));
+    }
+};
+
+jboolean com_android_internal_os_FuseAppLoop_start_loop(JNIEnv* env, jobject self, jint jfd) {
+    base::unique_fd fd(jfd);
+    Callback callback(env, self);
+
+    if (!callback.Init()) {
+        LOG(ERROR) << "Failed to init callback";
+        return JNI_FALSE;
+    }
+
+    return fuse::StartFuseAppLoop(fd.release(), &callback);
+}
+
+const JNINativeMethod methods[] = {
+    {
+        "native_start_loop",
+        "(I)Z",
+        (void *) com_android_internal_os_FuseAppLoop_start_loop
+    }
+};
+
+}  // namespace
+
+int register_com_android_internal_os_FuseAppLoop(JNIEnv* env) {
+    gFuseAppLoopClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, CLASS_NAME));
+    gOnGetSizeMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onGetSize", "(J)J");
+    gOnOpenMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onOpen", "(J)I");
+    gOnFsyncMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onFsync", "(J)I");
+    gOnReleaseMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRelease", "(J)I");
+    gOnReadMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onRead", "(JJI[B)I");
+    gOnWriteMethod = GetMethodIDOrDie(env, gFuseAppLoopClass, "onWrite", "(JJI[B)I");
+    RegisterMethodsOrDie(env, CLASS_NAME, methods, NELEM(methods));
+    return 0;
+}
+
+}  // namespace android