@cindex EMACS_CLASS_PATH environment variable, Android
Even when the location of the @command{libandroid-emacs.so} command is
-known in advance, special configuration is required to run Emacs from
+known in advance, special preparation is required to run Emacs from
elsewhere than a subprocess of an existing Emacs session, as it must be
made to understand the location of resources and shared libraries in or
extracted from the installed application package. The OS command
@command{pm path org.gnu.emacs} will print the location of the
-application package, and the adjacent @file{lib} directory will hold
-shared libraries extracted from the same, though the said command must
-be invoked in a peculiar manner to satisfy system restrictions on
-communication between pseudoterminal devices created by user
-applications and system services such as the package manager, which is
-to say, with the standard IO streams redirected to a real file or a
-pipe. Such values, once established, must be specified in the
-environment variables @code{EMACS_CLASS_PATH} and
-@code{EMACS_LD_LIBRARY_PATH}, so that this sample shell script may be
+application package, though the said command must be invoked in a
+peculiar manner to satisfy system restrictions on communication between
+pseudoterminal devices created by user applications and system services
+such as the package manager, which is to say, with the standard IO
+streams redirected to a real file or a pipe. This value, once
+established, must be specified in the environment variables
+@code{EMACS_CLASS_PATH}, so that this sample shell script may be
installed as @code{emacs} in any location that is accessible:
@example
package_name=`pm path org.gnu.emacs 2>/dev/null </dev/null \
| sed 's/^package://'`
emacs=
-ld_path=
EMACS_CLASS_PATH=$package_name
for libdir in `dirname $package_name`/lib/*; do
&& emacs="$libdir"/libandroid-emacs.so
done
-EMACS_LD_LIBRARY_PATH=$ld_path
-
export EMACS_CLASS_PATH
-export EMACS_LD_LIBRARY_PATH
test -x "$emacs" || exit 1
exec $emacs "$@@"
@end example
/* 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
{
/* Create and attach the activity thread. */
activityThread = method.invoke (null);
- context = null;
/* Now get an LoadedApk. */
}
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)
{
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);
+ }
}
};