aboutsummaryrefslogtreecommitdiffstats
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.
10Please keep the Java code indented with tabs and formatted according 10Please keep the Java code indented with tabs and formatted according
11to the rules for C code in the GNU coding standards. Always use 11to the rules for C code in the GNU coding standards. Always use
12C-style comments. 12C-style comments.
13
14======================================================================
15
16OVERVIEW OF JAVA
17
18Emacs developers do not know Java, and there is no reason they should
19have to. Thus, the code in this directory is confined to what is
20strictly necessary to support Emacs, and only uses a subset of Java
21written in a way that is easily understandable to C programmers.
22
23Java is required because the entire Android runtime is based around
24Java, and there is no way to write an Android program which runs
25without Java.
26
27This text exists to prime other Emacs developers, already familar with
28C, on the basic architecture of the Android port, and to teach them
29how to read and write the Java code found in this directory.
30
31Java is an object oriented language with automatic memory management
32compiled down to bytecode, which is then subject to interpretation by
33a Java virtual machine.
34
35What that means, is that:
36
37struct emacs_window
38{
39 int some_fields;
40 int of_emacs_window;
41};
42
43static void
44do_something_with_emacs_window (struct emacs_window *a, int n)
45{
46 a->some_fields = a->of_emacs_window + n;
47}
48
49would be written:
50
51public class EmacsWindow
52{
53 public int someFields;
54 public int ofEmacsWindow;
55
56 public void
57 doSomething (int n)
58 {
59 someFields = ofEmacsWindow + n;
60 }
61}
62
63and instead of doing:
64
65do_something_with_emacs_window (my_window, 1);
66
67you say:
68
69myWindow.doSomething (1);
70
71In addition to functions associated with an object of a given class
72(such as EmacsWindow), Java also has two other kinds of functions.
73
74The first are so-called ``static'' functions (the static means
75something entirely different from what it does in C.)
76
77A static function, while still having to be defined within a class,
78can be called without any object. Instead of the object, you write
79the name of the Java class within which it is defined. For example,
80the following C code:
81
82int
83multiply_a_with_b_and_then_add_c (int a, int b, int c)
84{
85 return a * b + c;
86}
87
88would be:
89
90public class EmacsSomething
91{
92 public static int
93 multiplyAWithBAndThenAddC (int a, int b, int c)
94 {
95 return a * b + c;
96 }
97};
98
99Then, instead of calling:
100
101int foo;
102
103foo = multiply_a_with_b_then_add_c (1, 2, 3);
104
105you say:
106
107int foo;
108
109foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3);
110
111In Java, ``static'' does not mean that the function is only used
112within its compilation unit! Instead, the ``private'' qualifier is
113used to mean more or less the same thing:
114
115static void
116this_procedure_is_only_used_within_this_file (void)
117{
118 do_something ();
119}
120
121becomes
122
123public class EmacsSomething
124{
125 private static void
126 thisProcedureIsOnlyUsedWithinThisClass ()
127 {
128
129 }
130}
131
132the other kind are called ``constructors''. They are functions that
133must be called to allocate memory to hold a class:
134
135public class EmacsFoo
136{
137 int bar;
138
139 public
140 EmacsFoo (int tokenA, int tokenB)
141 {
142 bar = tokenA + tokenB;
143 }
144}
145
146now, the following statement:
147
148EmacsFoo foo;
149
150foo = new EmacsFoo (1, 2);
151
152becomes more or less equivalent to the following C code:
153
154struct emacs_foo
155{
156 int bar;
157};
158
159struct emacs_foo *
160make_emacs_foo (int token_a, int token_b)
161{
162 struct emacs_foo *foo;
163
164 foo = xmalloc (sizeof *foo);
165 foo->bar = token_a + token_b;
166
167 return foo;
168}
169
170/* ... */
171
172struct emacs_foo *foo;
173
174foo = make_emacs_foo (1, 2);
175
176A class may have any number of constructors, or no constructors at
177all, in which case the compiler inserts an empty constructor.
178
179
180
181Sometimes, you will see Java code that looks like this:
182
183 allFiles = filesDirectory.listFiles (new FileFilter () {
184 @Override
185 public boolean
186 accept (File file)
187 {
188 return (!file.isDirectory ()
189 && file.getName ().endsWith (".pdmp"));
190 }
191 });
192
193This is Java's version of GCC's nested function extension. The major
194difference is that the nested function may still be called even after
195it goes out of scope, and always retains a reference to the class and
196local variables around where it was called.
197
198Being an object-oriented language, Java also allows defining that a
199class ``extends'' another class. The following C code:
200
201struct a
202{
203 long thirty_two;
204};
205
206struct b
207{
208 struct a a;
209 long long sixty_four;
210};
211
212extern void do_something (struct a *);
213
214void
215my_function (struct b *b)
216{
217 do_something (&b->a);
218}
219
220is roughly equivalent to the following Java code, split into two
221files:
222
223 A.java
224
225public class A
226{
227 int thirtyTwo;
228
229 public void
230 doSomething ()
231 {
232 etcEtcEtc ();
233 }
234};
235
236 B.java
237
238public class B extends A
239{
240 long sixty_four;
241
242 public static void
243 myFunction (B b)
244 {
245 b.doSomething ();
246 }
247}
248
249the Java runtime has transformed the call to ``b.doSomething'' to
250``((A) b).doSomething''.
251
252However, Java also allows overriding this behavior, by specifying the
253@Override keyword:
254
255public class B extends A
256{
257 long sixty_four;
258
259 @Override
260 public void
261 doSomething ()
262 {
263 Something.doSomethingTwo ();
264 super.doSomething ();
265 }
266}
267
268now, any call to ``doSomething'' on a ``B'' created using ``new B ()''
269will end up calling ``Something.doSomethingTwo'', before calling back
270to ``A.doSomething''. This override also applies in reverse; that is
271to say, even if you write:
272
273 ((A) b).doSomething ();
274
275B's version of doSomething will still be called, if ``b'' was created
276using ``new B ()''.
277
278This mechanism is used extensively throughout the Java language and
279Android windowing APIs.
280
281Elsewhere, you will encounter Java code that defines arrays:
282
283public class EmacsFrobinicator
284{
285 public static void
286 emacsFrobinicate (int something)
287 {
288 int[] primesFromSomething;
289
290 primesFromSomething = new int[numberOfPrimes];
291 /* ... */
292 }
293}
294
295Java arrays are similar to C arrays in that they can not grow. But
296they are very much unlike C arrays in that they are always references
297(as opposed to decaying into pointers in various situations), and
298contain information about their length.
299
300If another function named ``frobinicate1'' takes an array as an
301argument, then it need not take the length of the array.
302
303Instead, it simply iterates over the array like so:
304
305int i, k;
306
307for (i = 0; i < array.length; ++i)
308 {
309 k = array[i];
310
311 Whatever.doSomethingWithK (k);
312 }
313
314The syntax used to define arrays is also slightly different. As
315arrays are always references, there is no way for you to tell the
316runtime to allocate an array of size N in a structure (class.)
317
318Instead, if you need an array of that size, you must declare a field
319with the type of the array, and allocate the array inside the class's
320constructor, like so:
321
322public class EmacsArrayContainer
323{
324 public int[] myArray;
325
326 public
327 EmacsArrayContainer ()
328 {
329 myArray = new array[10];
330 }
331}
332
333while in C, you could just have written:
334
335struct emacs_array_container
336{
337 int my_array[10];
338};
339
340or, possibly even better,
341
342typedef int my_array[10];
343
344Alas, Java has no equivalent of `typedef'.
345
346JAVA NATIVE INTERFACE
347
348Java also provides an interface for C code to interface with Java.
349
350C functions exported from a shared library become static Java
351functions within a class, like so:
352
353public class EmacsNative
354{
355 /* Obtain the fingerprint of this build of Emacs. The fingerprint
356 can be used to determine the dump file name. */
357 public static native String getFingerprint ();
358
359 /* Set certain parameters before initializing Emacs.
360
361 assetManager must be the asset manager associated with the
362 context that is loading Emacs. It is saved and remains for the
363 remainder the lifetime of the Emacs process.
364
365 filesDir must be the package's data storage location for the
366 current Android user.
367
368 libDir must be the package's data storage location for native
369 libraries. It is used as PATH.
370
371 cacheDir must be the package's cache directory. It is used as
372 the `temporary-file-directory'.
373
374 pixelDensityX and pixelDensityY are the DPI values that will be
375 used by Emacs.
376
377 classPath must be the classpath of this app_process process, or
378 NULL.
379
380 emacsService must be the EmacsService singleton, or NULL. */
381 public static native void setEmacsParams (AssetManager assetManager,
382 String filesDir,
383 String libDir,
384 String cacheDir,
385 float pixelDensityX,
386 float pixelDensityY,
387 String classPath,
388 EmacsService emacsService);
389}
390
391Where the corresponding C functions are located in android.c, and
392loaded by the special invocation:
393
394 static
395 {
396 System.loadLibrary ("emacs");
397 };
398
399
400See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html
401for more details.
402
403
404
405OVERVIEW OF ANDROID
406
407When the Android system starts an application, it does not actually
408call the application's ``main'' function. It may not even start the
409application's process if one is already running.
410
411Instead, Android is organized around components. When the user opens
412the ``Emacs'' icon, the Android system looks up and starts the
413component associated with the ``Emacs'' icon. In this case, the
414component is called an activity, and is declared in
415the AndroidManifest.xml in this directory:
416
417 <activity android:name="org.gnu.emacs.EmacsActivity"
418 android:launchMode="singleTop"
419 android:windowSoftInputMode="adjustResize"
420 android:exported="true"
421 android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
422 <intent-filter>
423 <action android:name="android.intent.action.MAIN" />
424 <category android:name="android.intent.category.DEFAULT" />
425 <category android:name="android.intent.category.LAUNCHER" />
426 </intent-filter>
427 </activity>
428
429This tells Android to start the activity defined in ``EmacsActivity''
430(defined in org/gnu/emacs/EmacsActivity.java), a class extending the
431Android class ``Activity''.
432
433To do so, the Android system creates an instance of ``EmacsActivity''
434and the window system window associated with it, and eventually calls:
435
436 Activity activity;
437
438 activity.onCreate (...);
439
440But which ``onCreate'' is really called?
441It is actually the ``onCreate'' defined in EmacsActivity.java, as
442it overrides the ``onCreate'' defined in Android's own Activity class:
443
444 @Override
445 public void
446 onCreate (Bundle savedInstanceState)
447 {
448 FrameLayout.LayoutParams params;
449 Intent intent;
450
451Then, this is what happens step-by-step within the ``onCreate''
452function:
453
454 /* See if Emacs should be started with -Q. */
455 intent = getIntent ();
456 EmacsService.needDashQ
457 = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
458 false);
459
460Here, Emacs obtains the intent (a request to start a component) which
461was used to start Emacs, and sets a special flag if it contains a
462request for Emacs to start with the ``-Q'' command-line argument.
463
464 /* Set the theme to one without a title bar. */
465
466 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
467 setTheme (android.R.style.Theme_DeviceDefault_NoActionBar);
468 else
469 setTheme (android.R.style.Theme_NoTitleBar);
470
471Next, Emacs sets an appropriate theme for the activity's associated
472window decorations.
473
474 params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT,
475 LayoutParams.MATCH_PARENT);
476
477 /* Make the frame layout. */
478 layout = new FrameLayout (this);
479 layout.setLayoutParams (params);
480
481 /* Set it as the content view. */
482 setContentView (layout);
483
484Then, Emacs creates a ``FrameLayout'', a widget that holds a single
485other widget, and makes it the activity's ``content view''.
486
487The activity itself is a ``FrameLayout'', so the ``layout parameters''
488here apply to the FrameLayout itself, and not its children.
489
490 /* Maybe start the Emacs service if necessary. */
491 EmacsService.startEmacsService (this);
492
493And after that, Emacs calls the static function ``startEmacsService'',
494defined in the class ``EmacsService''. This starts the Emacs service
495component if necessary.
496
497 /* Add this activity to the list of available activities. */
498 EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
499
500 super.onCreate (savedInstanceState);
501
502Finally, Emacs registers that this activity is now ready to receive
503top-level frames (windows) created from Lisp.
504
505Activities come and go, but Emacs has to stay running in the mean
506time. Thus, Emacs also defines a ``service'', which is a long-running
507component that the Android system allows to run in the background.
508
509Let us go back and review the definition of ``startEmacsService'':
510
511 public static void
512 startEmacsService (Context context)
513 {
514 if (EmacsService.SERVICE == null)
515 {
516 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
517 /* Start the Emacs service now. */
518 context.startService (new Intent (context,
519 EmacsService.class));
520 else
521 /* Display the permanant notification and start Emacs as a
522 foreground service. */
523 context.startForegroundService (new Intent (context,
524 EmacsService.class));
525 }
526 }
527
528If ``EmacsService.SERVICE'' does not yet exist, what this does is to
529tell the ``context'' (the equivalent of an Xlib Display *) to start a
530service defined by the class ``EmacsService''. Eventually, this
531results in ``EmacsService.onCreate'' being called:
532
533 @Override
534 public void
535 onCreate ()
536 {
537 AssetManager manager;
538 Context app_context;
539 String filesDir, libDir, cacheDir, classPath;
540 double pixelDensityX;
541 double pixelDensityY;
542
543Here is what this function does, step-by-step:
544
545 SERVICE = this;
546
547First, it sets the special static variable ``SERVICE'' to ``this'',
548which is a pointer to the ``EmacsService' object that was created.
549
550 handler = new Handler (Looper.getMainLooper ());
551
552Next, it creates a ``Handler'' object for the ``main looper''.
553This is a helper structure which allows executing code on the Android
554user interface thread.
555
556 manager = getAssets ();
557 app_context = getApplicationContext ();
558 metrics = getResources ().getDisplayMetrics ();
559 pixelDensityX = metrics.xdpi;
560 pixelDensityY = metrics.ydpi;
561
562Finally, it obtains:
563
564 - the asset manager, which is used to retrieve assets packaged
565 into the Emacs application package.
566
567 - the application context, used to obtain application specific
568 information.
569
570 - the display metrics, and from them, the X and Y densities in dots
571 per inch.
572
573Then, inside a ``try'' block:
574
575 try
576 {
577 /* Configure Emacs with the asset manager and other necessary
578 parameters. */
579 filesDir = app_context.getFilesDir ().getCanonicalPath ();
580 libDir = getLibraryDirectory ();
581 cacheDir = app_context.getCacheDir ().getCanonicalPath ();
582
583It obtains the names of the Emacs home, shared library, and temporary
584file directories.
585
586 /* Now provide this application's apk file, so a recursive
587 invocation of app_process (through android-emacs) can
588 find EmacsNoninteractive. */
589 classPath = getApkFile ();
590
591The name of the Emacs application package.
592
593 Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
594 + ", libDir = " + libDir + ", and classPath = " + classPath);
595
596Prints a debug message to the Android system log with this
597information.
598
599 EmacsNative.setEmacsParams (manager, filesDir, libDir,
600 cacheDir, (float) pixelDensityX,
601 (float) pixelDensityY,
602 classPath, this);
603
604And calls the native function ``setEmacsParams'' (defined in
605android.c) to configure Emacs with this information.
606
607 /* Start the thread that runs Emacs. */
608 thread = new EmacsThread (this, needDashQ);
609 thread.start ();
610
611Then, it allocates an ``EmacsThread'' object, and starts that thread.
612Inside that thread is where Emacs's C code runs.
613
614 }
615 catch (IOException exception)
616 {
617 EmacsNative.emacsAbort ();
618 return;
619
620And here is the purpose of the ``try'' block. Functions related to
621file names in Java will signal errors of various types upon failure.
622
623This ``catch'' block means that the Java virtual machine will abort
624execution of the contents of the ``try'' block as soon as an error of
625type ``IOException'' is encountered, and begin executing the contents
626of the ``catch'' block.
627
628Any failure of that type here is a crash, and
629``EmacsNative.emacsAbort'' is called to quickly abort the process to
630get a useful backtrace.
631 }
632 }
633
634Now, let us look at the definition of the class ``EmacsThread'', found
635in org/gnu/emacs/EmacsThread.java:
636
637public class EmacsThread extends Thread
638{
639 /* Whether or not Emacs should be started -Q. */
640 private boolean startDashQ;
641
642 public
643 EmacsThread (EmacsService service, boolean startDashQ)
644 {
645 super ("Emacs main thread");
646 this.startDashQ = startDashQ;
647 }
648
649 @Override
650 public void
651 run ()
652 {
653 String args[];
654
655 if (!startDashQ)
656 args = new String[] { "libandroid-emacs.so", };
657 else
658 args = new String[] { "libandroid-emacs.so", "-Q", };
659
660 /* Run the native code now. */
661 EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
662 }
663};
664
665The class itself defines a single field, ``startDashQ'', a constructor
666with an unused argument of the type ``EmacsService'' (which is useful
667while debugging) and a flag ``startDashQ'', and a single function
668``run'', overriding the same function in the class ``Thread''.
669
670When ``thread.start'' is called, the Java virtual machine creates a
671new thread, and then calls the function ``run'' within that thread.
672
673This function then computes a suitable argument vector, and calls
674``EmacsNative.initEmacs'' (defined in android.c), which then calls a
675modified version of the regular Emacs ``main'' function.
676
677At that point, Emacs initialization proceeds as usual:
678Vinitial_window_system is set, loadup.el calls `normal-top-level',
679which calls `command-line', and finally
680`window-system-initialization', which initializes the `android'
681terminal interface as usual.
682
683What happens here is the same as on other platforms. Now, here is
684what happens when the initial frame is created: Fx_create_frame calls
685`android_create_frame_window' to create a top level window:
686
687static void
688android_create_frame_window (struct frame *f)
689{
690 struct android_set_window_attributes attributes;
691 enum android_window_value_mask attribute_mask;
692
693 attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f);
694 attribute_mask = ANDROID_CW_BACK_PIXEL;
695
696 block_input ();
697 FRAME_ANDROID_WINDOW (f)
698 = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
699 f->left_pos,
700 f->top_pos,
701 FRAME_PIXEL_WIDTH (f),
702 FRAME_PIXEL_HEIGHT (f),
703 attribute_mask, &attributes);
704 unblock_input ();
705}
706
707This calls the function `android_create_window' with some arguments
708whose meanings are identical to the arguments to `XCreateWindow'.
709
710Here is the definition of `android_create_window', in android.c:
711
712android_window
713android_create_window (android_window parent, int x, int y,
714 int width, int height,
715 enum android_window_value_mask value_mask,
716 struct android_set_window_attributes *attrs)
717{
718 static jclass class;
719 static jmethodID constructor;
720 jobject object, parent_object, old;
721 android_window window;
722 android_handle prev_max_handle;
723 bool override_redirect;
724
725What does it do? First, some context:
726
727At any time, there can be at most 65535 Java objects referred to by
728the rest of Emacs through the Java native interface. Each such object
729is assigned a ``handle'' (similar to an XID on X) and given a unique
730type. The function `android_resolve_handle' returns the JNI `jobject'
731associated with a given handle.
732
733 parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
734
735Here, it is being used to look up the `jobject' associated with the
736`parent' handle.
737
738 prev_max_handle = max_handle;
739 window = android_alloc_id ();
740
741Next, `max_handle' is saved, and a new handle is allocated for
742`window'.
743
744 if (!window)
745 error ("Out of window handles!");
746
747An error is signalled if Emacs runs out of available handles.
748
749 if (!class)
750 {
751 class = (*android_java_env)->FindClass (android_java_env,
752 "org/gnu/emacs/EmacsWindow");
753 assert (class != NULL);
754
755Then, if this initialization has not yet been completed, Emacs
756proceeds to find the Java class named ``EmacsWindow''.
757
758 constructor
759 = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
760 "(SLorg/gnu/emacs/EmacsWindow;"
761 "IIIIZ)V");
762 assert (constructor != NULL);
763
764And it tries to look up the constructor, which should take seven
765arguments:
766
767 S - a short. (the handle ID)
768 Lorg/gnu/Emacs/EmacsWindow; - an instance of the EmacsWindow
769 class. (the parent)
770 IIII - four ints. (the window geometry.)
771 Z - a boolean. (whether or not the
772 window is override-redirect; see
773 XChangeWindowAttributes.)
774
775 old = class;
776 class = (*android_java_env)->NewGlobalRef (android_java_env, class);
777 (*android_java_env)->ExceptionClear (android_java_env);
778 ANDROID_DELETE_LOCAL_REF (old);
779
780Next, it saves a global reference to the class and deletes the local
781reference. Global references will never be deallocated by the Java
782virtual machine as long as they still exist.
783
784 if (!class)
785 memory_full (0);
786 }
787
788 /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
789 creation time. */
790 override_redirect = ((value_mask
791 & ANDROID_CW_OVERRIDE_REDIRECT)
792 && attrs->override_redirect);
793
794 object = (*android_java_env)->NewObject (android_java_env, class,
795 constructor, (jshort) window,
796 parent_object, (jint) x, (jint) y,
797 (jint) width, (jint) height,
798 (jboolean) override_redirect);
799
800Then, it creates an instance of the ``EmacsWindow'' class with the
801appropriate arguments and previously determined constructor.
802
803 if (!object)
804 {
805 (*android_java_env)->ExceptionClear (android_java_env);
806
807 max_handle = prev_max_handle;
808 memory_full (0);
809
810If creating the object fails, Emacs clears the ``pending exception''
811and signals that it is out of memory.
812 }
813
814 android_handles[window].type = ANDROID_HANDLE_WINDOW;
815 android_handles[window].handle
816 = (*android_java_env)->NewGlobalRef (android_java_env,
817 object);
818 (*android_java_env)->ExceptionClear (android_java_env);
819 ANDROID_DELETE_LOCAL_REF (object);
820
821Otherwise, it associates a new global reference to the object with the
822handle, and deletes the local reference returned from the JNI
823NewObject function.
824
825 if (!android_handles[window].handle)
826 memory_full (0);
827
828If allocating the global reference fails, Emacs signals that it is out
829of memory.
830
831 android_change_window_attributes (window, value_mask, attrs);
832 return window;
833
834Otherwise, it applies the specified window attributes and returns the
835handle of the new window.
836}
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;
22import java.io.File; 22import java.io.File;
23import java.io.FileFilter; 23import java.io.FileFilter;
24 24
25import android.content.Context;
26
25import android.app.Application; 27import android.app.Application;
26import android.util.Log; 28import android.util.Log;
27 29
28public class EmacsApplication extends Application implements FileFilter 30public class EmacsApplication extends Application
29{ 31{
30 private static final String TAG = "EmacsApplication"; 32 private static final String TAG = "EmacsApplication";
31 33
32 /* The name of the dump file to use. */ 34 /* The name of the dump file to use. */
33 public static String dumpFileName; 35 public static String dumpFileName;
34 36
35 @Override 37 public static void
36 public boolean 38 findDumpFile (Context context)
37 accept (File file)
38 {
39 return (!file.isDirectory ()
40 && file.getName ().endsWith (".pdmp"));
41 }
42
43 @Override
44 public void
45 onCreate ()
46 { 39 {
47 File filesDirectory; 40 File filesDirectory;
48 File[] allFiles; 41 File[] allFiles;
@@ -52,13 +45,19 @@ public class EmacsApplication extends Application implements FileFilter
52 wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint () 45 wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint ()
53 + ".pdmp"); 46 + ".pdmp");
54 47
55 Log.d (TAG, "onCreate: looking for " + wantedDumpFile);
56
57 /* Obtain a list of all files ending with ``.pdmp''. Then, look 48 /* Obtain a list of all files ending with ``.pdmp''. Then, look
58 for a file named ``emacs-<fingerprint>.pdmp'' and delete the 49 for a file named ``emacs-<fingerprint>.pdmp'' and delete the
59 rest. */ 50 rest. */
60 filesDirectory = getFilesDir (); 51 filesDirectory = context.getFilesDir ();
61 allFiles = filesDirectory.listFiles (this); 52 allFiles = filesDirectory.listFiles (new FileFilter () {
53 @Override
54 public boolean
55 accept (File file)
56 {
57 return (!file.isDirectory ()
58 && file.getName ().endsWith (".pdmp"));
59 }
60 });
62 61
63 /* Now try to find the right dump file. */ 62 /* Now try to find the right dump file. */
64 for (i = 0; i < allFiles.length; ++i) 63 for (i = 0; i < allFiles.length; ++i)
@@ -69,9 +68,13 @@ public class EmacsApplication extends Application implements FileFilter
69 /* Delete this outdated dump file. */ 68 /* Delete this outdated dump file. */
70 allFiles[i].delete (); 69 allFiles[i].delete ();
71 } 70 }
71 }
72 72
73 Log.d (TAG, "onCreate: found " + dumpFileName); 73 @Override
74 74 public void
75 onCreate ()
76 {
77 findDumpFile (this);
75 super.onCreate (); 78 super.onCreate ();
76 } 79 }
77}; 80};
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
29 can be used to determine the dump file name. */ 29 can be used to determine the dump file name. */
30 public static native String getFingerprint (); 30 public static native String getFingerprint ();
31 31
32 /* Set certain parameters before initializing Emacs. This proves 32 /* Set certain parameters before initializing Emacs.
33 that libemacs.so is being loaded from Java code.
34 33
35 assetManager must be the asset manager associated with the 34 assetManager must be the asset manager associated with the
36 context that is loading Emacs. It is saved and remains for the 35 context that is loading Emacs. It is saved and remains for the
@@ -48,19 +47,26 @@ public class EmacsNative
48 pixelDensityX and pixelDensityY are the DPI values that will be 47 pixelDensityX and pixelDensityY are the DPI values that will be
49 used by Emacs. 48 used by Emacs.
50 49
51 emacsService must be the emacsService singleton. */ 50 classPath must be the classpath of this app_process process, or
51 NULL.
52
53 emacsService must be the EmacsService singleton, or NULL. */
52 public static native void setEmacsParams (AssetManager assetManager, 54 public static native void setEmacsParams (AssetManager assetManager,
53 String filesDir, 55 String filesDir,
54 String libDir, 56 String libDir,
55 String cacheDir, 57 String cacheDir,
56 float pixelDensityX, 58 float pixelDensityX,
57 float pixelDensityY, 59 float pixelDensityY,
60 String classPath,
58 EmacsService emacsService); 61 EmacsService emacsService);
59 62
60 /* Initialize Emacs with the argument array ARGV. Each argument 63 /* Initialize Emacs with the argument array ARGV. Each argument
61 must contain a NULL terminated string, or else the behavior is 64 must contain a NULL terminated string, or else the behavior is
62 undefined. */ 65 undefined.
63 public static native void initEmacs (String argv[]); 66
67 DUMPFILE is the dump file to use, or NULL if Emacs is to load
68 loadup.el itself. */
69 public static native void initEmacs (String argv[], String dumpFile);
64 70
65 /* Abort and generate a native core dump. */ 71 /* Abort and generate a native core dump. */
66 public static native void emacsAbort (); 72 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 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or (at
10your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20package org.gnu.emacs;
21
22import android.os.Looper;
23import android.os.Build;
24
25import android.content.Context;
26import android.content.res.AssetManager;
27
28import java.lang.reflect.Constructor;
29import java.lang.reflect.Method;
30
31/* Noninteractive Emacs.
32
33 This is the class that libandroid-emacs.so starts.
34 libandroid-emacs.so figures out the system classpath, then starts
35 dalvikvm with the framework jars.
36
37 At that point, dalvikvm calls main, which sets up the main looper,
38 creates an ActivityThread and attaches it to the main thread.
39
40 Then, it obtains an application context for the LoadedApk in the
41 application thread.
42
43 Finally, it obtains the necessary context specific objects and
44 initializes Emacs. */
45
46@SuppressWarnings ("unchecked")
47public class EmacsNoninteractive
48{
49 private static String
50 getLibraryDirectory (Context context)
51 {
52 int apiLevel;
53
54 apiLevel = Build.VERSION.SDK_INT;
55
56 if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
57 return context.getApplicationInfo().nativeLibraryDir;
58 else if (apiLevel >= Build.VERSION_CODES.DONUT)
59 return context.getApplicationInfo().dataDir + "/lib";
60
61 return "/data/data/" + context.getPackageName() + "/lib";
62 }
63
64 public static void
65 main (String[] args)
66 {
67 Object activityThread, loadedApk;
68 Class activityThreadClass, loadedApkClass, contextImplClass;
69 Class compatibilityInfoClass;
70 Method method;
71 Context context;
72 AssetManager assets;
73 String filesDir, libDir, cacheDir;
74
75 Looper.prepare ();
76 context = null;
77 assets = null;
78 filesDir = libDir = cacheDir = null;
79
80 try
81 {
82 /* Get the activity thread. */
83 activityThreadClass = Class.forName ("android.app.ActivityThread");
84
85 /* Get the systemMain method. */
86 method = activityThreadClass.getMethod ("systemMain");
87
88 /* Create and attach the activity thread. */
89 activityThread = method.invoke (null);
90
91 /* Now get an LoadedApk. */
92 loadedApkClass = Class.forName ("android.app.LoadedApk");
93
94 /* Get a LoadedApk. How to do this varies by Android version.
95 On Android 2.3.3 and earlier, there is no
96 ``compatibilityInfo'' argument to getPackageInfo. */
97
98 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD)
99 {
100 method
101 = activityThreadClass.getMethod ("getPackageInfo",
102 String.class,
103 int.class);
104 loadedApk = method.invoke (activityThread, "org.gnu.emacs",
105 0);
106 }
107 else
108 {
109 compatibilityInfoClass
110 = Class.forName ("android.content.res.CompatibilityInfo");
111
112 method
113 = activityThreadClass.getMethod ("getPackageInfo",
114 String.class,
115 compatibilityInfoClass,
116 int.class);
117 loadedApk = method.invoke (activityThread, "org.gnu.emacs", null,
118 0);
119 }
120
121 if (loadedApk == null)
122 throw new RuntimeException ("getPackageInfo returned NULL");
123
124 /* Now, get a context. */
125 contextImplClass = Class.forName ("android.app.ContextImpl");
126 method = contextImplClass.getDeclaredMethod ("createAppContext",
127 activityThreadClass,
128 loadedApkClass);
129 method.setAccessible (true);
130 context = (Context) method.invoke (null, activityThread, loadedApk);
131
132 /* Don't actually start the looper or anything. Instead, obtain
133 an AssetManager. */
134 assets = context.getAssets ();
135
136 /* Now configure Emacs. The class path should already be set. */
137
138 filesDir = context.getFilesDir ().getCanonicalPath ();
139 libDir = getLibraryDirectory (context);
140 cacheDir = context.getCacheDir ().getCanonicalPath ();
141 }
142 catch (Exception e)
143 {
144 System.err.println ("Internal error: " + e);
145 System.err.println ("This means that the Android platform changed,");
146 System.err.println ("and that Emacs needs adjustments in order to");
147 System.err.println ("obtain required system internal resources.");
148 System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org.");
149
150 System.exit (1);
151 }
152
153 EmacsNative.setEmacsParams (assets, filesDir,
154 libDir, cacheDir, 0.0f,
155 0.0f, null, null);
156
157 /* Now find the dump file that Emacs should use, if it has already
158 been dumped. */
159 EmacsApplication.findDumpFile (context);
160
161 /* Start Emacs. */
162 EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
163 }
164};
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;
41 41
42import android.content.Context; 42import android.content.Context;
43import android.content.Intent; 43import android.content.Intent;
44import android.content.pm.ApplicationInfo;
45import android.content.pm.PackageManager.ApplicationInfoFlags;
46import android.content.pm.PackageManager;
44import android.content.res.AssetManager; 47import android.content.res.AssetManager;
45 48
46import android.net.Uri; 49import android.net.Uri;
@@ -118,8 +121,38 @@ public class EmacsService extends Service
118 return null; 121 return null;
119 } 122 }
120 123
124 @SuppressWarnings ("deprecation")
125 private String
126 getApkFile ()
127 {
128 PackageManager manager;
129 ApplicationInfo info;
130
131 manager = getPackageManager ();
132
133 try
134 {
135 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
136 info = manager.getApplicationInfo ("org.gnu.emacs", 0);
137 else
138 info = manager.getApplicationInfo ("org.gnu.emacs",
139 ApplicationInfoFlags.of (0));
140
141 /* Return an empty string upon failure. */
142
143 if (info.sourceDir != null)
144 return info.sourceDir;
145
146 return "";
147 }
148 catch (Exception e)
149 {
150 return "";
151 }
152 }
153
121 @TargetApi (Build.VERSION_CODES.GINGERBREAD) 154 @TargetApi (Build.VERSION_CODES.GINGERBREAD)
122 String 155 private String
123 getLibraryDirectory () 156 getLibraryDirectory ()
124 { 157 {
125 int apiLevel; 158 int apiLevel;
@@ -142,7 +175,7 @@ public class EmacsService extends Service
142 { 175 {
143 AssetManager manager; 176 AssetManager manager;
144 Context app_context; 177 Context app_context;
145 String filesDir, libDir, cacheDir; 178 String filesDir, libDir, cacheDir, classPath;
146 double pixelDensityX; 179 double pixelDensityX;
147 double pixelDensityY; 180 double pixelDensityY;
148 181
@@ -162,13 +195,18 @@ public class EmacsService extends Service
162 libDir = getLibraryDirectory (); 195 libDir = getLibraryDirectory ();
163 cacheDir = app_context.getCacheDir ().getCanonicalPath (); 196 cacheDir = app_context.getCacheDir ().getCanonicalPath ();
164 197
198 /* Now provide this application's apk file, so a recursive
199 invocation of app_process (through android-emacs) can
200 find EmacsNoninteractive. */
201 classPath = getApkFile ();
202
165 Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir 203 Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
166 + " and libDir = " + libDir); 204 + ", libDir = " + libDir + ", and classPath = " + classPath);
167 205
168 EmacsNative.setEmacsParams (manager, filesDir, libDir, 206 EmacsNative.setEmacsParams (manager, filesDir, libDir,
169 cacheDir, (float) pixelDensityX, 207 cacheDir, (float) pixelDensityX,
170 (float) pixelDensityY, 208 (float) pixelDensityY,
171 this); 209 classPath, this);
172 210
173 /* Start the thread that runs Emacs. */ 211 /* Start the thread that runs Emacs. */
174 thread = new EmacsThread (this, needDashQ); 212 thread = new EmacsThread (this, needDashQ);
@@ -491,8 +529,6 @@ public class EmacsService extends Service
491 public static void 529 public static void
492 startEmacsService (Context context) 530 startEmacsService (Context context)
493 { 531 {
494 PendingIntent intent;
495
496 if (EmacsService.SERVICE == null) 532 if (EmacsService.SERVICE == null)
497 { 533 {
498 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) 534 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
33 this.startDashQ = startDashQ; 33 this.startDashQ = startDashQ;
34 } 34 }
35 35
36 @Override
36 public void 37 public void
37 run () 38 run ()
38 { 39 {
39 String args[]; 40 String args[];
40 41
41 if (EmacsApplication.dumpFileName == null) 42 if (!startDashQ)
42 { 43 args = new String[] { "libandroid-emacs.so", };
43 if (!startDashQ)
44 args = new String[] { "libandroid-emacs.so", };
45 else
46 args = new String[] { "libandroid-emacs.so", "-Q", };
47 }
48 else 44 else
49 { 45 args = new String[] { "libandroid-emacs.so", "-Q", };
50 if (!startDashQ)
51 args = new String[] { "libandroid-emacs.so", "--dump-file",
52 EmacsApplication.dumpFileName, };
53 else
54 args = new String[] { "libandroid-emacs.so", "-Q",
55 "--dump-file",
56 EmacsApplication.dumpFileName, };
57 }
58 46
59 /* Run the native code now. */ 47 /* Run the native code now. */
60 EmacsNative.initEmacs (args); 48 EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
61 } 49 }
62}; 50};