diff options
Diffstat (limited to 'java/org/gnu/emacs/EmacsNoninteractive.java')
-rw-r--r-- | java/org/gnu/emacs/EmacsNoninteractive.java | 250 |
1 files changed, 137 insertions, 113 deletions
diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java index ba23399cb3e..9f2b9fa8b56 100644 --- a/java/org/gnu/emacs/EmacsNoninteractive.java +++ b/java/org/gnu/emacs/EmacsNoninteractive.java @@ -30,37 +30,69 @@ import java.lang.reflect.Method; /* Noninteractive Emacs. - This is the class that libandroid-emacs.so starts. - libandroid-emacs.so figures out the system classpath, then starts - dalvikvm with the framework jars. - - At that point, dalvikvm calls main, which sets up the main looper, - creates an ActivityThread and attaches it to the main thread. - - Then, it obtains an application context for the LoadedApk in the - application thread. - - Finally, it obtains the necessary context specific objects and - initializes Emacs. */ + When started, libandroid-emacs.so invokes `app_process(64)' with a + command line placing Emacs's classes.dex file in the JVM class path, + which in turn transfers control to `main'. `main' creates a context, + which may be likened to a connection to the system server, and a + class loader derived from Emacs's application package, which it loads + beforehand. From this class loader, it loads another instance of + itself, and invokes `main1', to ensure the execution of + `EmacsNative''s static initializer within the application class + loader, where a proper library search path is in effect. */ @SuppressWarnings ("unchecked") public final class EmacsNoninteractive { + /* Prepare Emacs for startup and call `initEmacs'. This function is + called in an instance of `EmacsNoninteractive' loaded by the APK + ClassLoader acquired in `main', which guarantees that shared + libraries in the APK will be considered in resolving shared + libraries for `EmacsNative'. */ + + public static void + main1 (String[] args, Context context) + throws Exception + { + AssetManager assets; + String filesDir, libDir, cacheDir; + + /* Don't actually start the looper or anything. Instead, obtain + an AssetManager. */ + assets = context.getAssets (); + + /* Now configure Emacs. The class path should already be set. */ + + filesDir = context.getFilesDir ().getCanonicalPath (); + libDir = EmacsService.getLibraryDirectory (context); + cacheDir = context.getCacheDir ().getCanonicalPath (); + EmacsNative.setEmacsParams (assets, filesDir, + libDir, cacheDir, 0.0f, + 0.0f, 0.0f, null, null, + Build.VERSION.SDK_INT); + + /* Now find the dump file that Emacs should use, if it has already + been dumped. */ + EmacsApplication.findDumpFile (context); + + /* Start Emacs. */ + EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); + } + public static void main (String[] args) { Object activityThread, loadedApk; Class activityThreadClass, loadedApkClass, contextImplClass; - Class compatibilityInfoClass; + Class compatibilityInfoClass, emacsNoninteractiveClass; Method method; Context context; - AssetManager assets; - String filesDir, libDir, cacheDir; + ClassLoader classLoader; Looper.prepare (); + context = null; - assets = null; - filesDir = libDir = cacheDir = null; + loadedApkClass = null; + classLoader = null; try { @@ -72,7 +104,6 @@ public final class EmacsNoninteractive /* Create and attach the activity thread. */ activityThread = method.invoke (null); - context = null; /* Now get an LoadedApk. */ @@ -82,99 +113,88 @@ public final class EmacsNoninteractive } catch (ClassNotFoundException exception) { - /* Android 2.2 has no LoadedApk class, but fortunately it - does not need to be used, since contexts can be - directly created. */ + /* Android 2.2 has no LoadedApk class; the several following + statements will load a context and an + ActivityThread.PackageInfo, as is appropriate on this + system. */ + } - loadedApkClass = null; - contextImplClass = Class.forName ("android.app.ContextImpl"); + /* Get a LoadedApk or ActivityThread.PackageInfo. How to do + this varies by Android version. On Android 2.3.3 and + earlier, there is no ``compatibilityInfo'' argument to + getPackageInfo. */ - method = activityThreadClass.getDeclaredMethod ("getSystemContext"); - context = (Context) method.invoke (activityThread); - method = contextImplClass.getDeclaredMethod ("createPackageContext", - String.class, - int.class); - method.setAccessible (true); - context = (Context) method.invoke (context, "org.gnu.emacs", - 0); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) + { + method + = activityThreadClass.getMethod ("getPackageInfo", + String.class, + int.class); + loadedApk = method.invoke (activityThread, "org.gnu.emacs", + (Context.CONTEXT_INCLUDE_CODE + | Context.CONTEXT_IGNORE_SECURITY)); } + else + { + compatibilityInfoClass + = Class.forName ("android.content.res.CompatibilityInfo"); + + method + = activityThreadClass.getMethod ("getPackageInfo", + String.class, + compatibilityInfoClass, + int.class); + loadedApk = method.invoke (activityThread, "org.gnu.emacs", + null, (Context.CONTEXT_INCLUDE_CODE + | Context.CONTEXT_IGNORE_SECURITY)); + } + + if (loadedApk == null) + throw new RuntimeException ("getPackageInfo returned NULL"); + + /* If loadedApkClass remains NULL, substitute the class of + the object returned by getPackageInfo. */ + if (loadedApkClass == null) + loadedApkClass = loadedApk.getClass (); - /* If the context has not already been created, then do what - is appropriate for newer versions of Android. */ + /* Now, get a context. */ + contextImplClass = Class.forName ("android.app.ContextImpl"); - if (context == null) + try { - /* Get a LoadedApk. How to do this varies by Android version. - On Android 2.3.3 and earlier, there is no - ``compatibilityInfo'' argument to getPackageInfo. */ - - if (Build.VERSION.SDK_INT - <= Build.VERSION_CODES.GINGERBREAD_MR1) - { - method - = activityThreadClass.getMethod ("getPackageInfo", - String.class, - int.class); - loadedApk = method.invoke (activityThread, "org.gnu.emacs", - 0); - } - else - { - compatibilityInfoClass - = Class.forName ("android.content.res.CompatibilityInfo"); - - method - = activityThreadClass.getMethod ("getPackageInfo", - String.class, - compatibilityInfoClass, - int.class); - loadedApk = method.invoke (activityThread, "org.gnu.emacs", - null, 0); - } - - if (loadedApk == null) - throw new RuntimeException ("getPackageInfo returned NULL"); - - /* Now, get a context. */ - contextImplClass = Class.forName ("android.app.ContextImpl"); - - try - { - method - = contextImplClass.getDeclaredMethod ("createAppContext", - activityThreadClass, - loadedApkClass); - method.setAccessible (true); - context = (Context) method.invoke (null, activityThread, - loadedApk); - } - catch (NoSuchMethodException exception) - { - /* Older Android versions don't have createAppContext, but - instead require creating a ContextImpl, and then - calling createPackageContext. */ - method - = activityThreadClass.getDeclaredMethod ("getSystemContext"); - context = (Context) method.invoke (activityThread); - method - = contextImplClass.getDeclaredMethod ("createPackageContext", - String.class, - int.class); - method.setAccessible (true); - context = (Context) method.invoke (context, "org.gnu.emacs", - 0); - } + method + = contextImplClass.getDeclaredMethod ("createAppContext", + activityThreadClass, + loadedApkClass); + method.setAccessible (true); + context = (Context) method.invoke (null, activityThread, + loadedApk); + } + catch (NoSuchMethodException exception) + { + /* Older Android versions don't have createAppContext, but + instead require creating a ContextImpl, and then + calling createPackageContext. */ + method + = activityThreadClass.getDeclaredMethod ("getSystemContext"); + context = (Context) method.invoke (activityThread); + method + = contextImplClass.getDeclaredMethod ("createPackageContext", + String.class, + int.class); + method.setAccessible (true); + context = (Context) method.invoke (context, "org.gnu.emacs", + 0); } - /* Don't actually start the looper or anything. Instead, obtain - an AssetManager. */ - assets = context.getAssets (); - - /* Now configure Emacs. The class path should already be set. */ + /* Retrieve the LoadedApk's class loader and execute the + remaining portion of the start-up process within its version + of EmacsNoninteractive, which will indicate to the system + that it must load shared libraries from the APK's library + search path. */ - filesDir = context.getFilesDir ().getCanonicalPath (); - libDir = EmacsService.getLibraryDirectory (context); - cacheDir = context.getCacheDir ().getCanonicalPath (); + method = loadedApkClass.getDeclaredMethod ("getClassLoader"); + classLoader = (ClassLoader) method.invoke (loadedApk); } catch (Exception e) { @@ -188,16 +208,20 @@ public final class EmacsNoninteractive System.exit (1); } - EmacsNative.setEmacsParams (assets, filesDir, - libDir, cacheDir, 0.0f, - 0.0f, 0.0f, null, null, - Build.VERSION.SDK_INT); - - /* Now find the dump file that Emacs should use, if it has already - been dumped. */ - EmacsApplication.findDumpFile (context); - - /* Start Emacs. */ - EmacsNative.initEmacs (args, EmacsApplication.dumpFileName); + try + { + emacsNoninteractiveClass + = classLoader.loadClass ("org.gnu.emacs.EmacsNoninteractive"); + method = emacsNoninteractiveClass.getMethod ("main1", String[].class, + Context.class); + method.setAccessible (true); + method.invoke (null, args, context); + } + catch (Exception e) + { + System.err.println ("Internal error during startup: " + e); + e.printStackTrace (); + System.exit (1); + } } }; |