aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPo Lu2023-01-25 18:44:47 +0800
committerPo Lu2023-01-25 18:44:47 +0800
commit0900bfbcc57c555909cb75c38eb0ed26fb6964ef (patch)
tree9a2fa4328defab79f1cb3dcfac4f3c071bf0a633
parent6f9a2a8f29c7faf13d0d86001b140746efc455b5 (diff)
downloademacs-0900bfbcc57c555909cb75c38eb0ed26fb6964ef.tar.gz
emacs-0900bfbcc57c555909cb75c38eb0ed26fb6964ef.zip
Update Android port
* doc/emacs/android.texi (Android Startup, Android Environment): Document that restrictions on starting Emacs have been lifted. * java/README: Document Java for Emacs developers and how the Android port works. * java/org/gnu/emacs/EmacsApplication.java (EmacsApplication) (findDumpFile): New function. (onCreate): Factor out dump file finding functions to there. * java/org/gnu/emacs/EmacsNative.java (EmacsNative): Update function declarations. * java/org/gnu/emacs/EmacsNoninteractive.java (EmacsNoninteractive): New class. * java/org/gnu/emacs/EmacsService.java (EmacsService, getApkFile) (onCreate): Pass classpath to setEmacsParams. * java/org/gnu/emacs/EmacsThread.java (EmacsThread): Make run an override. * lisp/loadup.el: Don't dump on Android when noninteractive. * lisp/shell.el (shell--command-completion-data): Handle inaccessible directories. * src/Makefile.in (android-emacs): Link with gnulib. * src/android-emacs.c (main): Implement to launch app-process and then EmacsNoninteractive. * src/android.c (setEmacsParams): New argument `class_path'. Don't set stuff up when running noninteractive. * src/android.h (initEmacs): Likewise. * src/androidfont.c (init_androidfont): * src/androidselect.c (init_androidselect): Don't initialize when running noninteractive. * src/emacs.c (load_pdump): New argument `dump_file'. (android_emacs_init): Give new argument `dump_file' to `load_pdump'. * src/sfntfont-android.c (init_sfntfont_android): Don't initialize when running noninteractive.
-rw-r--r--doc/emacs/android.texi18
-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
-rw-r--r--lisp/loadup.el33
-rw-r--r--lisp/shell.el7
-rw-r--r--src/Makefile.in2
-rw-r--r--src/android-emacs.c83
-rw-r--r--src/android.c114
-rw-r--r--src/android.h8
-rw-r--r--src/androidfont.c3
-rw-r--r--src/androidselect.c3
-rw-r--r--src/emacs.c28
-rw-r--r--src/sfntfont-android.c3
17 files changed, 1306 insertions, 109 deletions
diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index cda0eadf226..e910e482ad8 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -102,8 +102,7 @@ files directory, containing an identifier unique to this copy of
102Emacs. 102Emacs.
103 103
104 The next time that same copy of Emacs starts up, it simply loads the 104 The next time that same copy of Emacs starts up, it simply loads the
105preloaded Lisp files contained in that dump file, greatly improving 105data contained in that dump file, greatly improving start up time.
106start up time.
107 106
108 However, if by some unforseen circumstance the dump file is 107 However, if by some unforseen circumstance the dump file is
109corrupted, Emacs can crash. If that happens, the dump file stored in 108corrupted, Emacs can crash. If that happens, the dump file stored in
@@ -192,12 +191,17 @@ installed. This means, instead of specifying @code{ctags} or
192@code{libctags.so} or @code{libemacsclient.so} on the commnd line 191@code{libctags.so} or @code{libemacsclient.so} on the commnd line
193instead when starting either of those programs in a subprocess. 192instead when starting either of those programs in a subprocess.
194 193
195@c TODO: remove this limitation. 194 The @file{/assets} directory containing Emacs start-up files is
196 In addition, the @file{/assets} directory containing Emacs start-up 195supposed to be inaccessible to processes not directly created by
197files is inaccessible to processes not directly created by
198@code{zygote}, the system service responsible for starting 196@code{zygote}, the system service responsible for starting
199applications. This makes it impossible to run Emacs in a subprocess 197applications. Since required Lisp is found in the @file{/assets}
200within itself. 198directory, it would thus follow that it is not possible for Emacs to
199start itself as a subprocess. A special binary named
200@command{libandroid-emacs.so} is provided with Emacs, and does its
201best to start Emacs, for the purpose of running Lisp in batch mode.
202However, the approach it takes was devised by reading Android source
203code, and is not sanctioned by the Android compatibility definition
204documents, so your mileage may vary.
201 205
202@section Running Emacs in the background 206@section Running Emacs in the background
203@cindex emacs killed, android 207@cindex emacs killed, android
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};
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 8d2e4bbb7c2..1747d1d960a 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -560,22 +560,23 @@ lost after dumping")))
560 ;; different build fingerprint upon being created, which happens 560 ;; different build fingerprint upon being created, which happens
561 ;; the moment the Android system starts Emacs. Then, it passes 561 ;; the moment the Android system starts Emacs. Then, it passes
562 ;; the appropriate "--dump-file" to libemacs.so as it starts. 562 ;; the appropriate "--dump-file" to libemacs.so as it starts.
563 (let ((temp-dir (getenv "TEMP")) 563 (when (not noninteractive)
564 (dump-file-name (format "%semacs-%s.pdmp" 564 (let ((temp-dir (getenv "TEMP"))
565 (file-name-as-directory "~") 565 (dump-file-name (format "%semacs-%s.pdmp"
566 pdumper-fingerprint)) 566 (file-name-as-directory "~")
567 (dump-temp-file-name (format "%s~emacs-%s.pdmp" 567 pdumper-fingerprint))
568 (file-name-as-directory "~") 568 (dump-temp-file-name (format "%s~emacs-%s.pdmp"
569 pdumper-fingerprint))) 569 (file-name-as-directory "~")
570 (unless (pdumper-stats) 570 pdumper-fingerprint)))
571 (condition-case () 571 (unless (pdumper-stats)
572 (progn 572 (condition-case ()
573 (dump-emacs-portable dump-temp-file-name) 573 (progn
574 ;; Move the dumped file to the actual dump file name. 574 (dump-emacs-portable dump-temp-file-name)
575 (rename-file dump-temp-file-name dump-file-name) 575 ;; Move the dumped file to the actual dump file name.
576 ;; Continue with loadup. 576 (rename-file dump-temp-file-name dump-file-name)
577 nil) 577 ;; Continue with loadup.
578 (error nil))))) 578 nil)
579 (error nil))))))
579 (if dump-mode 580 (if dump-mode
580 (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp") 581 (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp")
581 ((equal dump-mode "dump") "emacs") 582 ((equal dump-mode "dump") "emacs")
diff --git a/lisp/shell.el b/lisp/shell.el
index 5cf108bfa3b..29f7d5c02d4 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -1332,7 +1332,12 @@ Returns t if successful."
1332 (while path-dirs 1332 (while path-dirs
1333 (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) "."))) 1333 (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) ".")))
1334 comps-in-dir (and (file-accessible-directory-p dir) 1334 comps-in-dir (and (file-accessible-directory-p dir)
1335 (file-name-all-completions filenondir dir))) 1335 (condition-case nil
1336 (file-name-all-completions filenondir dir)
1337 ;; Systems such as Android sometimes
1338 ;; put inaccessible directories in
1339 ;; PATH.
1340 (permission-denied nil))))
1336 ;; Go thru each completion found, to see whether it should be used. 1341 ;; Go thru each completion found, to see whether it should be used.
1337 (while comps-in-dir 1342 (while comps-in-dir
1338 (setq file (car comps-in-dir) 1343 (setq file (car comps-in-dir)
diff --git a/src/Makefile.in b/src/Makefile.in
index 29beb519abb..fe745770b9f 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -768,7 +768,7 @@ libemacs.so: $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(EMACSRES) \
768 768
769android-emacs: libemacs.so android-emacs.o 769android-emacs: libemacs.so android-emacs.o
770 $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(LDFLAGS) \ 770 $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(LDFLAGS) \
771 -L. "-l:libemacs.so" android-emacs.o 771 $(LIBEGNU_ARCHIVE) -L. "-l:libemacs.so" android-emacs.o
772endif 772endif
773 773
774## The following oldxmenu-related rules are only (possibly) used if 774## The following oldxmenu-related rules are only (possibly) used if
diff --git a/src/android-emacs.c b/src/android-emacs.c
index d4fa14e39fb..c1f2a6f43bb 100644
--- a/src/android-emacs.c
+++ b/src/android-emacs.c
@@ -18,13 +18,88 @@ You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ 18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19 19
20#include <config.h> 20#include <config.h>
21#include "android.h" 21#include <stdio.h>
22#include <alloca.h>
23#include <string.h>
24#include <unistd.h>
22 25
23/* android-emacs is a wrapper around libemacs. It simply calls 26/* android-emacs is a wrapper around /system/bin/app_process(64).
24 android_emacs_init with the argv and argc given to it. */ 27 It invokes app_process(64) with the right class path and then
28 starts org.gnu.emacs.EmacsNoninteractive.
29
30 The main function in that class tries to load an activity thread
31 and obtain a context and asset manager before calling
32 android_emacs_init, which is required for Emacs to find required
33 preloaded Lisp. */
25 34
26int 35int
27main (int argc, char **argv) 36main (int argc, char **argv)
28{ 37{
29 return android_emacs_init (argc, argv); 38 char **args;
39 int i;
40 char *bootclasspath, *emacs_class_path;
41
42 /* Allocate enough to hold the arguments to app_process. */
43 args = alloca ((10 + argc) * sizeof *args);
44
45 /* Clear args. */
46 memset (args, 0, (10 + argc) * sizeof *args);
47
48 /* First, figure out what program to start. */
49#if defined __x86_64__ || defined __aarch64__
50 args[0] = (char *) "/system/bin/app_process64";
51#else
52 args[0] = (char *) "/system/bin/app_process";
53#endif
54
55 /* Next, obtain the boot class path. */
56 bootclasspath = getenv ("BOOTCLASSPATH");
57
58 /* And the Emacs class path. */
59 emacs_class_path = getenv ("EMACS_CLASS_PATH");
60
61 if (!bootclasspath)
62 {
63 fprintf (stderr, "The BOOTCLASSPATH environment variable"
64 " is not set. As a result, Emacs does not know"
65 " how to start app_process.\n"
66 "This is likely a change in the Android platform."
67 " Please report this to bug-gnu-emacs@gnu.org.\n");
68 return 1;
69 }
70
71 if (!emacs_class_path)
72 {
73 fprintf (stderr, "EMACS_CLASS_PATH not set."
74 " Please make sure Emacs is being started"
75 " from within a running copy of Emacs.\n");
76 return 1;
77 }
78
79 if (asprintf (&bootclasspath, "-Djava.class.path=%s:%s",
80 bootclasspath, emacs_class_path) < 0)
81 {
82 perror ("asprintf");
83 return 1;
84 }
85
86 args[1] = bootclasspath;
87 args[2] = (char *) "/system/bin";
88 args[3] = (char *) "--nice-name=emacs";
89 args[4] = (char *) "org.gnu.emacs.EmacsNoninteractive";
90
91 /* Arguments from here on are passed to main in
92 EmacsNoninteractive.java. */
93 args[5] = argv[0];
94
95 /* Now copy the rest of the arguments over. */
96 for (i = 1; i < argc; ++i)
97 args[5 + i] = argv[i];
98
99 /* Finally, try to start the app_process. */
100 execvp (args[0], args);
101
102 /* If exit fails, return an error indication. */
103 perror ("exec");
104 return 1;
30} 105}
diff --git a/src/android.c b/src/android.c
index c186b462360..8c4442e3397 100644
--- a/src/android.c
+++ b/src/android.c
@@ -145,6 +145,10 @@ char *android_game_path;
145/* The directory used to store temporary files. */ 145/* The directory used to store temporary files. */
146char *android_cache_dir; 146char *android_cache_dir;
147 147
148/* The list of archive files within which the Java virtual macine
149 looks for class files. */
150char *android_class_path;
151
148/* The display's pixel densities. */ 152/* The display's pixel densities. */
149double android_pixel_density_x, android_pixel_density_y; 153double android_pixel_density_x, android_pixel_density_y;
150 154
@@ -1315,6 +1319,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
1315 jobject cache_dir, 1319 jobject cache_dir,
1316 jfloat pixel_density_x, 1320 jfloat pixel_density_x,
1317 jfloat pixel_density_y, 1321 jfloat pixel_density_y,
1322 jobject class_path,
1318 jobject emacs_service_object) 1323 jobject emacs_service_object)
1319{ 1324{
1320 int pipefd[2]; 1325 int pipefd[2];
@@ -1372,19 +1377,30 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
1372 object from being deleted. */ 1377 object from being deleted. */
1373 (*env)->NewGlobalRef (env, local_asset_manager); 1378 (*env)->NewGlobalRef (env, local_asset_manager);
1374 1379
1375 /* Create a pipe and duplicate it to stdout and stderr. Next, make 1380 if (emacs_service_object)
1376 a thread that prints stderr to the system log. */ 1381 {
1382 /* Create a pipe and duplicate it to stdout and stderr. Next,
1383 make a thread that prints stderr to the system log.
1377 1384
1378 if (pipe2 (pipefd, O_CLOEXEC) < 0) 1385 Notice that this function is called in one of two ways. The
1379 emacs_abort (); 1386 first is when Emacs is being started as a GUI application by
1387 the system, and the second is when Emacs is being started by
1388 libandroid-emacs.so as an ordinary noninteractive Emacs.
1380 1389
1381 if (dup2 (pipefd[1], 2) < 0) 1390 In the second case, stderr is usually connected to a PTY, so
1382 emacs_abort (); 1391 this is unnecessary. */
1383 close (pipefd[1]);
1384 1392
1385 if (pthread_create (&thread, NULL, android_run_debug_thread, 1393 if (pipe2 (pipefd, O_CLOEXEC) < 0)
1386 (void *) (intptr_t) pipefd[0])) 1394 emacs_abort ();
1387 emacs_abort (); 1395
1396 if (dup2 (pipefd[1], 2) < 0)
1397 emacs_abort ();
1398 close (pipefd[1]);
1399
1400 if (pthread_create (&thread, NULL, android_run_debug_thread,
1401 (void *) (intptr_t) pipefd[0]))
1402 emacs_abort ();
1403 }
1388 1404
1389 /* Now set the path to the site load directory. */ 1405 /* Now set the path to the site load directory. */
1390 1406
@@ -1430,6 +1446,23 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
1430 (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir, 1446 (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir,
1431 java_string); 1447 java_string);
1432 1448
1449 if (class_path)
1450 {
1451 java_string = (*env)->GetStringUTFChars (env, (jstring) class_path,
1452 NULL);
1453
1454 if (!java_string)
1455 emacs_abort ();
1456
1457 android_class_path = strdup ((const char *) java_string);
1458
1459 if (!android_files_dir)
1460 emacs_abort ();
1461
1462 (*env)->ReleaseStringUTFChars (env, (jstring) class_path,
1463 java_string);
1464 }
1465
1433 /* Calculate the site-lisp path. */ 1466 /* Calculate the site-lisp path. */
1434 1467
1435 android_site_load_path = malloc (PATH_MAX + 1); 1468 android_site_load_path = malloc (PATH_MAX + 1);
@@ -1450,16 +1483,32 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
1450 "Site-lisp directory: %s\n" 1483 "Site-lisp directory: %s\n"
1451 "Files directory: %s\n" 1484 "Files directory: %s\n"
1452 "Native code directory: %s\n" 1485 "Native code directory: %s\n"
1453 "Game score path: %s\n", 1486 "Game score path: %s\n"
1487 "Class path: %s\n",
1454 android_site_load_path, 1488 android_site_load_path,
1455 android_files_dir, 1489 android_files_dir,
1456 android_lib_dir, android_game_path); 1490 android_lib_dir, android_game_path,
1491 (android_class_path
1492 ? android_class_path
1493 : "None"));
1494
1495 if (android_class_path)
1496 /* Set EMACS_CLASS_PATH to the class path where
1497 EmacsNoninteractive can be found. */
1498 setenv ("EMACS_CLASS_PATH", android_class_path, 1);
1499
1500 /* Set LD_LIBRARY_PATH to an appropriate value. */
1501 setenv ("LD_LIBRARY_PATH", android_lib_dir, 1);
1457 1502
1458 /* Make a reference to the Emacs service. */ 1503 /* Make a reference to the Emacs service. */
1459 emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
1460 1504
1461 if (!emacs_service) 1505 if (emacs_service_object)
1462 emacs_abort (); 1506 {
1507 emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
1508
1509 if (!emacs_service)
1510 emacs_abort ();
1511 }
1463 1512
1464 /* Set up events. */ 1513 /* Set up events. */
1465 android_init_events (); 1514 android_init_events ();
@@ -1660,12 +1709,14 @@ android_init_emacs_window (void)
1660} 1709}
1661 1710
1662extern JNIEXPORT void JNICALL 1711extern JNIEXPORT void JNICALL
1663NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) 1712NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
1713 jobject dump_file_object)
1664{ 1714{
1665 char **c_argv; 1715 char **c_argv;
1666 jsize nelements, i; 1716 jsize nelements, i;
1667 jobject argument; 1717 jobject argument;
1668 const char *c_argument; 1718 const char *c_argument;
1719 char *dump_file;
1669 1720
1670 android_java_env = env; 1721 android_java_env = env;
1671 1722
@@ -1705,9 +1756,34 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
1705 __android_log_print (ANDROID_LOG_WARN, __func__, 1756 __android_log_print (ANDROID_LOG_WARN, __func__,
1706 "chdir: %s", strerror (errno)); 1757 "chdir: %s", strerror (errno));
1707 1758
1708 /* Initialize the Android GUI. */ 1759 /* Initialize the Android GUI as long as the service object was
1709 android_init_gui = true; 1760 set. */
1710 android_emacs_init (nelements, c_argv); 1761
1762 if (emacs_service)
1763 android_init_gui = true;
1764
1765 /* Now see if a dump file has been specified and should be used. */
1766 dump_file = NULL;
1767
1768 if (dump_file_object)
1769 {
1770 c_argument
1771 = (*env)->GetStringUTFChars (env, (jstring) dump_file_object,
1772 NULL);
1773
1774 /* Copy the Java string data once. */
1775 dump_file = strdup (c_argument);
1776
1777 /* Release the Java string data. */
1778 (*env)->ReleaseStringUTFChars (env, (jstring) dump_file_object,
1779 c_argument);
1780 }
1781
1782 /* Delete local references to objects that are no longer needed. */
1783 ANDROID_DELETE_LOCAL_REF (argv);
1784 ANDROID_DELETE_LOCAL_REF (dump_file_object);
1785
1786 android_emacs_init (nelements, c_argv, dump_file);
1711 /* android_emacs_init should never return. */ 1787 /* android_emacs_init should never return. */
1712 emacs_abort (); 1788 emacs_abort ();
1713} 1789}
diff --git a/src/android.h b/src/android.h
index ce0ccfc4338..6c20995e4a1 100644
--- a/src/android.h
+++ b/src/android.h
@@ -38,15 +38,11 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
38#include "lisp.h" 38#include "lisp.h"
39#endif 39#endif
40 40
41/* This must be used in every symbol declaration to export it to the 41extern bool android_init_gui;
42 JNI Emacs wrapper. */
43#define ANDROID_EXPORT __attribute__ ((visibility ("default")))
44
45extern bool ANDROID_EXPORT android_init_gui;
46extern int ANDROID_EXPORT android_emacs_init (int, char **);
47 42
48#ifndef ANDROID_STUBIFY 43#ifndef ANDROID_STUBIFY
49 44
45extern int android_emacs_init (int, char **, char *);
50extern int android_select (int, fd_set *, fd_set *, fd_set *, 46extern int android_select (int, fd_set *, fd_set *, fd_set *,
51 struct timespec *); 47 struct timespec *);
52 48
diff --git a/src/androidfont.c b/src/androidfont.c
index bec5a59ca77..55f45758352 100644
--- a/src/androidfont.c
+++ b/src/androidfont.c
@@ -1104,6 +1104,9 @@ syms_of_androidfont (void)
1104void 1104void
1105init_androidfont (void) 1105init_androidfont (void)
1106{ 1106{
1107 if (!android_init_gui)
1108 return;
1109
1107 android_init_font_driver (); 1110 android_init_font_driver ();
1108 android_init_font_spec (); 1111 android_init_font_spec ();
1109 android_init_font_metrics (); 1112 android_init_font_metrics ();
diff --git a/src/androidselect.c b/src/androidselect.c
index 2e2f2d7e483..4585d64b7e8 100644
--- a/src/androidselect.c
+++ b/src/androidselect.c
@@ -240,6 +240,9 @@ init_androidselect (void)
240 jobject tem; 240 jobject tem;
241 jmethodID make_clipboard; 241 jmethodID make_clipboard;
242 242
243 if (!android_init_gui)
244 return;
245
243 android_init_emacs_clipboard (); 246 android_init_emacs_clipboard ();
244 247
245 make_clipboard = clipboard_class.make_clipboard; 248 make_clipboard = clipboard_class.make_clipboard;
diff --git a/src/emacs.c b/src/emacs.c
index b0c19fe0070..a24f9960494 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -875,19 +875,23 @@ dump_error_to_string (int result)
875 } 875 }
876} 876}
877 877
878/* This function returns the Emacs executable. */ 878/* This function returns the Emacs executable. DUMP_FILE is ignored
879 outside of Android. Otherwise, it is the name of the dump file to
880 use, or NULL if Emacs should look for a ``--dump-file'' argument
881 instead. */
882
879static char * 883static char *
880load_pdump (int argc, char **argv) 884load_pdump (int argc, char **argv, char *dump_file)
881{ 885{
882#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY 886#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
883 char *dump_file = NULL;
884 int skip_args = 0, result; 887 int skip_args = 0, result;
885 888
886 while (skip_args < argc - 1) 889 while (skip_args < argc - 1)
887 { 890 {
888 if (argmatch (argv, argc, "-dump-file", "--dump-file", 6, 891 if (argmatch (argv, argc, "-dump-file", "--dump-file",
889 &dump_file, &skip_args) 892 6, &dump_file, &skip_args)
890 || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args)) 893 || argmatch (argv, argc, "--", NULL, 2, NULL,
894 &skip_args))
891 break; 895 break;
892 skip_args++; 896 skip_args++;
893 } 897 }
@@ -933,7 +937,7 @@ load_pdump (int argc, char **argv)
933 937
934 /* Look for an explicitly-specified dump file. */ 938 /* Look for an explicitly-specified dump file. */
935 const char *path_exec = PATH_EXEC; 939 const char *path_exec = PATH_EXEC;
936 char *dump_file = NULL; 940 dump_file = NULL;
937 int skip_args = 0; 941 int skip_args = 0;
938 while (skip_args < argc - 1) 942 while (skip_args < argc - 1)
939 { 943 {
@@ -1276,7 +1280,7 @@ maybe_load_seccomp (int argc, char **argv)
1276 1280
1277#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY 1281#if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
1278int 1282int
1279android_emacs_init (int argc, char **argv) 1283android_emacs_init (int argc, char **argv, char *dump_file)
1280#else 1284#else
1281int 1285int
1282main (int argc, char **argv) 1286main (int argc, char **argv)
@@ -1286,6 +1290,12 @@ main (int argc, char **argv)
1286 for pointers. */ 1290 for pointers. */
1287 void *stack_bottom_variable; 1291 void *stack_bottom_variable;
1288 int old_argc; 1292 int old_argc;
1293#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
1294 char *dump_file;
1295
1296 /* This is just a dummy argument used to avoid extra defines. */
1297 dump_file = NULL;
1298#endif
1289 1299
1290 /* First, check whether we should apply a seccomp filter. This 1300 /* First, check whether we should apply a seccomp filter. This
1291 should come at the very beginning to allow the filter to protect 1301 should come at the very beginning to allow the filter to protect
@@ -1415,7 +1425,7 @@ main (int argc, char **argv)
1415 1425
1416#ifdef HAVE_PDUMPER 1426#ifdef HAVE_PDUMPER
1417 if (attempt_load_pdump) 1427 if (attempt_load_pdump)
1418 initial_emacs_executable = load_pdump (argc, argv); 1428 initial_emacs_executable = load_pdump (argc, argv, dump_file);
1419#else 1429#else
1420 ptrdiff_t bufsize; 1430 ptrdiff_t bufsize;
1421 initial_emacs_executable = find_emacs_executable (argv[0], &bufsize); 1431 initial_emacs_executable = find_emacs_executable (argv[0], &bufsize);
diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c
index bc8f1b35b84..ab90a2e4ecd 100644
--- a/src/sfntfont-android.c
+++ b/src/sfntfont-android.c
@@ -729,6 +729,9 @@ syms_of_sfntfont_android_for_pdumper (void)
729void 729void
730init_sfntfont_android (void) 730init_sfntfont_android (void)
731{ 731{
732 if (!android_init_gui)
733 return;
734
732 /* Make sure to pick the right Sans Serif font depending on what 735 /* Make sure to pick the right Sans Serif font depending on what
733 version of Android the device is running. */ 736 version of Android the device is running. */
734#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL 737#if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL