Added support for data directory on Android.

This makes GNUstep use the path returned by Context.getFilesDir() as the basis for storing data (e.g. NSUserDefaults) and when querying system directory paths (NSLibraryDirectory, NSApplicationSupportDirectory, etc.). Requires calling a new GSInitializeProcessAndroid() initialization function.
This commit is contained in:
Frederik Seiffert 2019-09-25 12:11:50 +02:00
parent ae95e859d6
commit d23c5013bf
4 changed files with 113 additions and 31 deletions

View file

@ -1,3 +1,14 @@
2019-09-25 Frederik Seiffert <frederik@algoriddim.com>
* Headers/Foundation/NSProcessInfo.h:
* Source/NSPathUtilities.m:
* Source/NSProcessInfo.m:
Added support for data directory on Android. This makes GNUstep use
the path returned by Context.getFilesDir() as the basis for storing
data (e.g. NSUserDefaults) and when querying system directory paths
(NSLibraryDirectory, NSApplicationSupportDirectory, etc.). Requires
calling a new GSInitializeProcessAndroid() initialization function.
2019-09-25 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSArray.m: [-removeObjectsInArray:] add checks to prevent

View file

@ -30,6 +30,10 @@
#import <Foundation/NSObject.h>
#ifdef __ANDROID__
#include <jni.h>
#endif
#if defined(__cplusplus)
extern "C" {
#endif
@ -265,6 +269,12 @@ DEFINE_BLOCK_TYPE(GSPerformExpiringActivityBlock, void, BOOL);
+ (void) initializeWithArguments: (char**)argv
count: (int)argc
environment: (char**)env;
#ifdef __ANDROID__
- (jobject) androidContext;
- (NSString *) androidFilesDir;
#endif
@end
/**
@ -278,6 +288,16 @@ DEFINE_BLOCK_TYPE(GSPerformExpiringActivityBlock, void, BOOL);
*/
GS_EXPORT void GSInitializeProcess(int argc, char **argv, char **envp);
#ifdef __ANDROID__
/**
* Android process initialization function.
* This should be called on Android to initialize GNUstep with the JNI
* environment and application context, which is used to set up support
* for the Android data directory and asset loading via NSBundle.
*/
GS_EXPORT void GSInitializeProcessAndroid(JNIEnv *env, jobject context);
#endif
/**
* Function for rapid testing to see if a debug level is set.<br />
* This is used by the debugging macros.<br />

View file

@ -1740,7 +1740,9 @@ NSHomeDirectoryForUser(NSString *loginName)
{
NSString *s = nil;
#if !defined(_WIN32)
#ifdef __ANDROID__
s = [[NSProcessInfo processInfo] androidFilesDir];
#elif !defined(_WIN32)
#if defined(HAVE_GETPWNAM_R)
struct passwd pw;
struct passwd *p;

View file

@ -230,6 +230,12 @@ static NSString *_operatingSystemVersion = nil;
static BOOL fallbackInitialisation = NO;
static NSMutableSet *mySet = nil;
#ifdef __ANDROID__
static jobject _androidContext = NULL;
static NSString *_androidFilesDir = nil;
#endif
/*************************************************************************
*** Implementing the gnustep_base_user_main function
*************************************************************************/
@ -943,36 +949,6 @@ extern char **__libc_argv;
}
}
#elif defined(__ANDROID__)
+ (void) initialize
{
if (nil == procLock) procLock = [NSRecursiveLock new];
if (self == [NSProcessInfo class]
&& !_gnu_processName && !_gnu_arguments && !_gnu_environment)
{
FILE *f = fopen("/proc/self/cmdline", "r");
if (f)
{
char identifier[BUFSIZ];
fgets(identifier, sizeof(identifier), f);
fclose(f);
// construct fake executable path
char *arg0;
asprintf(&arg0, "/data/data/%s/exe", identifier);
char *argv[] = { arg0 };
_gnu_process_args(sizeof(argv)/sizeof(char *), argv, NULL);
}
else
{
fprintf(stderr, "Failed to read cmdline\n");
}
}
}
#else
+ (void) initialize
{
@ -1589,6 +1565,66 @@ GSInitializeProcess(int argc, char **argv, char **envp)
[procLock unlock];
}
#ifdef __ANDROID__
void
GSInitializeProcessAndroid(JNIEnv *env, jobject context)
{
[NSProcessInfo class];
// create global reference to to prevent garbage collection
_androidContext = (*env)->NewGlobalRef(env, context);
// get package code path (path to APK)
jclass cls = (*env)->GetObjectClass(env, context);
jmethodID packageCodePathMethod = (*env)->GetMethodID(env, cls, "getPackageCodePath", "()Ljava/lang/String;");
jstring packageCodePathJava = (*env)->CallObjectMethod(env, context, packageCodePathMethod);
const char *packageCodePath = (*env)->GetStringUTFChars(env, packageCodePathJava, NULL);
// get package name
jmethodID packageNameMethod = (*env)->GetMethodID(env, cls, "getPackageName", "()Ljava/lang/String;");
jstring packageNameJava = (*env)->CallObjectMethod(env, context, packageNameMethod);
const char *packageName = (*env)->GetStringUTFChars(env, packageNameJava, NULL);
// create fake executable path consisting of package code path (without .apk)
// and package name as executable
char *lastSlash = strrchr(packageCodePath, '/');
if (lastSlash == NULL)
{
lastSlash = (char *)packageCodePath + strlen(packageCodePath);
}
char *arg0;
asprintf(&arg0, "%.*s/%s", (int)(lastSlash - packageCodePath), packageCodePath, packageName);
(*env)->ReleaseStringUTFChars(env, packageCodePathJava, packageCodePath);
(*env)->ReleaseStringUTFChars(env, packageNameJava, packageName);
// initialize process
[procLock lock];
fallbackInitialisation = YES;
char *argv[] = { arg0 };
_gnu_process_args(sizeof(argv)/sizeof(char *), argv, NULL);
[procLock unlock];
free(arg0);
// get Android files dir
jmethodID filesDirMethod = (*env)->GetMethodID(env, cls, "getFilesDir", "()Ljava/io/File;");
jobject filesDirObj = (*env)->CallObjectMethod(env, context, filesDirMethod);
jclass filesDirCls = (*env)->GetObjectClass(env, filesDirObj);
jmethodID getStoragePath = (*env)->GetMethodID(env, filesDirCls, "getAbsolutePath", "()Ljava/lang/String;");
jstring filesDirJava = (*env)->CallObjectMethod(env, filesDirObj, getStoragePath);
const jchar *unichars = (*env)->GetStringChars(env, filesDirJava, NULL);
jsize length = (*env)->GetStringLength(env, filesDirJava);
_androidFilesDir = [NSString stringWithCharacters:unichars length:length];
(*env)->ReleaseStringChars(env, filesDirJava, unichars);
// get asset manager and initialize NSBundle
jmethodID assetManagerMethod = (*env)->GetMethodID(env, cls, "getAssets", "()Landroid/content/res/AssetManager;");
jstring assetManagerJava = (*env)->CallObjectMethod(env, context, assetManagerMethod);
[NSBundle setJavaAssetManager:assetManagerJava withJNIEnv:env];
}
#endif
@implementation NSProcessInfo (GNUstep)
+ (void) initializeWithArguments: (char**)argv
@ -1620,6 +1656,19 @@ GSInitializeProcess(int argc, char **argv, char **envp)
}
return NO;
}
#ifdef __ANDROID__
- (jobject) androidContext
{
return _androidContext;
}
- (NSString *) androidFilesDir
{
return _androidFilesDir;
}
#endif
@end
BOOL