fopen/fread APK Assets from NativeActivity on Android - 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.

Related

Load file using C++ std::ifstream.getline() in 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".

Android NDK: Read large file using AAssetManager

I am trying to read in a large file using asset manager in Android NDK.
The problem is that the code that I have written is not reading the entire content. Rather only a portion of it. When I try to achieve the same functionality using Java, it is giving me correct results.
This is the code that I have written:
std::string file = hats::files::SOURCE_DATASET_FILENAME;
AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);
AAsset *asset = AAssetManager_open(mgr, file.c_str(), AASSET_MODE_BUFFER);
size_t assetLength = AAsset_getLength(asset);
char *buffer = (char *) malloc(assetLength + 1);
int nbytes{0};
while( (nbytes = AAsset_read(asset, buffer, assetLength)) > 0) {
LOGD("%s", buffer);
AAsset_seek(asset, nbytes, SEEK_CUR);
}
AAsset_close(asset);
return env->NewStringUTF(file.c_str());
I am not able to understand why only partial file is being read. I am not able to find any proper tutorial for Android NDK also.
Please help me out.

Android NDK: Why is AAssetManager_open returning NULL

I have some code:
AAsset* pAsset = AAssetManager_open(pAssetManager, "asset_test.txt", AASSET_MODE_STREAMING);
DebugPrint(pAsset?"pAsset not NULL\n":"pAsset NULL");
if (pAsset)
{
char buf[1024];
AAsset_read(pAsset, buf, sizeof(buf));
DebugPrint(buf);
AAsset_close(pAsset);
}
This code always prints "pAsset NULL" in logcat.
I put the asset_test.txt file in my assets directory, and I looked in the .apk to make sure it exists by renaming the .apk to .zip and opening it with 7zip.
I have some more code:
AAssetDir* pAssetDir = AAssetManager_openDir(pAssetManager, sDirectory.c_str());
if (!pAssetDir)
{
DebugPrint("pAssetDir NULL\n");
return;
}
const char* pszDir;
while ((pszDir = AAssetDir_getNextFileName(pAssetDir)) != NULL)
{
DebugPrint(pszDir);
}
AAssetDir_close(pAssetDir);
This code prints nothing. In other words, no files are ever found in the assets directory, regardless of what paths I pass into it.
Note: DebugPrint is just a prettier looking wrapper around __android_log_print().
I passed the Activity into AAssetManager_fromJava(), while I should have passed the AssetManager in. If you pass the wrong class into AAssetManager_fromJava() it will fail without printing anything to logcat.
How to get the asset manager with JNI:
JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();
jclass activity_class = env->GetObjectClass(activity);
jmethodID activity_class_getAssets = env->GetMethodID(activity_class, "getAssets", "()Landroid/content/res/AssetManager;");
jobject asset_manager = env->CallObjectMethod(activity, activity_class_getAssets); // activity.getAssets();
global_asset_manager = env->NewGlobalRef(asset_manager);
pAssetManager = AAssetManager_fromJava(env, global_asset_manager);
Stash that asset manager pointer somewhere and use it for all your AAssetManager_*() functions from now on.

Cannot load TTF file directly from a ZIP using libzip and FreeType

I'm trying to load a TTF file directly from a ZIP archive, using libzip and FreeType.
In particular, I'm using the FT_Open_Face function which can read from custom read/close functions (ft_zip_read and ft_zip_close). But although the file is apparently fully read, FT_Open_Face returns FT_Err_Unknown_File_Format. Opening the same file directly from the disk works fine.
I really don't know how to debug this, can anybody help?
The only thing I can imagine to be the issue right now is that my ft_zip_read function does not support seeking, the documentation says:
This function might be called to perform a seek or skip operation with
a ‘count’ of 0. A non-zero return value then indicates an error.
And it is indeed called with count 0 a couple of times, but I can't see any way to do a seek with libzip.
unsigned long ft_zip_read(FT_Stream stream, unsigned long offset,
unsigned char* buffer, unsigned long count)
{
zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
return zip_fread(file, buffer + offset, count);
}
void ft_zip_close(FT_Stream stream)
{
zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
zip_fclose(file);
}
FT_Face load_zipped_face(const std::string& name, unsigned int size,
const std::string& zip_path)
{
FT_Library library;
FT_Error error = FT_Init_FreeType(&library);
if (error)
throw freetype_error_string("Failed to initialise FreeType", error);
int zip_error;
zip* zip = zip_open(zip_path.c_str(), 0, &zip_error);
if (!zip) {
std::ostringstream message_stream;
message_stream << "Error loading ZIP (" << zip_path << "): "
<< zip_error;
throw message_stream.str();
}
std::string face_path = name + ".ttf";
struct zip_stat stat;
if (zip_stat(zip, face_path.c_str(), 0, &stat))
throw std::string("zip_stat failed");
zip_file* file = zip_fopen(zip, face_path.c_str(), 0);
if (file == 0)
throw face_path + ": " + strerror(errno);
FT_StreamDesc descriptor;
descriptor.pointer = file;
FT_StreamRec* stream = new FT_StreamRec;
stream->base = 0;
stream->size = stat.size;
stream->descriptor = descriptor;
stream->read = &ft_zip_read;
stream->close = &ft_zip_close;
FT_Open_Args open_args;
open_args.flags = FT_OPEN_STREAM;
open_args.stream = stream;
FT_Face face;
error = FT_Open_Face(library, &open_args, 0, &face);
zip_close(zip);
if (error == FT_Err_Unknown_File_Format)
throw std::string("Unsupported format");
else if (error)
throw freetype_error_string("Unknown error loading font", error);
error = FT_Set_Pixel_Sizes(face, 0, size);
if (error)
throw freetype_error_string("Unable to set pixel sizes", error);
return face;
}
Seeking the truth
To be able to seek in a compressed datastream you need to decompress the stream up until the point you wish to seek to (some exceptions exist like streams that have reset markers and indexes only have to decompress since the previous marker). This is just plain inefficient if done often (not to mention that you need to write code for it yourself).
Now thinking about it, the only reason you'd want to not load the entire face into memory and have a custom IO for a font file is if it is too big to keep in memory; so that makes seeking mandatory for the stream IO interface of FT.
What can you do?
If the file is small enough: Read it all into memory and use FT_New_Memory_Face to load the face from memory.
If the file is too big so that you don't want the entire face in memory at once, extract the font file to a temporary file and read from that. (Use windows/unix/cstdio temp file API to have a well behaved temporary file)
If neither of the above suits you then you can implement your own caching and seekable zip stream on top of libzip and pass that to FT. This is probably cumbersome and involves some work so I'd go with one of the other two personally :)

How to check the device running API level using c code via NDK?

I'm starting to work on Android with the NDK and I want to check what Android API level the device is running on my c code. How can I do that?
At first I thought I could use definition __ANDROID_API__ under /android/api-level.h but that was a wrong assumption.
**Note: I'm NOT asking how to check API level via java.
I've just been working on some JNI code and wanted to query the running OS build version as described by Jona. I wanted to do this as early as possible (ie in JNI_OnLoad) so would rather not hand it in from Java as described by FUBUs. Since API Level 4 this information has been available as the int field SDK_INT in android.os.Build.VERSION which is what I'm looking up in this snippet:
static const char* TAG = "testjnjni";
static bool _disableHttpKeepAliveOnBuggyPlatforms(JNIEnv *env)
{
// Based on article here:
// http://android-developers.blogspot.co.uk/2011/09/androids-http-clients.html
// Which references the issue documented here:
// http://code.google.com/p/android/issues/detail?id=2939
// We need to set "http.keepAlive" to "false" if running an OS version earlier than Froyo (API Level 8)
if ((*env)->ExceptionCheck(env))
return false; // already got an exception pending
bool success = true;
// VERSION is a nested class within android.os.Build (hence "$" rather than "/")
jclass versionClass = (*env)->FindClass(env, "android/os/Build$VERSION");
if (NULL == versionClass)
success = false;
jfieldID sdkIntFieldID = NULL;
if (success)
success = (NULL != (sdkIntFieldID = (*env)->GetStaticFieldID(env, versionClass, "SDK_INT", "I")));
jint sdkInt = 0;
if (success)
{
sdkInt = (*env)->GetStaticIntField(env, versionClass, sdkIntFieldID);
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "sdkInt = %d", sdkInt);
}
if (success && sdkInt < 8)
{
jclass systemClass = (*env)->FindClass(env, "java/lang/System");
if (NULL == systemClass)
success = false;
jmethodID setPropertyMethodID = NULL;
if (success)
success = (NULL != (setPropertyMethodID = (*env)->GetStaticMethodID(env, systemClass, "setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")));
jstring propString = NULL;
if (success)
success = (NULL != (propString = (*env)->NewStringUTF(env, "http.keepAlive")));
jstring valueString = NULL;
if (success)
success = (NULL != (valueString = (*env)->NewStringUTF(env, "false")));
jobject oldValueString = NULL;
if (success)
{
__android_log_print(ANDROID_LOG_VERBOSE, TAG, "Disabling http.keepAlive");
oldValueString = (*env)->CallStaticObjectMethod(env, systemClass, setPropertyMethodID, propString, valueString);
}
// cleanup
(*env)->DeleteLocalRef(env, propString);
(*env)->DeleteLocalRef(env, valueString);
(*env)->DeleteLocalRef(env, oldValueString);
(*env)->DeleteLocalRef(env, systemClass);
}
// cleanup
(*env)->DeleteLocalRef(env, versionClass);
return success;
}
All the information I needed to write this code is clearly documented in the PDF entitled "The Java Native Interface: Programmer's Guide and Specification" by Sheng Liang which used to be available from Oracle's site here but can also be purchased as a book (e.g. here). JNI is a very powerful technology and I would strongly recommend any developer wanting to get to grips with it reads that PDF as well as the Android Developers' JNI Tips.
Oh, and finally, it cannot be stressed how important it is to understand local and global references. Android's Developers blog has a good article here covering changes in ICS (nothing that veers away from the JNI specification but good points to reiterate!).
There is exists fully native solutions:
Prior Android L you can use __system_property_get("ro.build.version.sdk") from sys/system_properties.h
For Android L and newer you can use popen("getprop ro.build.version.sdk")
Use the compile time toolchain constant check #if __ANDROID_API__ < 21 to switch between those two implementations.
You can pass one time, the API Level what you get in JAVA to the C code and stock it in global variable. For me, is the easier way to do that.

Categories

Resources