diff options
| author | Po Lu | 2023-01-25 18:44:47 +0800 |
|---|---|---|
| committer | Po Lu | 2023-01-25 18:44:47 +0800 |
| commit | 0900bfbcc57c555909cb75c38eb0ed26fb6964ef (patch) | |
| tree | 9a2fa4328defab79f1cb3dcfac4f3c071bf0a633 | |
| parent | 6f9a2a8f29c7faf13d0d86001b140746efc455b5 (diff) | |
| download | emacs-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.texi | 18 | ||||
| -rw-r--r-- | java/README | 824 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsApplication.java | 39 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsNative.java | 16 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsNoninteractive.java | 164 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsService.java | 48 | ||||
| -rw-r--r-- | java/org/gnu/emacs/EmacsThread.java | 22 | ||||
| -rw-r--r-- | lisp/loadup.el | 33 | ||||
| -rw-r--r-- | lisp/shell.el | 7 | ||||
| -rw-r--r-- | src/Makefile.in | 2 | ||||
| -rw-r--r-- | src/android-emacs.c | 83 | ||||
| -rw-r--r-- | src/android.c | 114 | ||||
| -rw-r--r-- | src/android.h | 8 | ||||
| -rw-r--r-- | src/androidfont.c | 3 | ||||
| -rw-r--r-- | src/androidselect.c | 3 | ||||
| -rw-r--r-- | src/emacs.c | 28 | ||||
| -rw-r--r-- | src/sfntfont-android.c | 3 |
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 | |||
| 102 | Emacs. | 102 | Emacs. |
| 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 |
| 105 | preloaded Lisp files contained in that dump file, greatly improving | 105 | data contained in that dump file, greatly improving start up time. |
| 106 | start 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 |
| 109 | corrupted, Emacs can crash. If that happens, the dump file stored in | 108 | corrupted, 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 |
| 193 | instead when starting either of those programs in a subprocess. | 192 | instead 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 | 195 | supposed to be inaccessible to processes not directly created by |
| 197 | files 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 |
| 199 | applications. This makes it impossible to run Emacs in a subprocess | 197 | applications. Since required Lisp is found in the @file{/assets} |
| 200 | within itself. | 198 | directory, it would thus follow that it is not possible for Emacs to |
| 199 | start itself as a subprocess. A special binary named | ||
| 200 | @command{libandroid-emacs.so} is provided with Emacs, and does its | ||
| 201 | best to start Emacs, for the purpose of running Lisp in batch mode. | ||
| 202 | However, the approach it takes was devised by reading Android source | ||
| 203 | code, and is not sanctioned by the Android compatibility definition | ||
| 204 | documents, 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. | |||
| 10 | Please keep the Java code indented with tabs and formatted according | 10 | Please keep the Java code indented with tabs and formatted according |
| 11 | to the rules for C code in the GNU coding standards. Always use | 11 | to the rules for C code in the GNU coding standards. Always use |
| 12 | C-style comments. | 12 | C-style comments. |
| 13 | |||
| 14 | ====================================================================== | ||
| 15 | |||
| 16 | OVERVIEW OF JAVA | ||
| 17 | |||
| 18 | Emacs developers do not know Java, and there is no reason they should | ||
| 19 | have to. Thus, the code in this directory is confined to what is | ||
| 20 | strictly necessary to support Emacs, and only uses a subset of Java | ||
| 21 | written in a way that is easily understandable to C programmers. | ||
| 22 | |||
| 23 | Java is required because the entire Android runtime is based around | ||
| 24 | Java, and there is no way to write an Android program which runs | ||
| 25 | without Java. | ||
| 26 | |||
| 27 | This text exists to prime other Emacs developers, already familar with | ||
| 28 | C, on the basic architecture of the Android port, and to teach them | ||
| 29 | how to read and write the Java code found in this directory. | ||
| 30 | |||
| 31 | Java is an object oriented language with automatic memory management | ||
| 32 | compiled down to bytecode, which is then subject to interpretation by | ||
| 33 | a Java virtual machine. | ||
| 34 | |||
| 35 | What that means, is that: | ||
| 36 | |||
| 37 | struct emacs_window | ||
| 38 | { | ||
| 39 | int some_fields; | ||
| 40 | int of_emacs_window; | ||
| 41 | }; | ||
| 42 | |||
| 43 | static void | ||
| 44 | do_something_with_emacs_window (struct emacs_window *a, int n) | ||
| 45 | { | ||
| 46 | a->some_fields = a->of_emacs_window + n; | ||
| 47 | } | ||
| 48 | |||
| 49 | would be written: | ||
| 50 | |||
| 51 | public 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 | |||
| 63 | and instead of doing: | ||
| 64 | |||
| 65 | do_something_with_emacs_window (my_window, 1); | ||
| 66 | |||
| 67 | you say: | ||
| 68 | |||
| 69 | myWindow.doSomething (1); | ||
| 70 | |||
| 71 | In addition to functions associated with an object of a given class | ||
| 72 | (such as EmacsWindow), Java also has two other kinds of functions. | ||
| 73 | |||
| 74 | The first are so-called ``static'' functions (the static means | ||
| 75 | something entirely different from what it does in C.) | ||
| 76 | |||
| 77 | A static function, while still having to be defined within a class, | ||
| 78 | can be called without any object. Instead of the object, you write | ||
| 79 | the name of the Java class within which it is defined. For example, | ||
| 80 | the following C code: | ||
| 81 | |||
| 82 | int | ||
| 83 | multiply_a_with_b_and_then_add_c (int a, int b, int c) | ||
| 84 | { | ||
| 85 | return a * b + c; | ||
| 86 | } | ||
| 87 | |||
| 88 | would be: | ||
| 89 | |||
| 90 | public class EmacsSomething | ||
| 91 | { | ||
| 92 | public static int | ||
| 93 | multiplyAWithBAndThenAddC (int a, int b, int c) | ||
| 94 | { | ||
| 95 | return a * b + c; | ||
| 96 | } | ||
| 97 | }; | ||
| 98 | |||
| 99 | Then, instead of calling: | ||
| 100 | |||
| 101 | int foo; | ||
| 102 | |||
| 103 | foo = multiply_a_with_b_then_add_c (1, 2, 3); | ||
| 104 | |||
| 105 | you say: | ||
| 106 | |||
| 107 | int foo; | ||
| 108 | |||
| 109 | foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3); | ||
| 110 | |||
| 111 | In Java, ``static'' does not mean that the function is only used | ||
| 112 | within its compilation unit! Instead, the ``private'' qualifier is | ||
| 113 | used to mean more or less the same thing: | ||
| 114 | |||
| 115 | static void | ||
| 116 | this_procedure_is_only_used_within_this_file (void) | ||
| 117 | { | ||
| 118 | do_something (); | ||
| 119 | } | ||
| 120 | |||
| 121 | becomes | ||
| 122 | |||
| 123 | public class EmacsSomething | ||
| 124 | { | ||
| 125 | private static void | ||
| 126 | thisProcedureIsOnlyUsedWithinThisClass () | ||
| 127 | { | ||
| 128 | |||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | the other kind are called ``constructors''. They are functions that | ||
| 133 | must be called to allocate memory to hold a class: | ||
| 134 | |||
| 135 | public class EmacsFoo | ||
| 136 | { | ||
| 137 | int bar; | ||
| 138 | |||
| 139 | public | ||
| 140 | EmacsFoo (int tokenA, int tokenB) | ||
| 141 | { | ||
| 142 | bar = tokenA + tokenB; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | now, the following statement: | ||
| 147 | |||
| 148 | EmacsFoo foo; | ||
| 149 | |||
| 150 | foo = new EmacsFoo (1, 2); | ||
| 151 | |||
| 152 | becomes more or less equivalent to the following C code: | ||
| 153 | |||
| 154 | struct emacs_foo | ||
| 155 | { | ||
| 156 | int bar; | ||
| 157 | }; | ||
| 158 | |||
| 159 | struct emacs_foo * | ||
| 160 | make_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 | |||
| 172 | struct emacs_foo *foo; | ||
| 173 | |||
| 174 | foo = make_emacs_foo (1, 2); | ||
| 175 | |||
| 176 | A class may have any number of constructors, or no constructors at | ||
| 177 | all, in which case the compiler inserts an empty constructor. | ||
| 178 | |||
| 179 | |||
| 180 | |||
| 181 | Sometimes, 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 | |||
| 193 | This is Java's version of GCC's nested function extension. The major | ||
| 194 | difference is that the nested function may still be called even after | ||
| 195 | it goes out of scope, and always retains a reference to the class and | ||
| 196 | local variables around where it was called. | ||
| 197 | |||
| 198 | Being an object-oriented language, Java also allows defining that a | ||
| 199 | class ``extends'' another class. The following C code: | ||
| 200 | |||
| 201 | struct a | ||
| 202 | { | ||
| 203 | long thirty_two; | ||
| 204 | }; | ||
| 205 | |||
| 206 | struct b | ||
| 207 | { | ||
| 208 | struct a a; | ||
| 209 | long long sixty_four; | ||
| 210 | }; | ||
| 211 | |||
| 212 | extern void do_something (struct a *); | ||
| 213 | |||
| 214 | void | ||
| 215 | my_function (struct b *b) | ||
| 216 | { | ||
| 217 | do_something (&b->a); | ||
| 218 | } | ||
| 219 | |||
| 220 | is roughly equivalent to the following Java code, split into two | ||
| 221 | files: | ||
| 222 | |||
| 223 | A.java | ||
| 224 | |||
| 225 | public class A | ||
| 226 | { | ||
| 227 | int thirtyTwo; | ||
| 228 | |||
| 229 | public void | ||
| 230 | doSomething () | ||
| 231 | { | ||
| 232 | etcEtcEtc (); | ||
| 233 | } | ||
| 234 | }; | ||
| 235 | |||
| 236 | B.java | ||
| 237 | |||
| 238 | public 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 | |||
| 249 | the Java runtime has transformed the call to ``b.doSomething'' to | ||
| 250 | ``((A) b).doSomething''. | ||
| 251 | |||
| 252 | However, Java also allows overriding this behavior, by specifying the | ||
| 253 | @Override keyword: | ||
| 254 | |||
| 255 | public 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 | |||
| 268 | now, any call to ``doSomething'' on a ``B'' created using ``new B ()'' | ||
| 269 | will end up calling ``Something.doSomethingTwo'', before calling back | ||
| 270 | to ``A.doSomething''. This override also applies in reverse; that is | ||
| 271 | to say, even if you write: | ||
| 272 | |||
| 273 | ((A) b).doSomething (); | ||
| 274 | |||
| 275 | B's version of doSomething will still be called, if ``b'' was created | ||
| 276 | using ``new B ()''. | ||
| 277 | |||
| 278 | This mechanism is used extensively throughout the Java language and | ||
| 279 | Android windowing APIs. | ||
| 280 | |||
| 281 | Elsewhere, you will encounter Java code that defines arrays: | ||
| 282 | |||
| 283 | public 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 | |||
| 295 | Java arrays are similar to C arrays in that they can not grow. But | ||
| 296 | they are very much unlike C arrays in that they are always references | ||
| 297 | (as opposed to decaying into pointers in various situations), and | ||
| 298 | contain information about their length. | ||
| 299 | |||
| 300 | If another function named ``frobinicate1'' takes an array as an | ||
| 301 | argument, then it need not take the length of the array. | ||
| 302 | |||
| 303 | Instead, it simply iterates over the array like so: | ||
| 304 | |||
| 305 | int i, k; | ||
| 306 | |||
| 307 | for (i = 0; i < array.length; ++i) | ||
| 308 | { | ||
| 309 | k = array[i]; | ||
| 310 | |||
| 311 | Whatever.doSomethingWithK (k); | ||
| 312 | } | ||
| 313 | |||
| 314 | The syntax used to define arrays is also slightly different. As | ||
| 315 | arrays are always references, there is no way for you to tell the | ||
| 316 | runtime to allocate an array of size N in a structure (class.) | ||
| 317 | |||
| 318 | Instead, if you need an array of that size, you must declare a field | ||
| 319 | with the type of the array, and allocate the array inside the class's | ||
| 320 | constructor, like so: | ||
| 321 | |||
| 322 | public class EmacsArrayContainer | ||
| 323 | { | ||
| 324 | public int[] myArray; | ||
| 325 | |||
| 326 | public | ||
| 327 | EmacsArrayContainer () | ||
| 328 | { | ||
| 329 | myArray = new array[10]; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | while in C, you could just have written: | ||
| 334 | |||
| 335 | struct emacs_array_container | ||
| 336 | { | ||
| 337 | int my_array[10]; | ||
| 338 | }; | ||
| 339 | |||
| 340 | or, possibly even better, | ||
| 341 | |||
| 342 | typedef int my_array[10]; | ||
| 343 | |||
| 344 | Alas, Java has no equivalent of `typedef'. | ||
| 345 | |||
| 346 | JAVA NATIVE INTERFACE | ||
| 347 | |||
| 348 | Java also provides an interface for C code to interface with Java. | ||
| 349 | |||
| 350 | C functions exported from a shared library become static Java | ||
| 351 | functions within a class, like so: | ||
| 352 | |||
| 353 | public 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 | |||
| 391 | Where the corresponding C functions are located in android.c, and | ||
| 392 | loaded by the special invocation: | ||
| 393 | |||
| 394 | static | ||
| 395 | { | ||
| 396 | System.loadLibrary ("emacs"); | ||
| 397 | }; | ||
| 398 | |||
| 399 | |||
| 400 | See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html | ||
| 401 | for more details. | ||
| 402 | |||
| 403 | |||
| 404 | |||
| 405 | OVERVIEW OF ANDROID | ||
| 406 | |||
| 407 | When the Android system starts an application, it does not actually | ||
| 408 | call the application's ``main'' function. It may not even start the | ||
| 409 | application's process if one is already running. | ||
| 410 | |||
| 411 | Instead, Android is organized around components. When the user opens | ||
| 412 | the ``Emacs'' icon, the Android system looks up and starts the | ||
| 413 | component associated with the ``Emacs'' icon. In this case, the | ||
| 414 | component is called an activity, and is declared in | ||
| 415 | the 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 | |||
| 429 | This tells Android to start the activity defined in ``EmacsActivity'' | ||
| 430 | (defined in org/gnu/emacs/EmacsActivity.java), a class extending the | ||
| 431 | Android class ``Activity''. | ||
| 432 | |||
| 433 | To do so, the Android system creates an instance of ``EmacsActivity'' | ||
| 434 | and the window system window associated with it, and eventually calls: | ||
| 435 | |||
| 436 | Activity activity; | ||
| 437 | |||
| 438 | activity.onCreate (...); | ||
| 439 | |||
| 440 | But which ``onCreate'' is really called? | ||
| 441 | It is actually the ``onCreate'' defined in EmacsActivity.java, as | ||
| 442 | it 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 | |||
| 451 | Then, this is what happens step-by-step within the ``onCreate'' | ||
| 452 | function: | ||
| 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 | |||
| 460 | Here, Emacs obtains the intent (a request to start a component) which | ||
| 461 | was used to start Emacs, and sets a special flag if it contains a | ||
| 462 | request 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 | |||
| 471 | Next, Emacs sets an appropriate theme for the activity's associated | ||
| 472 | window 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 | |||
| 484 | Then, Emacs creates a ``FrameLayout'', a widget that holds a single | ||
| 485 | other widget, and makes it the activity's ``content view''. | ||
| 486 | |||
| 487 | The activity itself is a ``FrameLayout'', so the ``layout parameters'' | ||
| 488 | here apply to the FrameLayout itself, and not its children. | ||
| 489 | |||
| 490 | /* Maybe start the Emacs service if necessary. */ | ||
| 491 | EmacsService.startEmacsService (this); | ||
| 492 | |||
| 493 | And after that, Emacs calls the static function ``startEmacsService'', | ||
| 494 | defined in the class ``EmacsService''. This starts the Emacs service | ||
| 495 | component if necessary. | ||
| 496 | |||
| 497 | /* Add this activity to the list of available activities. */ | ||
| 498 | EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this); | ||
| 499 | |||
| 500 | super.onCreate (savedInstanceState); | ||
| 501 | |||
| 502 | Finally, Emacs registers that this activity is now ready to receive | ||
| 503 | top-level frames (windows) created from Lisp. | ||
| 504 | |||
| 505 | Activities come and go, but Emacs has to stay running in the mean | ||
| 506 | time. Thus, Emacs also defines a ``service'', which is a long-running | ||
| 507 | component that the Android system allows to run in the background. | ||
| 508 | |||
| 509 | Let 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 | |||
| 528 | If ``EmacsService.SERVICE'' does not yet exist, what this does is to | ||
| 529 | tell the ``context'' (the equivalent of an Xlib Display *) to start a | ||
| 530 | service defined by the class ``EmacsService''. Eventually, this | ||
| 531 | results 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 | |||
| 543 | Here is what this function does, step-by-step: | ||
| 544 | |||
| 545 | SERVICE = this; | ||
| 546 | |||
| 547 | First, it sets the special static variable ``SERVICE'' to ``this'', | ||
| 548 | which is a pointer to the ``EmacsService' object that was created. | ||
| 549 | |||
| 550 | handler = new Handler (Looper.getMainLooper ()); | ||
| 551 | |||
| 552 | Next, it creates a ``Handler'' object for the ``main looper''. | ||
| 553 | This is a helper structure which allows executing code on the Android | ||
| 554 | user interface thread. | ||
| 555 | |||
| 556 | manager = getAssets (); | ||
| 557 | app_context = getApplicationContext (); | ||
| 558 | metrics = getResources ().getDisplayMetrics (); | ||
| 559 | pixelDensityX = metrics.xdpi; | ||
| 560 | pixelDensityY = metrics.ydpi; | ||
| 561 | |||
| 562 | Finally, 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 | |||
| 573 | Then, 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 | |||
| 583 | It obtains the names of the Emacs home, shared library, and temporary | ||
| 584 | file 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 | |||
| 591 | The name of the Emacs application package. | ||
| 592 | |||
| 593 | Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir | ||
| 594 | + ", libDir = " + libDir + ", and classPath = " + classPath); | ||
| 595 | |||
| 596 | Prints a debug message to the Android system log with this | ||
| 597 | information. | ||
| 598 | |||
| 599 | EmacsNative.setEmacsParams (manager, filesDir, libDir, | ||
| 600 | cacheDir, (float) pixelDensityX, | ||
| 601 | (float) pixelDensityY, | ||
| 602 | classPath, this); | ||
| 603 | |||
| 604 | And calls the native function ``setEmacsParams'' (defined in | ||
| 605 | android.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 | |||
| 611 | Then, it allocates an ``EmacsThread'' object, and starts that thread. | ||
| 612 | Inside that thread is where Emacs's C code runs. | ||
| 613 | |||
| 614 | } | ||
| 615 | catch (IOException exception) | ||
| 616 | { | ||
| 617 | EmacsNative.emacsAbort (); | ||
| 618 | return; | ||
| 619 | |||
| 620 | And here is the purpose of the ``try'' block. Functions related to | ||
| 621 | file names in Java will signal errors of various types upon failure. | ||
| 622 | |||
| 623 | This ``catch'' block means that the Java virtual machine will abort | ||
| 624 | execution of the contents of the ``try'' block as soon as an error of | ||
| 625 | type ``IOException'' is encountered, and begin executing the contents | ||
| 626 | of the ``catch'' block. | ||
| 627 | |||
| 628 | Any failure of that type here is a crash, and | ||
| 629 | ``EmacsNative.emacsAbort'' is called to quickly abort the process to | ||
| 630 | get a useful backtrace. | ||
| 631 | } | ||
| 632 | } | ||
| 633 | |||
| 634 | Now, let us look at the definition of the class ``EmacsThread'', found | ||
| 635 | in org/gnu/emacs/EmacsThread.java: | ||
| 636 | |||
| 637 | public 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 | |||
| 665 | The class itself defines a single field, ``startDashQ'', a constructor | ||
| 666 | with an unused argument of the type ``EmacsService'' (which is useful | ||
| 667 | while debugging) and a flag ``startDashQ'', and a single function | ||
| 668 | ``run'', overriding the same function in the class ``Thread''. | ||
| 669 | |||
| 670 | When ``thread.start'' is called, the Java virtual machine creates a | ||
| 671 | new thread, and then calls the function ``run'' within that thread. | ||
| 672 | |||
| 673 | This function then computes a suitable argument vector, and calls | ||
| 674 | ``EmacsNative.initEmacs'' (defined in android.c), which then calls a | ||
| 675 | modified version of the regular Emacs ``main'' function. | ||
| 676 | |||
| 677 | At that point, Emacs initialization proceeds as usual: | ||
| 678 | Vinitial_window_system is set, loadup.el calls `normal-top-level', | ||
| 679 | which calls `command-line', and finally | ||
| 680 | `window-system-initialization', which initializes the `android' | ||
| 681 | terminal interface as usual. | ||
| 682 | |||
| 683 | What happens here is the same as on other platforms. Now, here is | ||
| 684 | what happens when the initial frame is created: Fx_create_frame calls | ||
| 685 | `android_create_frame_window' to create a top level window: | ||
| 686 | |||
| 687 | static void | ||
| 688 | android_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 | |||
| 707 | This calls the function `android_create_window' with some arguments | ||
| 708 | whose meanings are identical to the arguments to `XCreateWindow'. | ||
| 709 | |||
| 710 | Here is the definition of `android_create_window', in android.c: | ||
| 711 | |||
| 712 | android_window | ||
| 713 | android_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 | |||
| 725 | What does it do? First, some context: | ||
| 726 | |||
| 727 | At any time, there can be at most 65535 Java objects referred to by | ||
| 728 | the rest of Emacs through the Java native interface. Each such object | ||
| 729 | is assigned a ``handle'' (similar to an XID on X) and given a unique | ||
| 730 | type. The function `android_resolve_handle' returns the JNI `jobject' | ||
| 731 | associated with a given handle. | ||
| 732 | |||
| 733 | parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW); | ||
| 734 | |||
| 735 | Here, 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 | |||
| 741 | Next, `max_handle' is saved, and a new handle is allocated for | ||
| 742 | `window'. | ||
| 743 | |||
| 744 | if (!window) | ||
| 745 | error ("Out of window handles!"); | ||
| 746 | |||
| 747 | An 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 | |||
| 755 | Then, if this initialization has not yet been completed, Emacs | ||
| 756 | proceeds 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 | |||
| 764 | And it tries to look up the constructor, which should take seven | ||
| 765 | arguments: | ||
| 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 | |||
| 780 | Next, it saves a global reference to the class and deletes the local | ||
| 781 | reference. Global references will never be deallocated by the Java | ||
| 782 | virtual 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 | |||
| 800 | Then, it creates an instance of the ``EmacsWindow'' class with the | ||
| 801 | appropriate 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 | |||
| 810 | If creating the object fails, Emacs clears the ``pending exception'' | ||
| 811 | and 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 | |||
| 821 | Otherwise, it associates a new global reference to the object with the | ||
| 822 | handle, and deletes the local reference returned from the JNI | ||
| 823 | NewObject function. | ||
| 824 | |||
| 825 | if (!android_handles[window].handle) | ||
| 826 | memory_full (0); | ||
| 827 | |||
| 828 | If allocating the global reference fails, Emacs signals that it is out | ||
| 829 | of memory. | ||
| 830 | |||
| 831 | android_change_window_attributes (window, value_mask, attrs); | ||
| 832 | return window; | ||
| 833 | |||
| 834 | Otherwise, it applies the specified window attributes and returns the | ||
| 835 | handle 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; | |||
| 22 | import java.io.File; | 22 | import java.io.File; |
| 23 | import java.io.FileFilter; | 23 | import java.io.FileFilter; |
| 24 | 24 | ||
| 25 | import android.content.Context; | ||
| 26 | |||
| 25 | import android.app.Application; | 27 | import android.app.Application; |
| 26 | import android.util.Log; | 28 | import android.util.Log; |
| 27 | 29 | ||
| 28 | public class EmacsApplication extends Application implements FileFilter | 30 | public 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 | |||
| 3 | Copyright (C) 2023 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | it under the terms of the GNU General Public License as published by | ||
| 9 | the Free Software Foundation, either version 3 of the License, or (at | ||
| 10 | your option) any later version. | ||
| 11 | |||
| 12 | GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | GNU General Public License for more details. | ||
| 16 | |||
| 17 | You should have received a copy of the GNU General Public License | ||
| 18 | along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 19 | |||
| 20 | package org.gnu.emacs; | ||
| 21 | |||
| 22 | import android.os.Looper; | ||
| 23 | import android.os.Build; | ||
| 24 | |||
| 25 | import android.content.Context; | ||
| 26 | import android.content.res.AssetManager; | ||
| 27 | |||
| 28 | import java.lang.reflect.Constructor; | ||
| 29 | import 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") | ||
| 47 | public 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 | ||
| 42 | import android.content.Context; | 42 | import android.content.Context; |
| 43 | import android.content.Intent; | 43 | import android.content.Intent; |
| 44 | import android.content.pm.ApplicationInfo; | ||
| 45 | import android.content.pm.PackageManager.ApplicationInfoFlags; | ||
| 46 | import android.content.pm.PackageManager; | ||
| 44 | import android.content.res.AssetManager; | 47 | import android.content.res.AssetManager; |
| 45 | 48 | ||
| 46 | import android.net.Uri; | 49 | import 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 | ||
| 769 | android-emacs: libemacs.so android-emacs.o | 769 | android-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 |
| 772 | endif | 772 | endif |
| 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 | |||
| 18 | along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | 18 | along 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 | ||
| 26 | int | 35 | int |
| 27 | main (int argc, char **argv) | 36 | main (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. */ |
| 146 | char *android_cache_dir; | 146 | char *android_cache_dir; |
| 147 | 147 | ||
| 148 | /* The list of archive files within which the Java virtual macine | ||
| 149 | looks for class files. */ | ||
| 150 | char *android_class_path; | ||
| 151 | |||
| 148 | /* The display's pixel densities. */ | 152 | /* The display's pixel densities. */ |
| 149 | double android_pixel_density_x, android_pixel_density_y; | 153 | double 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 | ||
| 1662 | extern JNIEXPORT void JNICALL | 1711 | extern JNIEXPORT void JNICALL |
| 1663 | NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv) | 1712 | NATIVE_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 | 41 | extern bool android_init_gui; |
| 42 | JNI Emacs wrapper. */ | ||
| 43 | #define ANDROID_EXPORT __attribute__ ((visibility ("default"))) | ||
| 44 | |||
| 45 | extern bool ANDROID_EXPORT android_init_gui; | ||
| 46 | extern int ANDROID_EXPORT android_emacs_init (int, char **); | ||
| 47 | 42 | ||
| 48 | #ifndef ANDROID_STUBIFY | 43 | #ifndef ANDROID_STUBIFY |
| 49 | 44 | ||
| 45 | extern int android_emacs_init (int, char **, char *); | ||
| 50 | extern int android_select (int, fd_set *, fd_set *, fd_set *, | 46 | extern 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) | |||
| 1104 | void | 1104 | void |
| 1105 | init_androidfont (void) | 1105 | init_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 | |||
| 879 | static char * | 883 | static char * |
| 880 | load_pdump (int argc, char **argv) | 884 | load_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 |
| 1278 | int | 1282 | int |
| 1279 | android_emacs_init (int argc, char **argv) | 1283 | android_emacs_init (int argc, char **argv, char *dump_file) |
| 1280 | #else | 1284 | #else |
| 1281 | int | 1285 | int |
| 1282 | main (int argc, char **argv) | 1286 | main (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) | |||
| 729 | void | 729 | void |
| 730 | init_sfntfont_android (void) | 730 | init_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 |