summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/README824
-rw-r--r--java/org/gnu/emacs/EmacsApplication.java39
-rw-r--r--java/org/gnu/emacs/EmacsNative.java16
-rw-r--r--java/org/gnu/emacs/EmacsNoninteractive.java164
-rw-r--r--java/org/gnu/emacs/EmacsService.java48
-rw-r--r--java/org/gnu/emacs/EmacsThread.java22
6 files changed, 1067 insertions, 46 deletions
diff --git a/java/README b/java/README
index 05edf7744de..44f5a415162 100644
--- a/java/README
+++ b/java/README
@@ -10,3 +10,827 @@ to install different builds of Emacs on top of each other.
Please keep the Java code indented with tabs and formatted according
to the rules for C code in the GNU coding standards. Always use
C-style comments.
+
+======================================================================
+
+OVERVIEW OF JAVA
+
+Emacs developers do not know Java, and there is no reason they should
+have to. Thus, the code in this directory is confined to what is
+strictly necessary to support Emacs, and only uses a subset of Java
+written in a way that is easily understandable to C programmers.
+
+Java is required because the entire Android runtime is based around
+Java, and there is no way to write an Android program which runs
+without Java.
+
+This text exists to prime other Emacs developers, already familar with
+C, on the basic architecture of the Android port, and to teach them
+how to read and write the Java code found in this directory.
+
+Java is an object oriented language with automatic memory management
+compiled down to bytecode, which is then subject to interpretation by
+a Java virtual machine.
+
+What that means, is that:
+
+struct emacs_window
+{
+ int some_fields;
+ int of_emacs_window;
+};
+
+static void
+do_something_with_emacs_window (struct emacs_window *a, int n)
+{
+ a->some_fields = a->of_emacs_window + n;
+}
+
+would be written:
+
+public class EmacsWindow
+{
+ public int someFields;
+ public int ofEmacsWindow;
+
+ public void
+ doSomething (int n)
+ {
+ someFields = ofEmacsWindow + n;
+ }
+}
+
+and instead of doing:
+
+do_something_with_emacs_window (my_window, 1);
+
+you say:
+
+myWindow.doSomething (1);
+
+In addition to functions associated with an object of a given class
+(such as EmacsWindow), Java also has two other kinds of functions.
+
+The first are so-called ``static'' functions (the static means
+something entirely different from what it does in C.)
+
+A static function, while still having to be defined within a class,
+can be called without any object. Instead of the object, you write
+the name of the Java class within which it is defined. For example,
+the following C code:
+
+int
+multiply_a_with_b_and_then_add_c (int a, int b, int c)
+{
+ return a * b + c;
+}
+
+would be:
+
+public class EmacsSomething
+{
+ public static int
+ multiplyAWithBAndThenAddC (int a, int b, int c)
+ {
+ return a * b + c;
+ }
+};
+
+Then, instead of calling:
+
+int foo;
+
+foo = multiply_a_with_b_then_add_c (1, 2, 3);
+
+you say:
+
+int foo;
+
+foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3);
+
+In Java, ``static'' does not mean that the function is only used
+within its compilation unit! Instead, the ``private'' qualifier is
+used to mean more or less the same thing:
+
+static void
+this_procedure_is_only_used_within_this_file (void)
+{
+ do_something ();
+}
+
+becomes
+
+public class EmacsSomething
+{
+ private static void
+ thisProcedureIsOnlyUsedWithinThisClass ()
+ {
+
+ }
+}
+
+the other kind are called ``constructors''. They are functions that
+must be called to allocate memory to hold a class:
+
+public class EmacsFoo
+{
+ int bar;
+
+ public
+ EmacsFoo (int tokenA, int tokenB)
+ {
+ bar = tokenA + tokenB;
+ }
+}
+
+now, the following statement:
+
+EmacsFoo foo;
+
+foo = new EmacsFoo (1, 2);
+
+becomes more or less equivalent to the following C code:
+
+struct emacs_foo
+{
+ int bar;
+};
+
+struct emacs_foo *
+make_emacs_foo (int token_a, int token_b)
+{
+ struct emacs_foo *foo;
+
+ foo = xmalloc (sizeof *foo);
+ foo->bar = token_a + token_b;
+
+ return foo;
+}
+
+/* ... */
+
+struct emacs_foo *foo;
+
+foo = make_emacs_foo (1, 2);
+
+A class may have any number of constructors, or no constructors at
+all, in which case the compiler inserts an empty constructor.
+
+
+
+Sometimes, you will see Java code that looks like this:
+
+ allFiles = filesDirectory.listFiles (new FileFilter () {
+ @Override
+ public boolean
+ accept (File file)
+ {
+ return (!file.isDirectory ()
+ && file.getName ().endsWith (".pdmp"));
+ }
+ });
+
+This is Java's version of GCC's nested function extension. The major
+difference is that the nested function may still be called even after
+it goes out of scope, and always retains a reference to the class and
+local variables around where it was called.
+
+Being an object-oriented language, Java also allows defining that a
+class ``extends'' another class. The following C code:
+
+struct a
+{
+ long thirty_two;
+};
+
+struct b
+{
+ struct a a;
+ long long sixty_four;
+};
+
+extern void do_something (struct a *);
+
+void
+my_function (struct b *b)
+{
+ do_something (&b->a);
+}
+
+is roughly equivalent to the following Java code, split into two
+files:
+
+ A.java
+
+public class A
+{
+ int thirtyTwo;
+
+ public void
+ doSomething ()
+ {
+ etcEtcEtc ();
+ }
+};
+
+ B.java
+
+public class B extends A
+{
+ long sixty_four;
+
+ public static void
+ myFunction (B b)
+ {
+ b.doSomething ();
+ }
+}
+
+the Java runtime has transformed the call to ``b.doSomething'' to
+``((A) b).doSomething''.
+
+However, Java also allows overriding this behavior, by specifying the
+@Override keyword:
+
+public class B extends A
+{
+ long sixty_four;
+
+ @Override
+ public void
+ doSomething ()
+ {
+ Something.doSomethingTwo ();
+ super.doSomething ();
+ }
+}
+
+now, any call to ``doSomething'' on a ``B'' created using ``new B ()''
+will end up calling ``Something.doSomethingTwo'', before calling back
+to ``A.doSomething''. This override also applies in reverse; that is
+to say, even if you write:
+
+ ((A) b).doSomething ();
+
+B's version of doSomething will still be called, if ``b'' was created
+using ``new B ()''.
+
+This mechanism is used extensively throughout the Java language and
+Android windowing APIs.
+
+Elsewhere, you will encounter Java code that defines arrays:
+
+public class EmacsFrobinicator
+{
+ public static void
+ emacsFrobinicate (int something)
+ {
+ int[] primesFromSomething;
+
+ primesFromSomething = new int[numberOfPrimes];
+ /* ... */
+ }
+}
+
+Java arrays are similar to C arrays in that they can not grow. But
+they are very much unlike C arrays in that they are always references
+(as opposed to decaying into pointers in various situations), and
+contain information about their length.
+
+If another function named ``frobinicate1'' takes an array as an
+argument, then it need not take the length of the array.
+
+Instead, it simply iterates over the array like so:
+
+int i, k;
+
+for (i = 0; i < array.length; ++i)
+ {
+ k = array[i];
+
+ Whatever.doSomethingWithK (k);
+ }
+
+The syntax used to define arrays is also slightly different. As
+arrays are always references, there is no way for you to tell the
+runtime to allocate an array of size N in a structure (class.)
+
+Instead, if you need an array of that size, you must declare a field
+with the type of the array, and allocate the array inside the class's
+constructor, like so:
+
+public class EmacsArrayContainer
+{
+ public int[] myArray;
+
+ public
+ EmacsArrayContainer ()
+ {
+ myArray = new array[10];
+ }
+}
+
+while in C, you could just have written:
+
+struct emacs_array_container
+{
+ int my_array[10];
+};
+
+or, possibly even better,
+
+typedef int my_array[10];
+
+Alas, Java has no equivalent of `typedef'.
+
+JAVA NATIVE INTERFACE
+
+Java also provides an interface for C code to interface with Java.
+
+C functions exported from a shared library become static Java
+functions within a class, like so:
+
+public class EmacsNative
+{
+ /* Obtain the fingerprint of this build of Emacs. The fingerprint
+ can be used to determine the dump file name. */
+ public static native String getFingerprint ();
+
+ /* Set certain parameters before initializing Emacs.
+
+ assetManager must be the asset manager associated with the
+ context that is loading Emacs. It is saved and remains for the
+ remainder the lifetime of the Emacs process.
+
+ filesDir must be the package's data storage location for the
+ current Android user.
+
+ libDir must be the package's data storage location for native
+ libraries. It is used as PATH.
+
+ cacheDir must be the package's cache directory. It is used as
+ the `temporary-file-directory'.
+
+ pixelDensityX and pixelDensityY are the DPI values that will be
+ used by Emacs.
+
+ classPath must be the classpath of this app_process process, or
+ NULL.
+
+ emacsService must be the EmacsService singleton, or NULL. */
+ public static native void setEmacsParams (AssetManager assetManager,
+ String filesDir,
+ String libDir,
+ String cacheDir,
+ float pixelDensityX,
+ float pixelDensityY,
+ String classPath,
+ EmacsService emacsService);
+}
+
+Where the corresponding C functions are located in android.c, and
+loaded by the special invocation:
+
+ static
+ {
+ System.loadLibrary ("emacs");
+ };
+
+
+See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html
+for more details.
+
+
+
+OVERVIEW OF ANDROID
+
+When the Android system starts an application, it does not actually
+call the application's ``main'' function. It may not even start the
+application's process if one is already running.
+
+Instead, Android is organized around components. When the user opens
+the ``Emacs'' icon, the Android system looks up and starts the
+component associated with the ``Emacs'' icon. In this case, the
+component is called an activity, and is declared in
+the AndroidManifest.xml in this directory:
+
+ <activity android:name="org.gnu.emacs.EmacsActivity"
+ android:launchMode="singleTop"
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+This tells Android to start the activity defined in ``EmacsActivity''
+(defined in org/gnu/emacs/EmacsActivity.java), a class extending the
+Android class ``Activity''.
+
+To do so, the Android system creates an instance of ``EmacsActivity''
+and the window system window associated with it, and eventually calls:
+
+ Activity activity;
+
+ activity.onCreate (...);
+
+But which ``onCreate'' is really called?
+It is actually the ``onCreate'' defined in EmacsActivity.java, as
+it overrides the ``onCreate'' defined in Android's own Activity class:
+
+ @Override
+ public void
+ onCreate (Bundle savedInstanceState)
+ {
+ FrameLayout.LayoutParams params;
+ Intent intent;
+
+Then, this is what happens step-by-step within the ``onCreate''
+function:
+
+ /* See if Emacs should be started with -Q. */
+ intent = getIntent ();
+ EmacsService.needDashQ
+ = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
+ false);
+
+Here, Emacs obtains the intent (a request to start a component) which
+was used to start Emacs, and sets a special flag if it contains a
+request for Emacs to start with the ``-Q'' command-line argument.
+
+ /* Set the theme to one without a title bar. */
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ setTheme (android.R.style.Theme_DeviceDefault_NoActionBar);
+ else
+ setTheme (android.R.style.Theme_NoTitleBar);
+
+Next, Emacs sets an appropriate theme for the activity's associated
+window decorations.
+
+ params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+
+ /* Make the frame layout. */
+ layout = new FrameLayout (this);
+ layout.setLayoutParams (params);
+
+ /* Set it as the content view. */
+ setContentView (layout);
+
+Then, Emacs creates a ``FrameLayout'', a widget that holds a single
+other widget, and makes it the activity's ``content view''.
+
+The activity itself is a ``FrameLayout'', so the ``layout parameters''
+here apply to the FrameLayout itself, and not its children.
+
+ /* Maybe start the Emacs service if necessary. */
+ EmacsService.startEmacsService (this);
+
+And after that, Emacs calls the static function ``startEmacsService'',
+defined in the class ``EmacsService''. This starts the Emacs service
+component if necessary.
+
+ /* Add this activity to the list of available activities. */
+ EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
+
+ super.onCreate (savedInstanceState);
+
+Finally, Emacs registers that this activity is now ready to receive
+top-level frames (windows) created from Lisp.
+
+Activities come and go, but Emacs has to stay running in the mean
+time. Thus, Emacs also defines a ``service'', which is a long-running
+component that the Android system allows to run in the background.
+
+Let us go back and review the definition of ``startEmacsService'':
+
+ public static void
+ startEmacsService (Context context)
+ {
+ if (EmacsService.SERVICE == null)
+ {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+ /* Start the Emacs service now. */
+ context.startService (new Intent (context,
+ EmacsService.class));
+ else
+ /* Display the permanant notification and start Emacs as a
+ foreground service. */
+ context.startForegroundService (new Intent (context,
+ EmacsService.class));
+ }
+ }
+
+If ``EmacsService.SERVICE'' does not yet exist, what this does is to
+tell the ``context'' (the equivalent of an Xlib Display *) to start a
+service defined by the class ``EmacsService''. Eventually, this
+results in ``EmacsService.onCreate'' being called:
+
+ @Override
+ public void
+ onCreate ()
+ {
+ AssetManager manager;
+ Context app_context;
+ String filesDir, libDir, cacheDir, classPath;
+ double pixelDensityX;
+ double pixelDensityY;
+
+Here is what this function does, step-by-step:
+
+ SERVICE = this;
+
+First, it sets the special static variable ``SERVICE'' to ``this'',
+which is a pointer to the ``EmacsService' object that was created.
+
+ handler = new Handler (Looper.getMainLooper ());
+
+Next, it creates a ``Handler'' object for the ``main looper''.
+This is a helper structure which allows executing code on the Android
+user interface thread.
+
+ manager = getAssets ();
+ app_context = getApplicationContext ();
+ metrics = getResources ().getDisplayMetrics ();
+ pixelDensityX = metrics.xdpi;
+ pixelDensityY = metrics.ydpi;
+
+Finally, it obtains:
+
+ - the asset manager, which is used to retrieve assets packaged
+ into the Emacs application package.
+
+ - the application context, used to obtain application specific
+ information.
+
+ - the display metrics, and from them, the X and Y densities in dots
+ per inch.
+
+Then, inside a ``try'' block:
+
+ try
+ {
+ /* Configure Emacs with the asset manager and other necessary
+ parameters. */
+ filesDir = app_context.getFilesDir ().getCanonicalPath ();
+ libDir = getLibraryDirectory ();
+ cacheDir = app_context.getCacheDir ().getCanonicalPath ();
+
+It obtains the names of the Emacs home, shared library, and temporary
+file directories.
+
+ /* Now provide this application's apk file, so a recursive
+ invocation of app_process (through android-emacs) can
+ find EmacsNoninteractive. */
+ classPath = getApkFile ();
+
+The name of the Emacs application package.
+
+ Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+ + ", libDir = " + libDir + ", and classPath = " + classPath);
+
+Prints a debug message to the Android system log with this
+information.
+
+ EmacsNative.setEmacsParams (manager, filesDir, libDir,
+ cacheDir, (float) pixelDensityX,
+ (float) pixelDensityY,
+ classPath, this);
+
+And calls the native function ``setEmacsParams'' (defined in
+android.c) to configure Emacs with this information.
+
+ /* Start the thread that runs Emacs. */
+ thread = new EmacsThread (this, needDashQ);
+ thread.start ();
+
+Then, it allocates an ``EmacsThread'' object, and starts that thread.
+Inside that thread is where Emacs's C code runs.
+
+ }
+ catch (IOException exception)
+ {
+ EmacsNative.emacsAbort ();
+ return;
+
+And here is the purpose of the ``try'' block. Functions related to
+file names in Java will signal errors of various types upon failure.
+
+This ``catch'' block means that the Java virtual machine will abort
+execution of the contents of the ``try'' block as soon as an error of
+type ``IOException'' is encountered, and begin executing the contents
+of the ``catch'' block.
+
+Any failure of that type here is a crash, and
+``EmacsNative.emacsAbort'' is called to quickly abort the process to
+get a useful backtrace.
+ }
+ }
+
+Now, let us look at the definition of the class ``EmacsThread'', found
+in org/gnu/emacs/EmacsThread.java:
+
+public class EmacsThread extends Thread
+{
+ /* Whether or not Emacs should be started -Q. */
+ private boolean startDashQ;
+
+ public
+ EmacsThread (EmacsService service, boolean startDashQ)
+ {
+ super ("Emacs main thread");
+ this.startDashQ = startDashQ;
+ }
+
+ @Override
+ public void
+ run ()
+ {
+ String args[];
+
+ if (!startDashQ)
+ args = new String[] { "libandroid-emacs.so", };
+ else
+ args = new String[] { "libandroid-emacs.so", "-Q", };
+
+ /* Run the native code now. */
+ EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+ }
+};
+
+The class itself defines a single field, ``startDashQ'', a constructor
+with an unused argument of the type ``EmacsService'' (which is useful
+while debugging) and a flag ``startDashQ'', and a single function
+``run'', overriding the same function in the class ``Thread''.
+
+When ``thread.start'' is called, the Java virtual machine creates a
+new thread, and then calls the function ``run'' within that thread.
+
+This function then computes a suitable argument vector, and calls
+``EmacsNative.initEmacs'' (defined in android.c), which then calls a
+modified version of the regular Emacs ``main'' function.
+
+At that point, Emacs initialization proceeds as usual:
+Vinitial_window_system is set, loadup.el calls `normal-top-level',
+which calls `command-line', and finally
+`window-system-initialization', which initializes the `android'
+terminal interface as usual.
+
+What happens here is the same as on other platforms. Now, here is
+what happens when the initial frame is created: Fx_create_frame calls
+`android_create_frame_window' to create a top level window:
+
+static void
+android_create_frame_window (struct frame *f)
+{
+ struct android_set_window_attributes attributes;
+ enum android_window_value_mask attribute_mask;
+
+ attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f);
+ attribute_mask = ANDROID_CW_BACK_PIXEL;
+
+ block_input ();
+ FRAME_ANDROID_WINDOW (f)
+ = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
+ f->left_pos,
+ f->top_pos,
+ FRAME_PIXEL_WIDTH (f),
+ FRAME_PIXEL_HEIGHT (f),
+ attribute_mask, &attributes);
+ unblock_input ();
+}
+
+This calls the function `android_create_window' with some arguments
+whose meanings are identical to the arguments to `XCreateWindow'.
+
+Here is the definition of `android_create_window', in android.c:
+
+android_window
+android_create_window (android_window parent, int x, int y,
+ int width, int height,
+ enum android_window_value_mask value_mask,
+ struct android_set_window_attributes *attrs)
+{
+ static jclass class;
+ static jmethodID constructor;
+ jobject object, parent_object, old;
+ android_window window;
+ android_handle prev_max_handle;
+ bool override_redirect;
+
+What does it do? First, some context:
+
+At any time, there can be at most 65535 Java objects referred to by
+the rest of Emacs through the Java native interface. Each such object
+is assigned a ``handle'' (similar to an XID on X) and given a unique
+type. The function `android_resolve_handle' returns the JNI `jobject'
+associated with a given handle.
+
+ parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
+
+Here, it is being used to look up the `jobject' associated with the
+`parent' handle.
+
+ prev_max_handle = max_handle;
+ window = android_alloc_id ();
+
+Next, `max_handle' is saved, and a new handle is allocated for
+`window'.
+
+ if (!window)
+ error ("Out of window handles!");
+
+An error is signalled if Emacs runs out of available handles.
+
+ if (!class)
+ {
+ class = (*android_java_env)->FindClass (android_java_env,
+ "org/gnu/emacs/EmacsWindow");
+ assert (class != NULL);
+
+Then, if this initialization has not yet been completed, Emacs
+proceeds to find the Java class named ``EmacsWindow''.
+
+ constructor
+ = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
+ "(SLorg/gnu/emacs/EmacsWindow;"
+ "IIIIZ)V");
+ assert (constructor != NULL);
+
+And it tries to look up the constructor, which should take seven
+arguments:
+
+ S - a short. (the handle ID)
+ Lorg/gnu/Emacs/EmacsWindow; - an instance of the EmacsWindow
+ class. (the parent)
+ IIII - four ints. (the window geometry.)
+ Z - a boolean. (whether or not the
+ window is override-redirect; see
+ XChangeWindowAttributes.)
+
+ old = class;
+ class = (*android_java_env)->NewGlobalRef (android_java_env, class);
+ (*android_java_env)->ExceptionClear (android_java_env);
+ ANDROID_DELETE_LOCAL_REF (old);
+
+Next, it saves a global reference to the class and deletes the local
+reference. Global references will never be deallocated by the Java
+virtual machine as long as they still exist.
+
+ if (!class)
+ memory_full (0);
+ }
+
+ /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
+ creation time. */
+ override_redirect = ((value_mask
+ & ANDROID_CW_OVERRIDE_REDIRECT)
+ && attrs->override_redirect);
+
+ object = (*android_java_env)->NewObject (android_java_env, class,
+ constructor, (jshort) window,
+ parent_object, (jint) x, (jint) y,
+ (jint) width, (jint) height,
+ (jboolean) override_redirect);
+
+Then, it creates an instance of the ``EmacsWindow'' class with the
+appropriate arguments and previously determined constructor.
+
+ if (!object)
+ {
+ (*android_java_env)->ExceptionClear (android_java_env);
+
+ max_handle = prev_max_handle;
+ memory_full (0);
+
+If creating the object fails, Emacs clears the ``pending exception''
+and signals that it is out of memory.
+ }
+
+ android_handles[window].type = ANDROID_HANDLE_WINDOW;
+ android_handles[window].handle
+ = (*android_java_env)->NewGlobalRef (android_java_env,
+ object);
+ (*android_java_env)->ExceptionClear (android_java_env);
+ ANDROID_DELETE_LOCAL_REF (object);
+
+Otherwise, it associates a new global reference to the object with the
+handle, and deletes the local reference returned from the JNI
+NewObject function.
+
+ if (!android_handles[window].handle)
+ memory_full (0);
+
+If allocating the global reference fails, Emacs signals that it is out
+of memory.
+
+ android_change_window_attributes (window, value_mask, attrs);
+ return window;
+
+Otherwise, it applies the specified window attributes and returns the
+handle of the new window.
+}
diff --git a/java/org/gnu/emacs/EmacsApplication.java b/java/org/gnu/emacs/EmacsApplication.java
index 87085c32d62..96328b99d1c 100644
--- a/java/org/gnu/emacs/EmacsApplication.java
+++ b/java/org/gnu/emacs/EmacsApplication.java
@@ -22,27 +22,20 @@ package org.gnu.emacs;
import java.io.File;
import java.io.FileFilter;
+import android.content.Context;
+
import android.app.Application;
import android.util.Log;
-public class EmacsApplication extends Application implements FileFilter
+public class EmacsApplication extends Application
{
private static final String TAG = "EmacsApplication";
/* The name of the dump file to use. */
public static String dumpFileName;
- @Override
- public boolean
- accept (File file)
- {
- return (!file.isDirectory ()
- && file.getName ().endsWith (".pdmp"));
- }
-
- @Override
- public void
- onCreate ()
+ public static void
+ findDumpFile (Context context)
{
File filesDirectory;
File[] allFiles;
@@ -52,13 +45,19 @@ public class EmacsApplication extends Application implements FileFilter
wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint ()
+ ".pdmp");
- Log.d (TAG, "onCreate: looking for " + wantedDumpFile);
-
/* Obtain a list of all files ending with ``.pdmp''. Then, look
for a file named ``emacs-<fingerprint>.pdmp'' and delete the
rest. */
- filesDirectory = getFilesDir ();
- allFiles = filesDirectory.listFiles (this);
+ filesDirectory = context.getFilesDir ();
+ allFiles = filesDirectory.listFiles (new FileFilter () {
+ @Override
+ public boolean
+ accept (File file)
+ {
+ return (!file.isDirectory ()
+ && file.getName ().endsWith (".pdmp"));
+ }
+ });
/* Now try to find the right dump file. */
for (i = 0; i < allFiles.length; ++i)
@@ -69,9 +68,13 @@ public class EmacsApplication extends Application implements FileFilter
/* Delete this outdated dump file. */
allFiles[i].delete ();
}
+ }
- Log.d (TAG, "onCreate: found " + dumpFileName);
-
+ @Override
+ public void
+ onCreate ()
+ {
+ findDumpFile (this);
super.onCreate ();
}
};
diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java
index 9636561a524..a772b965301 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -29,8 +29,7 @@ public class EmacsNative
can be used to determine the dump file name. */
public static native String getFingerprint ();
- /* Set certain parameters before initializing Emacs. This proves
- that libemacs.so is being loaded from Java code.
+ /* Set certain parameters before initializing Emacs.
assetManager must be the asset manager associated with the
context that is loading Emacs. It is saved and remains for the
@@ -48,19 +47,26 @@ public class EmacsNative
pixelDensityX and pixelDensityY are the DPI values that will be
used by Emacs.
- emacsService must be the emacsService singleton. */
+ classPath must be the classpath of this app_process process, or
+ NULL.
+
+ emacsService must be the EmacsService singleton, or NULL. */
public static native void setEmacsParams (AssetManager assetManager,
String filesDir,
String libDir,
String cacheDir,
float pixelDensityX,
float pixelDensityY,
+ String classPath,
EmacsService emacsService);
/* Initialize Emacs with the argument array ARGV. Each argument
must contain a NULL terminated string, or else the behavior is
- undefined. */
- public static native void initEmacs (String argv[]);
+ undefined.
+
+ DUMPFILE is the dump file to use, or NULL if Emacs is to load
+ loadup.el itself. */
+ public static native void initEmacs (String argv[], String dumpFile);
/* Abort and generate a native core dump. */
public static native void emacsAbort ();
diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java b/java/org/gnu/emacs/EmacsNoninteractive.java
new file mode 100644
index 00000000000..a3aefee5e0b
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsNoninteractive.java
@@ -0,0 +1,164 @@
+/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
+
+package org.gnu.emacs;
+
+import android.os.Looper;
+import android.os.Build;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import java.lang.reflect.Constructor;
+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. */
+
+@SuppressWarnings ("unchecked")
+public class EmacsNoninteractive
+{
+ private static String
+ getLibraryDirectory (Context context)
+ {
+ int apiLevel;
+
+ apiLevel = Build.VERSION.SDK_INT;
+
+ if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
+ return context.getApplicationInfo().nativeLibraryDir;
+ else if (apiLevel >= Build.VERSION_CODES.DONUT)
+ return context.getApplicationInfo().dataDir + "/lib";
+
+ return "/data/data/" + context.getPackageName() + "/lib";
+ }
+
+ public static void
+ main (String[] args)
+ {
+ Object activityThread, loadedApk;
+ Class activityThreadClass, loadedApkClass, contextImplClass;
+ Class compatibilityInfoClass;
+ Method method;
+ Context context;
+ AssetManager assets;
+ String filesDir, libDir, cacheDir;
+
+ Looper.prepare ();
+ context = null;
+ assets = null;
+ filesDir = libDir = cacheDir = null;
+
+ try
+ {
+ /* Get the activity thread. */
+ activityThreadClass = Class.forName ("android.app.ActivityThread");
+
+ /* Get the systemMain method. */
+ method = activityThreadClass.getMethod ("systemMain");
+
+ /* Create and attach the activity thread. */
+ activityThread = method.invoke (null);
+
+ /* Now get an LoadedApk. */
+ loadedApkClass = Class.forName ("android.app.LoadedApk");
+
+ /* 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)
+ {
+ 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");
+ method = contextImplClass.getDeclaredMethod ("createAppContext",
+ activityThreadClass,
+ loadedApkClass);
+ method.setAccessible (true);
+ context = (Context) method.invoke (null, activityThread, loadedApk);
+
+ /* 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 = getLibraryDirectory (context);
+ cacheDir = context.getCacheDir ().getCanonicalPath ();
+ }
+ catch (Exception e)
+ {
+ System.err.println ("Internal error: " + e);
+ System.err.println ("This means that the Android platform changed,");
+ System.err.println ("and that Emacs needs adjustments in order to");
+ System.err.println ("obtain required system internal resources.");
+ System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org.");
+
+ System.exit (1);
+ }
+
+ EmacsNative.setEmacsParams (assets, filesDir,
+ libDir, cacheDir, 0.0f,
+ 0.0f, null, null);
+
+ /* 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);
+ }
+};
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index 4db1ea5359f..91db76b08e3 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -41,6 +41,9 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
@@ -118,8 +121,38 @@ public class EmacsService extends Service
return null;
}
+ @SuppressWarnings ("deprecation")
+ private String
+ getApkFile ()
+ {
+ PackageManager manager;
+ ApplicationInfo info;
+
+ manager = getPackageManager ();
+
+ try
+ {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
+ info = manager.getApplicationInfo ("org.gnu.emacs", 0);
+ else
+ info = manager.getApplicationInfo ("org.gnu.emacs",
+ ApplicationInfoFlags.of (0));
+
+ /* Return an empty string upon failure. */
+
+ if (info.sourceDir != null)
+ return info.sourceDir;
+
+ return "";
+ }
+ catch (Exception e)
+ {
+ return "";
+ }
+ }
+
@TargetApi (Build.VERSION_CODES.GINGERBREAD)
- String
+ private String
getLibraryDirectory ()
{
int apiLevel;
@@ -142,7 +175,7 @@ public class EmacsService extends Service
{
AssetManager manager;
Context app_context;
- String filesDir, libDir, cacheDir;
+ String filesDir, libDir, cacheDir, classPath;
double pixelDensityX;
double pixelDensityY;
@@ -162,13 +195,18 @@ public class EmacsService extends Service
libDir = getLibraryDirectory ();
cacheDir = app_context.getCacheDir ().getCanonicalPath ();
+ /* Now provide this application's apk file, so a recursive
+ invocation of app_process (through android-emacs) can
+ find EmacsNoninteractive. */
+ classPath = getApkFile ();
+
Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
- + " and libDir = " + libDir);
+ + ", libDir = " + libDir + ", and classPath = " + classPath);
EmacsNative.setEmacsParams (manager, filesDir, libDir,
cacheDir, (float) pixelDensityX,
(float) pixelDensityY,
- this);
+ classPath, this);
/* Start the thread that runs Emacs. */
thread = new EmacsThread (this, needDashQ);
@@ -491,8 +529,6 @@ public class EmacsService extends Service
public static void
startEmacsService (Context context)
{
- PendingIntent intent;
-
if (EmacsService.SERVICE == null)
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java
index 5b76d11db4b..f5e9d54044a 100644
--- a/java/org/gnu/emacs/EmacsThread.java
+++ b/java/org/gnu/emacs/EmacsThread.java
@@ -33,30 +33,18 @@ public class EmacsThread extends Thread
this.startDashQ = startDashQ;
}
+ @Override
public void
run ()
{
String args[];
- if (EmacsApplication.dumpFileName == null)
- {
- if (!startDashQ)
- args = new String[] { "libandroid-emacs.so", };
- else
- args = new String[] { "libandroid-emacs.so", "-Q", };
- }
+ if (!startDashQ)
+ args = new String[] { "libandroid-emacs.so", };
else
- {
- if (!startDashQ)
- args = new String[] { "libandroid-emacs.so", "--dump-file",
- EmacsApplication.dumpFileName, };
- else
- args = new String[] { "libandroid-emacs.so", "-Q",
- "--dump-file",
- EmacsApplication.dumpFileName, };
- }
+ args = new String[] { "libandroid-emacs.so", "-Q", };
/* Run the native code now. */
- EmacsNative.initEmacs (args);
+ EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
}
};