Load file using C++ std::ifstream.getline() in Android - android

I'm struggling with reading file stream inside Android environment using C++ library.
I believe I set all the permissions correctly (1st figure) and I'm getting the file path using Android internal library. Would you please give me a snippet to correctly read a file using std::ifstream.getline()?
For instance, I get "/document/1EF7-1509:Download/015_1440.jpg" for the image file existing in the 2nd figure under "Download" folder. This path is a raw value returned by "Intent.getData().getPath()" with "ACTION_GET_CONTENT".
extern "C" JNIEXPORT jstring JNICALL Java_com_example_testpplication_MainActivity_testGeneralEncryption(JNIEnv* env, jobject, jstring myString)
{
const char *nativeString = env->GetStringUTFChars(myString, nullptr);
std::string filePath = std::string(nativeString);
std::string buffer;
std::ifstream fStreamIn(filePath);
if(fStreamIn.is_open())
{
std::getline(fStreamIn, buffer);
}
else
{
bool exists = fStreamIn.good();
if(exists)
{
buffer = "Exists";
}
else
{
buffer = "Non-existing";
}
}
return env->NewStringUTF((buffer + ":" + filePath).c_str());
}

Thanks to #blackapps, I did a google with a different keyword and found below answer. Basically, my question is a duplicate.
Inspired by this answer:
https://stackoverflow.com/a/49221353/1770003
The idea is, Android doesn't directly return the absolute path as other OS reports like Windows. A different approach is needed.
The approach I took is,
Add this in my solution: https://android-arsenal.com/details/1/8142#!package
Edit the project: https://github.com/onimur/handle-path-oz/wiki/Java-Single-Uri
In my case, I moved the caller into onRequestHandPathOz which is add by implementing "HandlePathOzListener.SingleUri".
#Override
public void onRequestHandlePathOz(PathOz pathOz, Throwable throwable)
{
txt_pathShow.setText(testGeneralEncryption(pathOz.getPath()));
}
The method "testGeneralEncryption" is defined in the original question and pathOz.getPath() is passed as an argument to the parameter "jstring myString".

Related

JNI DETECTED ERROR IN APPLICATION: use of invalid jobject when returning Array of String

Description of the intended goal
I'm trying to implement OpenSSL-generated public/private key pairs in Android/Kotlin using JNI, in order to implement user encryption on the information stored to the cloud server. I've compiled all OpenSSL source code and generated all .so files correcly.
The code (C++)
The C++ code to use OpenSSL is shown below. CmakeLists.txt and NDK configuration is working fine.
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_eu_joober_ui_entry_SplashFragment_generateRSAKeyPair(JNIEnv *env, jobject thiz) {
int ret = 0;
RSA *r = nullptr;
BIGNUM *bne = nullptr;
BIO *bp_public = nullptr, *bp_private = nullptr;
int bits = 2048;
unsigned long e = RSA_F4;
jstring public_key_text;
jstring private_key_text;
jobjectArray returnPair = env->NewObjectArray(2, env->FindClass("java/lang/String"),nullptr);
// 1. generate rsa key
bne = BN_new();
ret = BN_set_word(bne,e);
if(ret != 1){
goto free_all;
}
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, nullptr);
if(ret != 1){
goto free_all;
}
// 2. get public key
bp_public = BIO_new(BIO_s_mem());
ret = PEM_write_bio_RSAPublicKey(bp_public, r);
BIO_get_mem_data(bp_public, &public_key_text);
if(ret != 1){
goto free_all;
}
// 3. get private key
bp_private = BIO_new(BIO_s_mem());
ret = PEM_write_bio_RSAPrivateKey(bp_private, r, nullptr, nullptr, 0, nullptr, nullptr);
BIO_get_mem_data(bp_private, &private_key_text);
// Check public and private keys were generated correctly
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Public key is: \n%s",public_key_text);
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Private key is: \n%s",private_key_text);
// 4. free
free_all:
BIO_free_all(bp_public);
BIO_free_all(bp_private);
RSA_free(r);
BN_free(bne);
// 5. Return strings using jobjectArray
if (ret == 1) {
env->SetObjectArrayElement(returnPair, 0, public_key_text);
env->SetObjectArrayElement(returnPair, 1, private_key_text);
return returnPair;
}
else {
return nullptr;
}
}
The error
If I check the Android Logcat, both public and private key seem to be generated correctly (as per __android_log_print output) but the app crashes with the following error when env->SetObjectArrayElement(returnPair, 0, public_key_text); is called:
JNI DETECTED ERROR IN APPLICATION: use of invalid jobject
The IDE (Android Studio) does not complain on any error, and the log suggests that key pair is being generated correctly, but I don't know why the keys are not being stored in the jobjectArray correctly. In fact, if I just simply put:
env->SetObjectArrayElement(returnPair, 0, env->NewStringUTF("Hello"));
env->SetObjectArrayElement(returnPair, 1, env->NewStringUTF("World"));
the code works completely fine, my Kotlin code gets the Strings correctly ("Hello" and "World"), and the app does not crash, which makes me think that problem is only on the C++ side.
The question
What I am doing wrong? I have checked other SO questions like JNI converting jstring to char * or jstring return in JNI program with slight modifications and combinations with no luck.
SIDE NOTE: I'm using OpenSSL implementation with C++ because Android/Kotlin code does not provide the private key generated using KeyPairGenerator.getInstance() and generatePair() (only public key can be retrieved from Keystore), which I need to be stored in a different place so that user information can be retrieved even if the app is uninstalled, as every subsequent call to generatePair() will lead to a different key pair. If you know a different approach to this problem I am more than welcome to any suggestions you may provide.
You never created a java string out of public_key_text
Try
char * public_key_text;
...
BIO_get_mem_data(bp_public, &public_key_text);
...
env->SetObjectArrayElement(returnPair, 0, env->NewStringUTF(public_key_text));

Load image on Android and process it using halide

I am going to load an image by provided file_path and then downsample it then save it. The whole thing should be done on android device.
I am in trouble to load an image on device and convert to halide::buffer.
Halide::Tools::load_image didn't work on android if I use it like
JNIEXPORT bool JNICALL Java_com_example_boxdownsample_MainActivity_downsample(
JNIEnv *env, jobject obj, jstring file_path) { ...
const char *path = env->GetStringUTFChars(file_path, NULL);
std::string work_path = path;
LOGD("the path is:%s", path);
std::string input_file = work_path + "input.png";
std::string output_file = work_path + "output.png";
Halide::Buffer<uint16_t> input = Halide::Tools::load_image(input_file);//load_image didn't work ....
int ret = box_downsample_halide(input, downsample_factor, output_u16); //box_downsample_halide is a static lib generated by halide generator
... } }
So, am I use it wrong? or
I should load it use java(that would be bitmap format) then encode it to halide::buffer, but this seems a bit hard and indirect.
Is there any easier way to do it?
Thanks!
UPDATE:
I found a way, in case who also need this, please go to my github

Android JNI - Reliable way to convert jstring to wchar_t

In my Android JNI code, I need to convert jstring to wchar_t. The closest reference I found was How do I convert jstring to wchar_t *.
One can obtain jchar* and the length using the following code:
const jchar *raw = env->GetStringChars(string, 0);
jsize len = env->GetStringLength(string);
wchar_t* wStr = new wchar_t[len+1];
It seems I cannot use wcncpy to copy "raw" into "wStr." Although jchar is 2-bytes long, wchar_t is 4 bytes long on all modern versions of Android OS.
One option is to copy one character at a time in a for loop:
for(int i=0;i<len;i++) {
wStr[i] = raw[i];
}
wStr[len] = 0;
The other option would be to call env->GetStringUTFChars() and use iconv_* routines to convert to wchar_t type.
Can someone please confirm if option 1 is valid? Hope I don't have to resort to option 2. Is there a better option? Regards.
wchar_t specifies an element size but not a character set or encoding. Since you are asking about a 32-bit element, can we assume you want to use Unicode/UTF-32? Regardless, once you decide which encoding you want, standard Java libraries are up to the task.
Use a String.getBytes() overload to get an array of bytes. (It is easier to do this in Java rather than JNI, if you have a choice.) Once you have a jbyteArray, you can copy it to a C buffer and cast to wchar_t *.
On Android, you might want Unicode/UTF-8. But that has an 8-bit code-unit so you probably wouldn't be asking about wchar_t. (BTW-a character in UTF-8 can need 1 or more bytes.)
One way would be to use String.getBytes("UTF-32LE"). Note this is making the ASSUMPTION that wchar_t is 4 bytes and little-endian, but this should be a fairly safe assumption to make.
Here's an example that passes a String from Java to C++, where it is converted to std::wstring, reversed, and passed back to Java:
class MyClass {
private native byte[] reverseString(byte[] arr);
String reverseString(String s) {
try {
return new String(reverseString(s.getBytes("UTF-32")), "UTF-32");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "";
}
}
}
On the C++ side you have:
std::wstring toWStr(JNIEnv *env, jbyteArray s)
{
const wchar_t *buf = (wchar_t*) env->GetByteArrayElements(s, NULL);
int n = env->GetArrayLength(s) / sizeof(wchar_t);
// First byte is BOM (0xfeff), so we skip it, hence the "buf + 1".
// There IS NO null-terminator.
std::wstring ret(buf + 1, buf + n);
env->ReleaseByteArrayElements(s, (jbyte*) buf, 0);
return ret;
}
jbyteArray fromWStr(JNIEnv *env, const std::wstring &s)
{
jbyteArray ret = env->NewByteArray((s.size()+1)*sizeof(wchar_t));
// Add the BOM in front.
wchar_t bom = 0xfeff;
env->SetByteArrayRegion(ret, 0, sizeof(wchar_t), (const jbyte*) &bom);
env->SetByteArrayRegion(ret, sizeof(wchar_t), s.size()*sizeof(wchar_t), (const jbyte*) s.c_str());
return ret;
}
extern "C" JNIEXPORT jbyteArray JNICALL Java_MyClass_reverseString(JNIEnv *env, jobject thiz, jbyteArray arr)
{
std::wstring s= toWStr(env, arr);
std::reverse(s.begin(), s.end());
return fromWStr(env, s);
}
I tested it both on my phone, which has Android 4.1.2 and ARM CPU, and on the Android Emulator - Android 4.4.2 and x86 CPU, and this code:
MyClass obj = new MyClass();
Log.d("test", obj.reverseString("hello, здравствуйте, 您好, こんにちは"));
Gave this output:
06-04 17:18:20.605: D/test(8285): はちにんこ ,好您 ,етйувтсвардз ,olleh
As long as all your data is UCS2, you can use option 1. Please see wchar_t for UTF-16 on Linux? for a similar discussion. Note that C++11 provides std::codecvt_utf16 to deal with the situation.
No need to convert. Cast const jchar to (wchar_t *). jni.h define jchar as typedef uint16_t jchar; /* unsigned 16 bits */ which is eventually wchar_t.
You can try this, it worked for me in old project.

Android: Find out which core the thread is running on

I'm trying to write code for Android which would give me some kind of information (id?) of the processor and the core on which a thread is running on.
I've google'd and grep'ed the sources for some inspiration but with no luck. All I know is, that most likely I will need some C/C++ calls.
What I have working is the following:
#include <jni.h>
int getCpuId() {
// missing code
return 0;
}
int getCoreId() {
// missing code
return 0;
}
JNIEXPORT int JNICALL Java_com_spendoptima_Utils_getCpuId(JNIEnv * env,
jobject obj) {
return getCpuId();
}
JNIEXPORT int JNICALL Java_com_spendoptima_Utils_getCoreId(JNIEnv * env,
jobject obj) {
return getCoreId();
}
The whole project compiles and runs just fine. I'm able to call the functions from within Java and I get the proper responses.
Is here anybody who could fill in the blanks?
This is what seems to work for me:
//...
#include <sys/syscall.h>
//...
int getCpuId() {
unsigned cpu;
if (syscall(__NR_getcpu, &cpu, NULL, NULL) < 0) {
return -1;
} else {
return (int) cpu;
}
}
//...
The good news is, the necessary library and system call are defined on Android (sched_getcpu() and __getcpu()). The bad news is, they aren't part of the NDK.
You can roll your own syscall wrapper and library call, using the method shown in this answer.
Another approach is to read /proc/self/stat and parse out the processor entry. The proc(5) man page describes the format:
/proc/[pid]/stat
Status information about the process. This is used by ps(1).
It is defined in /usr/src/linux/fs/proc/array.c.
...
processor %d (since Linux 2.2.8)
CPU number last executed on.
This is much slower, and the "file" format may change if the kernel is updated, so it's not the recommended approach.

fopen/fread APK Assets from NativeActivity on Android

I have only been able to find solutions dated 2010 and earlier. So I wanted to see if there was a more up-to-date stance on this.
I'd like to avoid using Java and purely use C++, to access files (some less-or-more than 1MB) stored away in the APK. Using AssetManager means I can't access files like every other file on every other operating system (including iOS).
If not, is there a method in C++ where I could somehow map fopen/fread to the AssetManager APIs?
I actually found pretty elegant answer to the problem and blogged about it here.
The summary is:
The AAssetManager API has NDK bindings. This lets you load assets from the APK.
It is possible to combine a set of functions that know how to read/write/seek against anything and disguise them as a file pointer (FILE*).
If we create a function that takes an asset name, uses AssetManager to open it, and then disguises the result as a FILE* then we have something that's very similar to fopen.
If we define a macro named fopen we can replace all uses of that function with ours instead.
My blog has a full write up and all the code you need to implement in pure C. I use this to build lua and libogg for Android.
Short answer
No. AFAIK mapping fread/fopen in C++ to AAssetManager is not possible. And if were it would probably limit you to files in the assets folder. There is however a workaround, but it's not straightforward.
Long Answer
It IS possible to access any file anywhere in the APK using zlib and libzip in C++.
Requirements : some java, zlib and/or libzip (for ease of use, so that's what I settled for). You can get libzip here: http://www.nih.at/libzip/
libzip may need some tinkering to get it to work on android, but nothing serious.
Step 1 : retrieve APK location in Java and pass to JNI/C++
String PathToAPK;
ApplicationInfo appInfo = null;
PackageManager packMgmr = parent.getPackageManager();
try {
appInfo = packMgmr.getApplicationInfo("com.your.application", 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Unable to locate APK...");
}
PathToAPK = appInfo.sourceDir;
Passing PathToAPK to C++/JNI
JNIEXPORT jlong JNICALL Java_com_your_app(JNIEnv *env, jobject obj, jstring PathToAPK)
{
// convert strings
const char *apk_location = env->GetStringUTFChars(PathToAPK, 0);
// Do some assigning, data init, whatever...
// insert code here
//release strings
env->ReleaseStringUTFChars(PathToAPK, apk_location);
return 0;
}
Assuming that you now have a std::string with your APK location and you have zlib on libzip working you can do something like this:
if(apk_open == false)
{
apk_file = zip_open(apk_location.c_str(), 0, NULL);
if(apk_file == NULL)
{
LOGE("Error opening APK!");
result = ASSET_APK_NOT_FOUND_ERROR;
}else
{
apk_open = true;
result = ASSET_NO_ERROR;
}
}
And to read a file from the APK:
if(apk_file != NULL){
// file you wish to read; **any** file from the APK, you're not limited to regular assets
const char *file_name = "path/to/file.png";
int file_index;
zip_file *file;
struct zip_stat file_stat;
file_index = zip_name_locate(apk_file, file_name, 0);
if(file_index == -1)
{
zip_close(apk_file);
apk_open = false;
return;
}
file = zip_fopen_index(apk_file, file_index, 0);
if(file == NULL)
{
zip_close(apk_file);
apk_open = false;
return;
}
// get the file stats
zip_stat_init(&file_stat);
zip_stat(apk_file, file_name, 0, &file_stat);
char *buffer = new char[file_stat.size];
// read the file
int result = zip_fread(file, buffer, file_stat.size);
if(result == -1)
{
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
return;
}
// do something with the file
// code goes here
// delete the buffer, close the file and apk
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
Not exactly fopen/fread but it gets the job done. It should be pretty easy to wrap this to your own file reading function to abstract the zip layer.

Categories

Resources