Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental Android Module executed In-Process #14

Merged
merged 4 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if --user -3 is load-bearing could use comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


while [ ! -f $proc_file ]; do sleep 0.1; done
cat $proc_file
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'