ipc_android: Add Android-specific Java/AIDL code

This commit is contained in:
Ryan Pavlik 2020-10-13 15:11:35 -05:00
parent d0187cee9a
commit d6564e3798
9 changed files with 331 additions and 1 deletions

View file

@ -3,4 +3,5 @@
rootProject.name = 'monado'
include ":src:xrt:auxiliary:android"
include ':src:xrt:auxiliary:android'
include ':src:xrt:ipc_android'

View file

@ -0,0 +1,33 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
apply plugin: 'com.android.library'
android {
compileSdkVersion project.sharedTargetSdk
buildToolsVersion '30.0.2'
defaultConfig {
minSdkVersion 24
targetSdkVersion project.sharedTargetSdk
}
buildTypes {
release {
minifyEnabled false
// Gradle plugin produces proguard-android-optimize.txt from @Keep annotations
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation project(':src:xrt:auxiliary:android')
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.annotation:annotation:1.1.0'
}

View file

@ -0,0 +1,4 @@
# Copyright 2020, Collabora, Ltd.
# SPDX-License-Identifier: BSL-1.0
# see http://developer.android.com/guide/developing/tools/proguard.html
# Trying to keep most of them in source code annotations and let Gradle do the work

View file

@ -0,0 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.freedesktop.monado.ipc">
<!--
Copyright 2020, Collabora, Ltd.
SPDX-License-Identifier: BSL-1.0
-->
<application>
<service android:name=".MonadoService">
<intent-filter>
<action android:name="org.freedesktop.monado.CONNECT" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -0,0 +1,25 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Interface to bootstrap the Monado IPC connection.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup aux_android_java
*/
package org.freedesktop.monado.ipc;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
interface IMonado {
/*!
* Pass one side of the socket pair to the service to set up the IPC.
*/
void connect(in ParcelFileDescriptor parcelFileDescriptor);
/*!
* Provide the surface we inject into the activity, back to the service.
*/
void passAppSurface(in Surface surface);
}

View file

@ -0,0 +1,147 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Implementation of the Monado AIDL server
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
package org.freedesktop.monado.ipc;
import android.content.ComponentName;
import android.content.Context;
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.
* <p>
* 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.
* <p>
* 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;
/**
* Indicates that we tried to connect but failed.
* <p>
* 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)
* <p>
* 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?
* <p>
* 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(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.
* <p>
* 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.
*/
}

View file

@ -0,0 +1,72 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Implementation of the Monado AIDL server
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
package org.freedesktop.monado.ipc;
import android.os.ParcelFileDescriptor;
import android.view.Surface;
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 {
public void connect(ParcelFileDescriptor parcelFileDescriptor) {
nativeAddClient(parcelFileDescriptor);
}
public void passAppSurface(Surface surface) {
nativeAppSurface(surface);
}
/**
* 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");
}
}

View file

@ -0,0 +1,31 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Service implementation for exposing IMonado.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
package org.freedesktop.monado.ipc;
import android.app.Service;
import android.content.Intent;
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
@Override
public IBinder onBind(Intent intent) {
return new MonadoImpl();
}
}

View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>