From 0ec843535775671130beca14adc3cbf98cec05e9 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sun, 30 Sep 2018 13:42:17 +0200 Subject: [PATCH] implement android import, update android project and build for release --- .gitignore | 2 + android/build.gradle | 72 +++++++++-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/ppkey.jks | Bin 0 -> 2285 bytes android/src/main/AndroidManifest.xml | 6 +- android/src/main/cpp/main.cpp | 115 +++++++++++++++++- .../com/omixlab/panopainter/MainActivity.java | 49 ++++++++ .../com/omixlab/panopainter/PathUtil.java | 94 ++++++++++++++ src/app_events.cpp | 3 +- 9 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 android/ppkey.jks create mode 100644 android/src/main/java/com/omixlab/panopainter/MainActivity.java create mode 100644 android/src/main/java/com/omixlab/panopainter/PathUtil.java diff --git a/.gitignore b/.gitignore index e2e8d4d..e0a80cf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ panopainter-log.txt *.pano frames/ src/version.gen.h +com_omixlab_panopainter_MainActivity.h +android/release diff --git a/android/build.gradle b/android/build.gradle index 8a5144b..7d1c96d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,21 +1,49 @@ buildscript { repositories { + google() jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:3.2.0' + classpath "gradle.plugin.com.gladed.gradle.androidgitversion:gradle-android-git-version:0.4.5" } } -apply plugin: 'android' + +allprojects { + repositories { + google() // and here + jcenter() + } +} + +apply plugin: "com.android.application" +apply plugin: "com.gladed.androidgitversion" + +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) +def sdkDir = properties.getProperty("sdk.dir") +def classpath = "build/intermediates/classes/debug:" + sdkDir + "/platforms/android-26/android.jar" +def activity = "com.omixlab.panopainter.MainActivity" +def outpath = "src/main/cpp" + +def ver_branch = "git rev-parse --abbrev-ref HEAD".execute().text.trim() +def ver_hash = "git log --pretty=format:%h -n 1".execute().text.trim() +def ver_count = Integer.parseInt("git rev-list --count HEAD".execute().text.trim()) +def ver_tag = "git describe --tags --abbrev=0".execute().text.trim() android { - compileSdkVersion 19 - buildToolsVersion '25.0.0' + compileSdkVersion 26 + android.buildToolsVersion "28.0.3" defaultConfig { - applicationId = 'com.omigamedev' - minSdkVersion 9 - targetSdkVersion 19 + applicationId = 'com.omixlab.panopainter' + minSdkVersion 19 + targetSdkVersion 26 + versionCode ver_count + versionName "${ver_tag}.${ver_count}" // This block is different from the one you use to link Gradle // to your CMake or ndk-build script. externalNativeBuild { @@ -27,11 +55,11 @@ android { //cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2" // Sets a flag to enable format macro constants for the C++ compiler. //cppFlags "-D__STDC_FORMAT_MACROS" - arguments '-DANDROID_PLATFORM=android-19', - '-DANDROID_TOOLCHAIN=clang', - '-DANDROID_STL=gnustl_static', - '-DCMAKE_BUILD_TYPE=Release', - '-DANDROID_ARM_NEON=TRUE' + arguments '-DANDROID_TOOLCHAIN=clang', + //'-DANDROID_PLATFORM=android-19', + '-DANDROID_STL=gnustl_static', + '-DCMAKE_BUILD_TYPE=Release', + '-DANDROID_ARM_NEON=TRUE' } } ndk { @@ -52,10 +80,20 @@ android { // } // } + signingConfigs { + release { + storeFile file("ppkey.jks") + storePassword "om4r@Pano#Painter_2018" + keyAlias "PanoPainter" + keyPassword "om4r@Pano#Painter_2018" + } + } + buildTypes { release { minifyEnabled false proguardFile getDefaultProguardFile('proguard-android.txt') + signingConfig signingConfigs.release } } @@ -80,3 +118,13 @@ task generateVersioning(type: Exec) { commandLine 'python', 'scripts/pre-build.py', 'release' } copyFiles.dependsOn(generateVersioning) + +task generateJavaH(type: Exec) { + workingDir "$projectDir" + commandLine 'javah', "-jni", "-force", "-cp", "$classpath", "-d", "$outpath", "$activity" +} +generateVersioning.dependsOn(generateJavaH) + +repositories { + google() +} \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b8608bd..cb2d279 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/android/ppkey.jks b/android/ppkey.jks new file mode 100644 index 0000000000000000000000000000000000000000..0e2fae08f8f81c80e778fc0d9969e1eeedbd23a2 GIT binary patch literal 2285 zcmc(g`8(8$7sqGDm@<0{SUrBoFC5XIp=xK^FHT!pMB0g2Lu8^xB>nr5SM(Y z!Iyl<)KF3gm-#7EvX~$cF9@JPcL5{}t_lM~!730zFqjtvr9l@JEfDE%OE``+@sMys zN#3C|zapxP@3imbFpjr%Mfy#@-Xqu)J-w|fxcm4NeqA!6I8?qB<%O{qcd%W)83`HA z9qc60ui{!r$VXB9e%T(tXA9>i54W#+{}|IWTKH4U>m+2XnaL~t7P&ICnirO!zI^mK zk|HtblCr4h>hJrR{oQFZ%MAH$^N6rUHJ{nj2at_bsp8RbuhvF$H~;yj-jP)H?L(s&dd9D=gJ8hzj98Ea2jx- zl%x3%r0L(=Zh@q1-+y|679~LQ>C6k;&~x_|tVk!)vb8r-kJJ~k&W=>?MfB7yNu7UT z8KBvrbIYl(F-)dZ(1{|~-iOMNf)i%EBPCC&_k3sGypzuREk;8PZ@sz~^KzhHC$E8U zOtiF*{vi!JDb`W^1D+qE6kHJ88@mn`taf~;7?P1>YKaH4YmDeD>95C*Lc3fxGp|fa z2zGCvzJ1Z>|JeA@Ly@GA@*cI-r*dL`rS1&gm0GD;TB7thQ^WIN^B*fgip%k`5_VJG z9^z{6^2K)ga_eIhWzC80yh~ET;%1bCoDYVPZdG+kWqw5Fu zE_+mG*jTSFk@bo0W|hz#o2>6zt@6HC<;;y590kUC#^vn@kbKm6GpS9Q_Thibk=ITA zB%lWZ@%#?Zcya+6h1Pp;dWtDoZ0e%>tN~}%n7C#t_;2LHW1_AKFcwC?`6vPFG zIm~rWZgLQHz>?}8sTg5E=x+c=Asi{>YxX`D(DtFk|4{@A zVM-;21e1wq3sM*c4M_i8%Yzbir1*rO9fAXVC?q1D9LxJnElhX80%evB@lrwIT$ zT39S_PD}s`XmbHZ=fB|pJ|PV(`)|>>ABBeo7T~@bFdRYygFzEeV~;*J+De`sdA-ur zU(SG?+NY|~ereBg8@>s^4~4rX?>%qqskS?pfL>`$%sC*`vOTvpNt2r2uh__rxTc9? z8{QWfD)fu|sgK^3UCN8*K=!w17yIk+FCAl^Sz2}&M5b32bYmY0@+Ax081m>s>JIj# zIj@{nA4$ED;vke2GZkZb8nRklP2jiwsd0FIQwj5MOp?s9>N0#fuG3$#{enruBDLBBT!)i zEmW%&+%uhMC|)(!`)n?7)|dWo5xJMg6%h;)UC*h&rv)%0mMd{9{mw!Dl8TC18e+9` z8^lCM^(ppBR|nW*Y( @@ -9,13 +9,11 @@ - diff --git a/android/src/main/cpp/main.cpp b/android/src/main/cpp/main.cpp index 3f6dd87..1353c92 100755 --- a/android/src/main/cpp/main.cpp +++ b/android/src/main/cpp/main.cpp @@ -29,6 +29,7 @@ #include "asset.h" #include "keymap.h" #include "main.h" +#include "com_omixlab_panopainter_MainActivity.h" typedef void (*GLDEBUGPROC)(GLenum source, GLenum type, @@ -51,6 +52,12 @@ typedef void (*fnDebugMessageCallback)(GLDEBUGPROC callback, const void* userPar EGLDisplay g_display = EGL_NO_DISPLAY; EGLContext g_context = EGL_NO_CONTEXT; +jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) +{ + LOG("JNI_OnLoad"); + return JNI_VERSION_1_6; +} + std::string utf8chr(int cp) { char c[5]={ 0x00,0x00,0x00,0x00,0x00 }; @@ -226,6 +233,100 @@ void displayKeyboard(android_app* mApplication, bool pShow) lJavaVM->DetachCurrentThread(); } +/* + * Class: com_omixlab_panopainter_MainActivity + * Method: pickFileCallback + * Signature: (Ljava/lang/String;)V + */ +std::function pick_file_callback; +std::function pick_file_callback_context; +extern "C" +{ +JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_pickFileCallback(JNIEnv *env, jobject, jstring path) +{ + const char* path_utf = env->GetStringUTFChars(path, nullptr); + std::string file_path = path_utf; // create a copy + env->ReleaseStringUTFChars(path, path_utf); + + LOG("received %s", file_path.c_str()); + pick_file_callback_context = [file_path] + { + if (pick_file_callback) + { + LOG("callback"); + pick_file_callback(file_path); + pick_file_callback = nullptr; + } + }; +} +} + +void pick_file(android_app* mApplication, std::function callback) +{ + // Attaches the current thread to the JVM. + jint lResult; + jint lFlags = 0; + + JavaVM* lJavaVM = mApplication->activity->vm; + JNIEnv* lJNIEnv = mApplication->activity->env; + + JavaVMAttachArgs lJavaVMAttachArgs; + lJavaVMAttachArgs.version = JNI_VERSION_1_6; + lJavaVMAttachArgs.name = "NativeThread"; + lJavaVMAttachArgs.group = NULL; + + lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs); + if (lResult == JNI_ERR) + { + return; + } + + pick_file_callback = callback; + + // Retrieves NativeActivity. + jobject lNativeActivity = mApplication->activity->clazz; + jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity); + + jmethodID MethodPickFile = lJNIEnv->GetMethodID(ClassNativeActivity, "pickFile", "()V"); + lJNIEnv->CallVoidMethod(lNativeActivity, MethodPickFile); + + // Finished with the JVM. + lJavaVM->DetachCurrentThread(); +} + + +float get_display_density(android_app* mApplication) +{ + // Attaches the current thread to the JVM. + jint lResult; + jint lFlags = 0; + + JavaVM* lJavaVM = mApplication->activity->vm; + JNIEnv* lJNIEnv = mApplication->activity->env; + + JavaVMAttachArgs lJavaVMAttachArgs; + lJavaVMAttachArgs.version = JNI_VERSION_1_6; + lJavaVMAttachArgs.name = "NativeThread"; + lJavaVMAttachArgs.group = NULL; + + lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs); + if (lResult == JNI_ERR) + { + return 1; + } + + // Retrieves NativeActivity. + jobject lNativeActivity = mApplication->activity->clazz; + jclass ClassNativeActivity = lJNIEnv->GetObjectClass(lNativeActivity); + + jmethodID MethodPickFile = lJNIEnv->GetMethodID(ClassNativeActivity, "getDensity", "()F"); + float density = lJNIEnv->CallFloatMethod(lNativeActivity, MethodPickFile); + + // Finished with the JVM. + lJavaVM->DetachCurrentThread(); + return density; +} + /** * Initialize an EGL context for the current display. */ @@ -376,12 +477,17 @@ static int engine_init_display(struct engine* engine) { engine->height = h; engine->state.angle = 0; + float density = get_display_density(engine->app); + LOG("density %f", density); + App::I.zoom = density / 1.5; + g_display = display; g_context = context; if (resuming_context) { LOG("RESUME APP"); + App::I.and_app = engine->app; return 0; } @@ -855,11 +961,12 @@ void android_main(struct android_app* state) { } //if (engine.animating) + if (engine.display != EGL_NO_DISPLAY) { - // Done with events; draw next animation frame. - engine.state.angle += .01f; - if (engine.state.angle > 1) { - engine.state.angle = 0; + if (pick_file_callback_context) + { + pick_file_callback_context(); + pick_file_callback_context = nullptr; } // Drawing is throttled to the screen update rate, so there diff --git a/android/src/main/java/com/omixlab/panopainter/MainActivity.java b/android/src/main/java/com/omixlab/panopainter/MainActivity.java new file mode 100644 index 0000000..c9566e5 --- /dev/null +++ b/android/src/main/java/com/omixlab/panopainter/MainActivity.java @@ -0,0 +1,49 @@ +package com.omixlab.panopainter; + +import android.app.NativeActivity; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; + +import java.io.File; +import java.net.URISyntaxException; + +public class MainActivity extends NativeActivity { + static { + System.loadLibrary("native-lib"); + } + + public native void pickFileCallback(String path); + + public float getDensity() + { + return getResources().getDisplayMetrics().density; + } + + public void pickFile() + { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(Intent.createChooser(intent, "Select a file"), 1); + Log.v("PICK", "start"); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if(requestCode == 1 && resultCode == RESULT_OK) { + Uri uri = data.getData(); + //File myFile = new File(uri.getPath()); + //String path = myFile.getAbsolutePath(); + String path = null; + try { + path = PathUtil.getPath(this, uri); + Log.v("PICK", "selected " + path); + pickFileCallback(path); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + } +} diff --git a/android/src/main/java/com/omixlab/panopainter/PathUtil.java b/android/src/main/java/com/omixlab/panopainter/PathUtil.java new file mode 100644 index 0000000..02b51a8 --- /dev/null +++ b/android/src/main/java/com/omixlab/panopainter/PathUtil.java @@ -0,0 +1,94 @@ +package com.omixlab.panopainter; +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +import java.net.URISyntaxException; + +/** + * Created by Aki on 1/7/2017. + */ + +public class PathUtil { + /* + * Gets the file path of the given Uri. + */ + @SuppressLint("NewApi") + public static String getPath(Context context, Uri uri) throws URISyntaxException { + final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19; + String selection = null; + String[] selectionArgs = null; + // Uri is different in versions after KITKAT (Android 4.4), we need to + // deal with different Uris. + if (needToCheckUri && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + uri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + } else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + if ("image".equals(type)) { + uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + selection = "_id=?"; + selectionArgs = new String[]{ split[1] }; + } + } + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = { MediaStore.Images.Media.DATA }; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + } + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} \ No newline at end of file diff --git a/src/app_events.cpp b/src/app_events.cpp index e70d89f..1290527 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -3,6 +3,7 @@ #ifdef __ANDROID__ void displayKeyboard(android_app* mApplication, bool pShow); +void pick_file(android_app* mApplication, std::function callback); #elif _WIN32 std::string win32_open_file(); #endif @@ -55,7 +56,7 @@ void App::pick_image(std::function callback) callback(path); }); #elif __ANDROID__ - //displayKeyboard(and_app, false); + pick_file(and_app, callback); #elif _WIN32 std::string path = win32_open_file(); if (!path.empty())