diff --git a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/Client.java b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/Client.java index 60a9d611e82578d6ca9b3e6b587d557ec35e771c..5d75546f33916154ad6fa52718a8ca5d624b078c 100644 --- a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/Client.java +++ b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/Client.java @@ -15,41 +15,133 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.util.Log; import androidx.annotation.Keep; import java.io.IOException; +/** + * Provides the client-side code to initiate connection to Monado IPC service. + *
+ * This class will get loaded into the OpenXR client application by our native code. + */ @Keep public class Client implements ServiceConnection { + private static final String TAG = "monado-ipc-client"; + + /** + * Context provided by app. + */ + private Context context; + /** + * Pointer to local IPC proxy: calling methods on it automatically transports arguments across binder IPC. + *
+ * May be null! + */ public IMonado monado; + + /** + * "Our" side of the socket pair - the other side is sent to the server automatically on connection. + */ public ParcelFileDescriptor fd; - public void bind(Context context) { - /** - * @todo how to get the right package here? Do we have to go so far as to re-enumerate ourselves? - */ + /** + * Indicates that we tried to connect but failed. + *
+ * Used to distinguish a "not yet fully connected" null monado member from a "tried and failed" + * null monado member. + */ + public boolean failed = false; + + /** + * Bind to the Monado IPC service - this asynchronously starts connecting (and launching the + * service if it's not already running) + *
+ * The IPC client code on Android should load this class (from the right package), instantiate + * this class, and call this method. + * + * @param context_ Context to use to make the connection. (We get the application context + * from it.) + * @param packageName The package name containing the Monado runtime. The caller is guaranteed + * to know this because it had to load this class from that package. + * (Often "org.freedesktop.monado.openxr.out_of_process" for now, at least) + * @todo how to get the right package name here? Do we have to go so far as to re-enumerate ourselves? + *
+ * Various builds, variants, etc. will have different package names, but we must specify the + * package name explicitly to avoid violating security restrictions. + */ + public void bind(Context context_, String packageName) { + context = context_.getApplicationContext(); + if (context == null) { + // in case app context returned null + context = context_; + } context.bindService( new Intent("org.freedesktop.monado.CONNECT") - .setPackage("org.freedesktop.monado.openxr.out_of_process"), + .setPackage(packageName), this, Context.BIND_AUTO_CREATE); // does not bind right away! This takes some time. } + /** + * Some on-failure cleanup. + */ + private void handleFailure() { + failed = true; + if (context != null) context.unbindService(this); + monado = null; + } + + /** + * Handle the asynchronous connection of the binder IPC. + *
+ * This sets up the class member `monado`, as well as the member `fd`. It calls + * `IMonado.connect()` automatically. The client still needs to call `IMonado.passAppSurface()` + * on `monado`. + * + * @param name should match the intent above, but not used. + * @param service the associated service, which we cast in this function. + */ @Override public void onServiceConnected(ComponentName name, IBinder service) { monado = IMonado.Stub.asInterface(service); + ParcelFileDescriptor theirs; try { ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair(); + fd = fds[0]; + theirs = fds[1]; } catch (IOException e) { e.printStackTrace(); + Log.e(TAG, "could not create socket pair: " + e.toString()); + handleFailure(); + return; + } + try { + monado.connect(theirs); + } catch (RemoteException e) { + e.printStackTrace(); + Log.e(TAG, "could not call IMonado.connect: " + e.toString()); + handleFailure(); } - } + /** + * Handle asynchronous disconnect. + * + * @param name should match the intent above, but not used. + */ @Override public void onServiceDisconnected(ComponentName name) { monado = null; } + + /* + * @todo do we need to watch for a disconnect here? + * https://stackoverflow.com/questions/18078914/notify-an-android-service-when-a-bound-client-disconnects + * + * Our existing native disconnect handling might be sufficient. + */ } diff --git a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoImpl.java b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoImpl.java index be0ddb71c62141ba15c0ff7b0a4c898bdacdbb8e..341f212ca674f2f0415b6de9dbffb9c2c69de007 100644 --- a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoImpl.java +++ b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoImpl.java @@ -16,6 +16,11 @@ import androidx.annotation.Keep; import org.freedesktop.monado.ipc.IMonado.Stub; +/** + * Java implementation of the IMonado IPC interface. + * + * All this does is delegate calls to native JNI implementations + */ @Keep public class MonadoImpl extends IMonado.Stub { @@ -28,15 +33,40 @@ public class MonadoImpl extends IMonado.Stub { } /** + * Native handling of receiving a surface: should convert it to an ANativeWindow then do stuff + * with it. + * + * Ignore Android Studio complaining that this function is missing: it is not, it is just in a + * different module. See `src/xrt/targets/service-lib/lib.cpp` for the implementation. + * (Ignore the warning saying that file isn't included in the build: it is, Android Studio + * is just confused.) + * * @param surface * @todo figure out a good way to make the MonadoImpl pointer a client ID */ private native void nativeAppSurface(Surface surface); + /** + * Native handling of receiving an FD for a new client: the FD should be used to start up the + * rest of the native IPC code on that socket. + * + * This is essentially the entry point for the monado service on Android: if it's already + * running, this will be called in it. If it's not already running, a process will be created, + * and this will be the first native code executed in that process. + * + * Ignore Android Studio complaining that this function is missing: it is not, it is just in a + * different module. See `src/xrt/targets/service-lib/lib.cpp` for the implementation. + * (Ignore the warning saying that file isn't included in the build: it is, Android Studio + * is just confused.) + * + * @param surface + * @todo figure out a good way to make the MonadoImpl pointer a client ID + */ private native void nativeAddClient(ParcelFileDescriptor parcelFileDescriptor); static { // Load the shared library with the native parts of this class + // This is the service-lib target. System.loadLibrary("monado-service"); } } diff --git a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoService.java b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoService.java index 39ffcef1a67d7c64e5b7e4dab31278fd7a6b54fc..d29feec950e2ac90fde93a801842f1665b208873 100644 --- a/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoService.java +++ b/src/xrt/ipc/android/src/main/java/org/freedesktop/monado/ipc/MonadoService.java @@ -15,6 +15,11 @@ import android.os.IBinder; import androidx.annotation.Nullable; +/** + * Minimal implementation of a Service. + * + * This is needed so that the APK can expose the binder service implemented in MonadoImpl. + */ public class MonadoService extends Service { @Nullable