AVF: allow FD -> IBinder.

Okay, so before we have two APIs:
- connectToVsockServer "AB"
- connectVsock "A"

This adds:
- binderFromPreconnectedClient "B"

That is:
- connectToVsockServer = connectVsock + binderFromPreconnectedClient

Normally, in the same process, you do all of this, but to
pass a connection to another process, imagine
processes "Q -> R -> S", where:
- Q is client
- R is VM creator
- S is VM

Then Q can call binderFromPreconnectedClient, and it can
implement the provider by calling an API on R which will
return the results of R calling connectVsock.

Non-binder use of connectVsock is not recommended.

Bugs: me and without this, extra hops cost performannce
Test: atest MicrodroidTestApp - several tests pass, so letting TH run
the rest

Change-Id: I670243714bbb6167a1fdae2bb898b1f5a9d01f5f
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index 5f634ef..3927a98 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -1960,8 +1960,43 @@
         }
     }
 
-    @Nullable
-    private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
+    /**
+     * Abstracts away the task of creating a vsock connection. Normally, in the same process, you'll
+     * make the connection and then promote it to a binder as part of the same API call, but if you
+     * want to pass a connection to another process first, before establishing the RPC binder
+     * connection, you may implement this method by getting a vsock connection from another process.
+     *
+     * <p>It is recommended to convert other types of exceptions (e.g. remote exceptions) to
+     * VirtualMachineException, so that all connection failures will be visible under the same type
+     * of exception.
+     *
+     * @hide
+     */
+    public interface VsockConnectionProvider {
+        @NonNull
+        public ParcelFileDescriptor addConnection() throws VirtualMachineException;
+    }
+
+    /**
+     * Class to make it easy to use JNI, without needing PFD and other classes.
+     *
+     * @hide
+     */
+    private static class NativeProviderWrapper {
+        private VsockConnectionProvider mProvider = null;
+
+        // ensures last FD is owned until get is called again
+        private ParcelFileDescriptor mMoreLifetime = null;
+
+        public NativeProviderWrapper(VsockConnectionProvider provider) {
+            mProvider = provider;
+        }
+
+        int connect() throws VirtualMachineException {
+            mMoreLifetime = mProvider.addConnection();
+            return mMoreLifetime.getFileDescriptor().getInt$();
+        }
+    }
 
     /**
      * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
@@ -1981,15 +2016,36 @@
     public IBinder connectToVsockServer(
             @IntRange(from = MIN_VSOCK_PORT, to = MAX_VSOCK_PORT) long port)
             throws VirtualMachineException {
+        VsockConnectionProvider provider =
+                new VsockConnectionProvider() {
+                    @Override
+                    public ParcelFileDescriptor addConnection() throws VirtualMachineException {
+                        return connectVsock(port);
+                    }
+                };
+        return binderFromPreconnectedClient(provider);
+    }
 
-        synchronized (mLock) {
-            IBinder iBinder =
-                    nativeConnectToVsockServer(getRunningVm().asBinder(), validatePort(port));
-            if (iBinder == null) {
-                throw new VirtualMachineException("Failed to connect to vsock server");
-            }
-            return iBinder;
+    @Nullable
+    private static native IBinder nativeBinderFromPreconnectedClient(
+            NativeProviderWrapper provider);
+
+    /**
+     * Convert existing vsock connection to a binder connection.
+     *
+     * <p>connectToVsockServer = connectToVsock + binderFromPreconnectedClient
+     *
+     * @hide
+     */
+    @WorkerThread
+    @NonNull
+    public static IBinder binderFromPreconnectedClient(@NonNull VsockConnectionProvider provider)
+            throws VirtualMachineException {
+        IBinder binder = nativeBinderFromPreconnectedClient(new NativeProviderWrapper(provider));
+        if (binder == null) {
+            throw new VirtualMachineException("Failed to connect to vsock server");
         }
+        return binder;
     }
 
     /**
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
index 3f1d8a0..67a4716 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -48,32 +48,30 @@
 } // namespace
 
 extern "C" JNIEXPORT jobject JNICALL
-Java_android_system_virtualmachine_VirtualMachine_nativeConnectToVsockServer(
-        JNIEnv *env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
-    using aidl::android::system::virtualizationservice::IVirtualMachine;
-    using ndk::ScopedFileDescriptor;
-    using ndk::SpAIBinder;
+Java_android_system_virtualmachine_VirtualMachine_nativeBinderFromPreconnectedClient(
+        [[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jobject provider) {
+    ScopedLocalRef<jclass> callback_class(env, env->GetObjectClass(provider));
+    jmethodID mid = env->GetMethodID(callback_class.get(), "connect", "()I");
+    LOG_ALWAYS_FATAL_IF(mid == nullptr, "Could not find method");
 
-    auto vm = IVirtualMachine::fromBinder(SpAIBinder{AIBinder_fromJavaBinder(env, vmBinder)});
+    // TODO(b/398890208): make this memory owned by the connection
+    struct State {
+        JNIEnv *mEnv;
+        jobject mProvider;
+        jmethodID mMid;
+    } state;
 
-    std::tuple args{env, vm.get(), port};
-    using Args = decltype(args);
+    state.mEnv = env;
+    state.mProvider = provider;
+    state.mMid = mid;
 
-    auto requestFunc = [](void *param) {
-        auto [env, vm, port] = *static_cast<Args *>(param);
-
-        ScopedFileDescriptor fd;
-        if (auto status = vm->connectVsock(port, &fd); !status.isOk()) {
-            env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
-                          ("Failed to connect vsock: " + status.getDescription()).c_str());
-            return -1;
-        }
-
-        // take ownership
-        int ret = fd.get();
-        *fd.getR() = -1;
-
-        return ret;
+    using RequestFun = int (*)(void *);
+    RequestFun requestFunc = [](void *param) -> int {
+        State *state = reinterpret_cast<State *>(param);
+        int ownedFd = state->mEnv->CallIntMethod(state->mProvider, state->mMid);
+        // FD is owned by PFD in Java layer, need to dupe it so that
+        // ARpcSession_setupPreconnectedClient can take ownership when it calls unique_fd internally
+        return fcntl(ownedFd, F_DUPFD_CLOEXEC, 0);
     };
 
     RpcSessionHandle session;
@@ -82,7 +80,7 @@
     // want too many. The number 1 is chosen after some discussion, and to match
     // the server-side default (mMaxThreads on RpcServer).
     ARpcSession_setMaxIncomingThreads(session.get(), 1);
-    auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
+    auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &state);
     return AIBinder_toJavaBinder(env, client);
 }