Skip to content

Commit

Permalink
Add experimental Android Module executed In-Process (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
edaniels authored Apr 9, 2024
1 parent 3cea020 commit 3234489
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 3 deletions.
1 change: 1 addition & 0 deletions android/examples/module-in-process/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
19 changes: 19 additions & 0 deletions android/examples/module-in-process/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Android Module In-Process Example (Experimental)

This *experimental* version of a module allows you to run it in the same process as the Viam
Android App (as a thread calling `main`). Right now this is beneficial for getting access to its
ApplicationContext, something
the normal module cannot yet fully construct itself due to it running as a library
inside `app_process`. Use this at your own risk.

How to use:

* It's exactly the same as a normal module except in the `module` gradle configuration block, you
use `executeInProcess = true`. This allows access to `Module.getParentContext()`.

What's not tested:

* Multiple modules running in-process
* Logging

See [examples/module](../module/README.md) for more info.
40 changes: 40 additions & 0 deletions android/examples/module-in-process/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.2'
}
}

plugins {
id 'com.viam.sdk.android.module' version '1.0-SNAPSHOT'
}

module {
mainEntryClass = "com.viam.sdk.android.examples.module.MyModuleInProcess"
executeInProcess = true
}

android {
namespace 'com.viam.sdk.android.examples.module'

defaultConfig {
minSdkVersion min_api
targetSdkVersion target_api
compileSdk target_api
versionCode 1
versionName "1.0"
}

buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}

dependencies {
implementation project(':android:viam-android-sdk')
}
21 changes: 21 additions & 0 deletions android/examples/module-in-process/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.viam.sdk.android.examples.module;


import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import com.viam.common.v1.Common;
import com.viam.sdk.android.module.Module;
import com.viam.sdk.core.component.generic.Generic;
import com.viam.sdk.core.resource.Model;
import com.viam.sdk.core.resource.ModelFamily;
import com.viam.sdk.core.resource.Registry;
import com.viam.sdk.core.resource.Resource;
import com.viam.sdk.core.resource.ResourceCreatorRegistration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import viam.app.v1.Robot.ComponentConfig;

public class MyModuleInProcess {

public static void main(String[] args) {
Registry.registerResourceCreator(
Generic.SUBTYPE,
MyGeneric.MODEL,
new ResourceCreatorRegistration(MyGeneric::new, MyGeneric::validateConfig)
);
final Module module = new Module(args);
module.start();
}

public static class MyGeneric extends Generic {

public static final Model MODEL = new Model(new ModelFamily("viam", "generic"), "mygeneric");

public MyGeneric(ComponentConfig config, Map<Common.ResourceName, Resource> dependencies) {
super(config.getName());
}

public static Set<String> validateConfig(final ComponentConfig config) {
return new HashSet<>();
}

@Override
public Struct doCommand(Map<String, Value> command) {
final Struct.Builder builder = Struct.newBuilder();
return builder.putFields("hello",
Value.newBuilder().setStringValue(Module.getParentContext().getPackageName()).build())
.build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface AndroidModulePluginExtension {
Property<String> getMainEntryClass()

Property<Boolean> getForce32Bit()

Property<Boolean> getExecuteInProcess()
}

abstract class CopyModuleTask extends DefaultTask {
Expand All @@ -37,6 +39,7 @@ abstract class CopyModuleTask extends DefaultTask {
class AndroidModulePlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('module', AndroidModulePluginExtension)

// Note(erd): based on research, going the route of an android library makes it *very*
// difficult to maintain the logic of how to create a "fat AAR" such that we have all
// dependencies (including transitive) in one package. Using application naturally gets
Expand All @@ -49,6 +52,7 @@ class AndroidModulePlugin implements Plugin<Project> {
if (!extension.mainEntryClass.isPresent()) {
throw new Exception("Must set module.mainEntryClass")
}
def executeInProcess = extension.executeInProcess.getOrElse(false)

project.android.applicationVariants.all { variant ->
def outputFile = variant.outputs.first().outputFile
Expand All @@ -57,7 +61,9 @@ class AndroidModulePlugin implements Plugin<Project> {

def tmpModDir = "${project.layout.buildDirectory.get()}/tmp/module"
project.file(tmpModDir).mkdirs()
def modScript = getClass().getResourceAsStream("/mod.sh").getText()
def modScript = executeInProcess ?
getClass().getResourceAsStream("/mod-in-process.sh").getText() :
getClass().getResourceAsStream("/mod.sh").getText()

assembleTask.configure {
doLast {
Expand Down
62 changes: 62 additions & 0 deletions android/module-plugin/src/main/resources/mod-in-process.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/sh

CWD=`pwd`
JAR_PATH=__MODULE_JAR_PATH__
JAR_PARENT_PATH="$(realpath $(dirname "$JAR_PATH"))"

# only copy the jar if it is not next to our script
if [ "$JAR_PARENT_PATH" == "$CWD" ]; then
SAFE_JAR_PATH=$JAR_PATH
else
SAFE_JAR_PATH=$(mktemp -d -p `pwd`)
cp $JAR_PATH $SAFE_JAR_PATH/module.jar
SAFE_JAR_PATH=$SAFE_JAR_PATH/module.jar
fi

ABI_LIST=`getprop ro.product.cpu.abilist`
ABI_ARRAY=(${ABI_LIST//,/ })

LIBRARY_PATH=
for abi in "${ABI_ARRAY[@]}"
do
NEXT_PATH_TMP=$SAFE_JAR_PATH!/lib/$abi
if [ -z "$LIBRARY_PATH" ]; then
LIBRARY_PATH=$NEXT_PATH_TMP
else
LIBRARY_PATH=$LIBRARY_PATH:$NEXT_PATH_TMP
fi
done
FORCE_32=__FORCE_32__
if [ "${FORCE_32}" != "true" ] && [ `getconf LONG_BIT` == "64" ]; then
LIBRARY_PATH=$LIBRARY_PATH:/system/lib64:/system/product/lib64
else
LIBRARY_PATH=$LIBRARY_PATH:/system/lib:/system/product/lib
fi

proc_file=$(mktemp -p $_VIAM_FG_TEMP_DIR)
rm $proc_file
trap 'rm -rf -- "$proc_file"' EXIT

intentURI="intent:#Intent;action=com.viam.rdk.fgservice.START_MODULE"
intentURI="$intentURI;S.secret=$_VIAM_FG_SECRET"
intentURI="$intentURI;S.proc_file=$proc_file"
intentURI="$intentURI;S.java_class_path=$SAFE_JAR_PATH"
intentURI="$intentURI;S.java_library_path=$LIBRARY_PATH"
intentURI="$intentURI;S.java_entry_point_class=__MAIN_ENTRY_CLASS__"
saveIFS="$IFS"
IFS=$'\n'
intentURI="$intentURI;S.java_entry_point_args=$*"
IFS=$saveIFS
intentURI="$intentURI;end"

# Note(erd):
# user being -3 is USER_CURRENT_OR_SELF which works better than -2 (USER_CURRENT).
# When passing nothing before or even the same UID as the Viam APK, this would fail with
# a tautological message. -2 did not help. -3 allowed this new process (in the shell) to
# execute as the self user from a user process. I assume user process is some other pid
# that is not the app itself. Also, the "current" option is -2 and I found no human readable
# option that is for -3.
am broadcast --user -3 "$intentURI"

while [ ! -f $proc_file ]; do sleep 0.1; done
cat $proc_file
13 changes: 11 additions & 2 deletions android/sdk/src/main/java/com/viam/sdk/android/module/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.net.LocalSocketAddress;
import android.os.Looper;
import com.viam.sdk.android.module.fake.FakeContext;
import com.viam.sdk.core.module.BaseModule;
import com.viam.sdk.core.robot.RobotClient;
Expand All @@ -18,11 +17,14 @@
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.io.IOUtils;
import org.webrtc.MediaStream;

public class Module extends BaseModule {

private static Supplier<Context> parentContext;

RobotClient parent;

/**
Expand All @@ -34,7 +36,14 @@ public class Module extends BaseModule {
*/
public Module(final String[] args) {
super(args);
Looper.prepareMainLooper();
}

public static Context getParentContext() {
if (parentContext == null) {
throw new RuntimeException(
"invalid module state with no parent context (not running as in-process module?)");
}
return parentContext.get();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include ':core:sdk',
':android:module-plugin',
':android:examples:simple',
':android:examples:module',
':android:examples:module-in-process',
':android:examples:mlmodel-module'

project(':core:sdk').name = 'viam-core-sdk'
Expand All @@ -30,4 +31,5 @@ project(':android:services:mlmodel').name = 'viam-android-sdk-mlmodel-service'
project(':android:module-plugin').name = 'viam-android-module'
project(':android:examples:simple').name = 'viam-android-sdk-examples-simple'
project(':android:examples:module').name = 'viam-android-sdk-examples-module'
project(':android:examples:module-in-process').name = 'viam-android-sdk-examples-module-in-process'
project(':android:examples:mlmodel-module').name = 'viam-android-sdk-examples-mlmodel-module'

0 comments on commit 3234489

Please sign in to comment.