diff --git a/templates/android-make/Makefile b/templates/android-make/Makefile new file mode 100644 index 0000000..0bd33b0 --- /dev/null +++ b/templates/android-make/Makefile @@ -0,0 +1,59 @@ +include vars.mk + +JAVAC = javac + +APKSIGNER = ${BUILD_TOOLS_DIR}/apksigner +ZIPALIGN = ${BUILD_TOOLS_DIR}/zipalign +AAPT = ${BUILD_TOOLS_DIR}/aapt +D8 = ${BUILD_TOOLS_DIR}/d8 + +JARS := \ + ${ANDROID_JAR_FILE} \ + ${ANDROIDX_CORE_FILE} \ + ${ANDROIDX_LIFECYCLE_COMMON_FILE} \ + ${ANDROIDX_VERSIONEDPARCELABLE_FILE} \ + ${KOTLIN_STDLIB_FILE} \ + ${KOTLINX_COROUTINES_CORE_FILE} \ + ${KOTLINX_COROUTINES_CORE_JVM_FILE} + +CLASSES_PATH := $(subst /,.,$(JAVA_PACKAGE_NAME)) + +CLASSES := MainActivity + +ifeq (${HAS_MIDI_IN}, yes) +MIN_API := 29 +else +MIN_API := 26 +endif + +all: build/${BUNDLE_NAME}.apk + +build/${BUNDLE_NAME}.apk: build/gen/${BUNDLE_NAME}.aligned.apk ${KEY_STORE} + ${APKSIGNER} sign --ks ${KEY_STORE} --ks-key-alias ${KEY_ALIAS} --ks-pass ${STORE_PASS} --key-pass ${KEY_PASS} --out $@ build/gen/${BUNDLE_NAME}.aligned.apk + +build/gen/${BUNDLE_NAME}.aligned.apk: build/gen/${BUNDLE_NAME}.unsigned.apk + ${ZIPALIGN} -f -p 4 $^ $@ + +build/gen/${BUNDLE_NAME}.unsigned.apk: build/apk/classes.dex src/AndroidManifest.xml | build/gen + ${AAPT} package -f -M src/AndroidManifest.xml $(foreach jar,$(JARS),-I $(jar)) -F $@ build/apk + +build/apk/classes.dex: build/apk/my_classes.jar + cd build/apk && ${D8} --min-api ${MIN_API} ../../$^ ${JARS} && cd ../.. + +build/apk/my_classes.jar: $(foreach class,$(CLASSES),build/obj/$(CLASSES_PATH)/$(class).class) | build/apk + ${D8} $(foreach class,$(CLASSES),'build/obj/$(CLASSES_PATH)/$(class).class') --min-api ${MIN_API} --output $@ --no-desugaring + +build/obj/${CLASSES_PATH}/MainActivity.class: src/MainActivity.java | build/obj + ${JAVAC} -classpath "$(subst $() $(),:,$(JARS))" -d build/obj $^ + +build/gen build/obj: + mkdir -p $@ + +clean: + rm -fr build + +install: build/${BUNDLE_NAME}.apk + [ -n "`${ADB} shell pm list packages | grep ^package:${JAVA_PACKAGE_NAME}$`" ] && ${ADB} uninstall ${JAVA_PACKAGE_NAME}; exit 0 + ${ADB} install $^ + +.PHONY: all clean install diff --git a/templates/android-make/tibia-index.js b/templates/android-make/tibia-index.js new file mode 100644 index 0000000..53d79fb --- /dev/null +++ b/templates/android-make/tibia-index.js @@ -0,0 +1,4 @@ +module.exports = function (data, api) { + api.copyFile(`Makefile`, `Makefile`); + api.generateFileFromTemplateFile(`vars.mk`, `vars.mk`, data); +}; diff --git a/templates/android-make/vars.mk b/templates/android-make/vars.mk new file mode 100644 index 0000000..746e222 --- /dev/null +++ b/templates/android-make/vars.mk @@ -0,0 +1,22 @@ +BUNDLE_NAME := {{=it.product.bundleName}} +JAVA_PACKAGE_NAME := {{=it.android.javaPackageName}} + +KEY_STORE := {{=it.android_make.keyStore}} +KEY_ALIAS := {{=it.android_make.keyAlias}} +STORE_PASS := {{=it.android_make.storePass}} +KEY_PASS := {{=it.android_make.keyPass}} + +ANDROID_SDK_DIR := {{=it.android_make.sdkDir}} +BUILD_TOOLS_DIR := ${ANDROID_SDK_DIR}/build/tools/{{=it.android_make.buildToolsVersion}} +ANDROIDX_DIR := {{=it.android_make.androidxDir}} +KOTLIN_DIR := {{=it.android_make.kotlinDir}} + +ANDROID_JAR_FILE := ${ANDROID_SDK_DIR}/platforms/android-{{=it.android_make.androidVersion}}/android.jar +ANDROIDX_CORE_FILE := ${ANDROIDX_DIR}/core-{{=it.android_make.androidxCoreVersion}}.jar +ANDROIDX_LIFECYCLE_COMMON_FILE := ${ANDROIDX_DIR}/lifecycle-common-{{=it.android_make.androidxLifecycleCommonVersion}}.jar +ANDROIDX_VERSIONEDPARCELABLE_FILE := ${ANDROIDX_DIR}/versionedparcelable-{{=it.android_make.androidxVersionedparcelableVersion}}.jar +KOTLIN_STDLIB_FILE := ${KOTLIN_DIR}/kotlin-stdlib-{{=it.android_make.kotlinStdlibVersion}}.jar +KOTLINX_COROUTINES_CORE_FILE := ${KOTLIN_DIR}/kotlinx-coroutines-core-{{=it.android_make.kotlinxCoroutinesCoreVersion}}.jar +KOTLINX_COROUTINES_CORE_JVM_FILE := ${KOTLIN_DIR}/kotlinx-coroutines-core-jvm-{{=it.android_make.kotlinxCoroutinesCoreJVMVersion}}.jar + +HAS_MIDI_IN := {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0 ? "yes" : "no"}} diff --git a/templates/android/data/AndroidManifest.xml b/templates/android/data/AndroidManifest.xml new file mode 100644 index 0000000..603c4d7 --- /dev/null +++ b/templates/android/data/AndroidManifest.xml @@ -0,0 +1,21 @@ + + +{{?it.product.buses.filter(x => x.type == "audio" && x.direction == "input").length > 0}} + +{{?}} +{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}} + +{{??}} + +{{?}} + + + + + + + + + + + diff --git a/templates/android/src/MainActivity.java b/templates/android/src/MainActivity.java new file mode 100644 index 0000000..437f522 --- /dev/null +++ b/templates/android/src/MainActivity.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023, 2024 Orastron Srl unipersonale + */ + +package {{=it.android.javaPackageName}}; + +import android.app.Activity; +import android.os.Bundle; +import android.webkit.WebView; +import android.webkit.WebSettings; +import android.webkit.WebChromeClient; +import android.webkit.WebViewClient; +import android.webkit.JavascriptInterface; +import android.content.Context; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; + +public class MainActivity extends Activity { +/* + static { + System.loadLibrary("{{=it.android.javaPackageName}}"); + } + + public native boolean nativeAudioStart(); + public native void nativeAudioStop(); + public native float nativeGetParameter(int i); + public native void nativeSetParameter(int i, float v); + + private WebView webView; + + public class WebAppInterface { + @JavascriptInterface + public boolean hasAudioPermission() { + return MainActivity.this.checkCallingOrSelfPermission(android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + } + + @JavascriptInterface + public void requestAudioPermission() { + ActivityCompat.requestPermissions(MainActivity.this, new String[] { android.Manifest.permission.RECORD_AUDIO }, 0); + } + + @JavascriptInterface + public boolean audioStart() { + return nativeAudioStart(); + } + + @JavascriptInterface + public void audioStop() { + nativeAudioStop(); + } + + @JavascriptInterface + public float getParameter(int i) { + return nativeGetParameter(i); + } + + @JavascriptInterface + public void setParameter(int i, float v) { + nativeSetParameter(i, v); + } + } +*/ + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + webView = new WebView(this); + setContentView(webView); + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webView.setWebChromeClient(new WebChromeClient()); + webView.setWebViewClient(new WebViewClient()); + webSettings.setDomStorageEnabled(true); + webView.addJavascriptInterface(new WebAppInterface(), "Android"); + //webView.loadUrl("file:///android_asset/index.html"); + webView.loadUrl("https://www.orastron.com/"); + } + +/* + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (grantResults.length > 0) + webView.loadUrl("javascript:gotAudioPermission()"); + } +*/ +} diff --git a/templates/android/tibia-index.js b/templates/android/tibia-index.js new file mode 100644 index 0000000..bf395d0 --- /dev/null +++ b/templates/android/tibia-index.js @@ -0,0 +1,7 @@ +var path = require("path"); +var sep = path.sep; + +module.exports = function (data, api) { + api.generateFileFromTemplateFile(`data${sep}AndroidManifest.xml`, `data${sep}AndroidManifest.xml`, data); + api.generateFileFromTemplateFile(`src${sep}MainActivity.java`, `src${sep}MainActivity.java`, data); +}; diff --git a/test/android-make.json b/test/android-make.json new file mode 100644 index 0000000..364b5b6 --- /dev/null +++ b/test/android-make.json @@ -0,0 +1,19 @@ +{ + "android_make": { + "keyStore": "keystore.jks", + "keyAlias": "androidkey", + "storePass": "android", + "keyPass": "android", + "sdkDir": "${HOME}/Android/Sdk", + "buildToolsVersion": "34.0.0", + "androidxDir": "{HOME}/Android/androidx", + "kotlinDir": "${HOME}/Android/kotlin", + "androidVersion": "34", + "androidxCoreVersion": "1.10.1", + "androidxLifecycleCommonVersion": "2.6.1", + "androidxVersionedparcelableVersion": "1.1.1", + "kotlinStdlibVersion": "1.9.0", + "kotlinxCoroutinesCoreVersion": "1.7.3", + "kotlinxCoroutinesCoreJVMVersion": "1.7.3" + } +} diff --git a/test/android.json b/test/android.json new file mode 100644 index 0000000..8ff614f --- /dev/null +++ b/test/android.json @@ -0,0 +1,5 @@ +{ + "android": { + "javaPackageName": "com.example.tibia_test" + } +} diff --git a/test/keystore.jks b/test/keystore.jks new file mode 100644 index 0000000..b4854b1 Binary files /dev/null and b/test/keystore.jks differ diff --git a/test/run.sh b/test/run.sh index 539d929..3206107 100755 --- a/test/run.sh +++ b/test/run.sh @@ -13,3 +13,7 @@ $dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/web $dir/../ $dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/web-make $dir/../out/web $dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/web-demo $dir/../out/web cp $dir/plugin.h $dir/../out/web/src + +$dir/../tibia $dir/product.json,$dir/company.json,$dir/android.json $dir/../templates/android $dir/../out/android +$dir/../tibia $dir/product.json,$dir/company.json,$dir/android.json,$dir/android-make.json $dir/../templates/android-make $dir/../out/android +cp $dir/keystore.jks $dir/../out/android