emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android 0900bfbcc5 3/4: Update Android port


From: Po Lu
Subject: feature/android 0900bfbcc5 3/4: Update Android port
Date: Wed, 25 Jan 2023 05:45:25 -0500 (EST)

branch: feature/android
commit 0900bfbcc57c555909cb75c38eb0ed26fb6964ef
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    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.
---
 doc/emacs/android.texi                      |  18 +-
 java/README                                 | 824 ++++++++++++++++++++++++++++
 java/org/gnu/emacs/EmacsApplication.java    |  39 +-
 java/org/gnu/emacs/EmacsNative.java         |  16 +-
 java/org/gnu/emacs/EmacsNoninteractive.java | 164 ++++++
 java/org/gnu/emacs/EmacsService.java        |  48 +-
 java/org/gnu/emacs/EmacsThread.java         |  22 +-
 lisp/loadup.el                              |  33 +-
 lisp/shell.el                               |   7 +-
 src/Makefile.in                             |   2 +-
 src/android-emacs.c                         |  83 ++-
 src/android.c                               | 114 +++-
 src/android.h                               |   8 +-
 src/androidfont.c                           |   3 +
 src/androidselect.c                         |   3 +
 src/emacs.c                                 |  28 +-
 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 cda0eadf22..e910e482ad 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
 Emacs.
 
   The next time that same copy of Emacs starts up, it simply loads the
-preloaded Lisp files contained in that dump file, greatly improving
-start up time.
+data contained in that dump file, greatly improving start up time.
 
   However, if by some unforseen circumstance the dump file is
 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
 @code{libctags.so} or @code{libemacsclient.so} on the commnd line
 instead when starting either of those programs in a subprocess.
 
-@c TODO: remove this limitation.
-  In addition, the @file{/assets} directory containing Emacs start-up
-files is inaccessible to processes not directly created by
+  The @file{/assets} directory containing Emacs start-up files is
+supposed to be inaccessible to processes not directly created by
 @code{zygote}, the system service responsible for starting
-applications.  This makes it impossible to run Emacs in a subprocess
-within itself.
+applications.  Since required Lisp is found in the @file{/assets}
+directory, it would thus follow that it is not possible for Emacs to
+start itself as a subprocess.  A special binary named
+@command{libandroid-emacs.so} is provided with Emacs, and does its
+best to start Emacs, for the purpose of running Lisp in batch mode.
+However, the approach it takes was devised by reading Android source
+code, and is not sanctioned by the Android compatibility definition
+documents, so your mileage may vary.
 
 @section Running Emacs in the background
 @cindex emacs killed, android
diff --git a/java/README b/java/README
index 05edf7744d..44f5a41516 100644
--- a/java/README
+++ b/java/README
@@ -10,3 +10,827 @@ to install different builds of Emacs on top of each other.
 Please keep the Java code indented with tabs and formatted according
 to the rules for C code in the GNU coding standards.  Always use
 C-style comments.
+
+======================================================================
+
+OVERVIEW OF JAVA
+
+Emacs developers do not know Java, and there is no reason they should
+have to.  Thus, the code in this directory is confined to what is
+strictly necessary to support Emacs, and only uses a subset of Java
+written in a way that is easily understandable to C programmers.
+
+Java is required because the entire Android runtime is based around
+Java, and there is no way to write an Android program which runs
+without Java.
+
+This text exists to prime other Emacs developers, already familar with
+C, on the basic architecture of the Android port, and to teach them
+how to read and write the Java code found in this directory.
+
+Java is an object oriented language with automatic memory management
+compiled down to bytecode, which is then subject to interpretation by
+a Java virtual machine.
+
+What that means, is that:
+
+struct emacs_window
+{
+  int some_fields;
+  int of_emacs_window;
+};
+
+static void
+do_something_with_emacs_window (struct emacs_window *a, int n)
+{
+  a->some_fields = a->of_emacs_window + n;
+}
+
+would be written:
+
+public class EmacsWindow
+{
+  public int someFields;
+  public int ofEmacsWindow;
+
+  public void
+  doSomething (int n)
+  {
+    someFields = ofEmacsWindow + n;
+  }
+}
+
+and instead of doing:
+
+do_something_with_emacs_window (my_window, 1);
+
+you say:
+
+myWindow.doSomething (1);
+
+In addition to functions associated with an object of a given class
+(such as EmacsWindow), Java also has two other kinds of functions.
+
+The first are so-called ``static'' functions (the static means
+something entirely different from what it does in C.)
+
+A static function, while still having to be defined within a class,
+can be called without any object.  Instead of the object, you write
+the name of the Java class within which it is defined. For example,
+the following C code:
+
+int
+multiply_a_with_b_and_then_add_c (int a, int b, int c)
+{
+  return a * b + c;
+}
+
+would be:
+
+public class EmacsSomething
+{
+  public static int
+  multiplyAWithBAndThenAddC (int a, int b, int c)
+  {
+    return a * b + c;
+  }
+};
+
+Then, instead of calling:
+
+int foo;
+
+foo = multiply_a_with_b_then_add_c (1, 2, 3);
+
+you say:
+
+int foo;
+
+foo = EmacsSomething.multiplyAWithBAndThenAddC (1, 2, 3);
+
+In Java, ``static'' does not mean that the function is only used
+within its compilation unit!  Instead, the ``private'' qualifier is
+used to mean more or less the same thing:
+
+static void
+this_procedure_is_only_used_within_this_file (void)
+{
+  do_something ();
+}
+
+becomes
+
+public class EmacsSomething
+{
+  private static void
+  thisProcedureIsOnlyUsedWithinThisClass ()
+  {
+
+  }
+}
+
+the other kind are called ``constructors''.  They are functions that
+must be called to allocate memory to hold a class:
+
+public class EmacsFoo
+{
+  int bar;
+
+  public
+  EmacsFoo (int tokenA, int tokenB)
+  {
+    bar = tokenA + tokenB;
+  }
+}
+
+now, the following statement:
+
+EmacsFoo foo;
+
+foo = new EmacsFoo (1, 2);
+
+becomes more or less equivalent to the following C code:
+
+struct emacs_foo
+{
+  int bar;
+};
+
+struct emacs_foo *
+make_emacs_foo (int token_a, int token_b)
+{
+  struct emacs_foo *foo;
+
+  foo = xmalloc (sizeof *foo);
+  foo->bar = token_a + token_b;
+
+  return foo;
+}
+
+/* ... */
+
+struct emacs_foo *foo;
+
+foo = make_emacs_foo (1, 2);
+
+A class may have any number of constructors, or no constructors at
+all, in which case the compiler inserts an empty constructor.
+
+
+
+Sometimes, you will see Java code that looks like this:
+
+    allFiles = filesDirectory.listFiles (new FileFilter () {
+       @Override
+       public boolean
+       accept (File file)
+       {
+         return (!file.isDirectory ()
+                 && file.getName ().endsWith (".pdmp"));
+       }
+      });
+
+This is Java's version of GCC's nested function extension.  The major
+difference is that the nested function may still be called even after
+it goes out of scope, and always retains a reference to the class and
+local variables around where it was called.
+
+Being an object-oriented language, Java also allows defining that a
+class ``extends'' another class.  The following C code:
+
+struct a
+{
+  long thirty_two;
+};
+
+struct b
+{
+  struct a a;
+  long long sixty_four;
+};
+
+extern void do_something (struct a *);
+
+void
+my_function (struct b *b)
+{
+  do_something (&b->a);
+}
+
+is roughly equivalent to the following Java code, split into two
+files:
+
+  A.java
+
+public class A
+{
+  int thirtyTwo;
+
+  public void
+  doSomething ()
+  {
+    etcEtcEtc ();
+  }
+};
+
+  B.java
+
+public class B extends A
+{
+  long sixty_four;
+
+  public static void
+  myFunction (B b)
+  {
+    b.doSomething ();
+  }
+}
+
+the Java runtime has transformed the call to ``b.doSomething'' to
+``((A) b).doSomething''.
+
+However, Java also allows overriding this behavior, by specifying the
+@Override keyword:
+
+public class B extends A
+{
+  long sixty_four;
+
+  @Override
+  public void
+  doSomething ()
+  {
+    Something.doSomethingTwo ();
+    super.doSomething ();
+  }
+}
+
+now, any call to ``doSomething'' on a ``B'' created using ``new B ()''
+will end up calling ``Something.doSomethingTwo'', before calling back
+to ``A.doSomething''.  This override also applies in reverse; that is
+to say, even if you write:
+
+  ((A) b).doSomething ();
+
+B's version of doSomething will still be called, if ``b'' was created
+using ``new B ()''.
+
+This mechanism is used extensively throughout the Java language and
+Android windowing APIs.
+
+Elsewhere, you will encounter Java code that defines arrays:
+
+public class EmacsFrobinicator
+{
+  public static void
+  emacsFrobinicate (int something)
+  {
+    int[] primesFromSomething;
+
+    primesFromSomething = new int[numberOfPrimes];
+    /* ... */
+  }
+}
+
+Java arrays are similar to C arrays in that they can not grow. But
+they are very much unlike C arrays in that they are always references
+(as opposed to decaying into pointers in various situations), and
+contain information about their length.
+
+If another function named ``frobinicate1'' takes an array as an
+argument, then it need not take the length of the array.
+
+Instead, it simply iterates over the array like so:
+
+int i, k;
+
+for (i = 0; i < array.length; ++i)
+  {
+    k = array[i];
+
+    Whatever.doSomethingWithK (k);
+  }
+
+The syntax used to define arrays is also slightly different.  As
+arrays are always references, there is no way for you to tell the
+runtime to allocate an array of size N in a structure (class.)
+
+Instead, if you need an array of that size, you must declare a field
+with the type of the array, and allocate the array inside the class's
+constructor, like so:
+
+public class EmacsArrayContainer
+{
+  public int[] myArray;
+
+  public
+  EmacsArrayContainer ()
+  {
+    myArray = new array[10];
+  }
+}
+
+while in C, you could just have written:
+
+struct emacs_array_container
+{
+  int my_array[10];
+};
+
+or, possibly even better,
+
+typedef int my_array[10];
+
+Alas, Java has no equivalent of `typedef'.
+
+JAVA NATIVE INTERFACE
+
+Java also provides an interface for C code to interface with Java.
+
+C functions exported from a shared library become static Java
+functions within a class, like so:
+
+public class EmacsNative
+{
+  /* Obtain the fingerprint of this build of Emacs.  The fingerprint
+     can be used to determine the dump file name.  */
+  public static native String getFingerprint ();
+
+  /* Set certain parameters before initializing Emacs.
+
+     assetManager must be the asset manager associated with the
+     context that is loading Emacs.  It is saved and remains for the
+     remainder the lifetime of the Emacs process.
+
+     filesDir must be the package's data storage location for the
+     current Android user.
+
+     libDir must be the package's data storage location for native
+     libraries.         It is used as PATH.
+
+     cacheDir must be the package's cache directory.  It is used as
+     the `temporary-file-directory'.
+
+     pixelDensityX and pixelDensityY are the DPI values that will be
+     used by Emacs.
+
+     classPath must be the classpath of this app_process process, or
+     NULL.
+
+     emacsService must be the EmacsService singleton, or NULL. */
+  public static native void setEmacsParams (AssetManager assetManager,
+                                           String filesDir,
+                                           String libDir,
+                                           String cacheDir,
+                                           float pixelDensityX,
+                                           float pixelDensityY,
+                                           String classPath,
+                                           EmacsService emacsService);
+}
+
+Where the corresponding C functions are located in android.c, and
+loaded by the special invocation:
+
+  static
+  {
+    System.loadLibrary ("emacs");
+  };
+
+
+See http://docs.oracle.com/en/java/javase/19/docs/specs/jni/intro.html
+for more details.
+
+
+
+OVERVIEW OF ANDROID
+
+When the Android system starts an application, it does not actually
+call the application's ``main'' function.  It may not even start the
+application's process if one is already running.
+
+Instead, Android is organized around components.  When the user opens
+the ``Emacs'' icon, the Android system looks up and starts the
+component associated with the ``Emacs'' icon.  In this case, the
+component is called an activity, and is declared in
+the AndroidManifest.xml in this directory:
+
+    <activity android:name="org.gnu.emacs.EmacsActivity"
+             android:launchMode="singleTop"
+             android:windowSoftInputMode="adjustResize"
+             android:exported="true"
+             
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
+      <intent-filter>
+       <action android:name="android.intent.action.MAIN" />
+       <category android:name="android.intent.category.DEFAULT" />
+       <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+
+This tells Android to start the activity defined in ``EmacsActivity''
+(defined in org/gnu/emacs/EmacsActivity.java), a class extending the
+Android class ``Activity''.
+
+To do so, the Android system creates an instance of ``EmacsActivity''
+and the window system window associated with it, and eventually calls:
+
+  Activity activity;
+
+  activity.onCreate (...);
+
+But which ``onCreate'' is really called?
+It is actually the ``onCreate'' defined in EmacsActivity.java, as
+it overrides the ``onCreate'' defined in Android's own Activity class:
+
+  @Override
+  public void
+  onCreate (Bundle savedInstanceState)
+  {
+    FrameLayout.LayoutParams params;
+    Intent intent;
+
+Then, this is what happens step-by-step within the ``onCreate''
+function:
+
+    /* See if Emacs should be started with -Q. */
+    intent = getIntent ();
+    EmacsService.needDashQ
+      = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
+                               false);
+
+Here, Emacs obtains the intent (a request to start a component) which
+was used to start Emacs, and sets a special flag if it contains a
+request for Emacs to start with the ``-Q'' command-line argument.
+
+    /* Set the theme to one without a title bar.  */
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+      setTheme (android.R.style.Theme_DeviceDefault_NoActionBar);
+    else
+      setTheme (android.R.style.Theme_NoTitleBar);
+
+Next, Emacs sets an appropriate theme for the activity's associated
+window decorations.
+
+    params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT,
+                                          LayoutParams.MATCH_PARENT);
+
+    /* Make the frame layout.  */
+    layout = new FrameLayout (this);
+    layout.setLayoutParams (params);
+
+    /* Set it as the content view.  */
+    setContentView (layout);
+
+Then, Emacs creates a ``FrameLayout'', a widget that holds a single
+other widget, and makes it the activity's ``content view''.
+
+The activity itself is a ``FrameLayout'', so the ``layout parameters''
+here apply to the FrameLayout itself, and not its children.
+
+    /* Maybe start the Emacs service if necessary.  */
+    EmacsService.startEmacsService (this);
+
+And after that, Emacs calls the static function ``startEmacsService'',
+defined in the class ``EmacsService''. This starts the Emacs service
+component if necessary.
+
+    /* Add this activity to the list of available activities.  */
+    EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
+
+    super.onCreate (savedInstanceState);
+
+Finally, Emacs registers that this activity is now ready to receive
+top-level frames (windows) created from Lisp.
+
+Activities come and go, but Emacs has to stay running in the mean
+time.  Thus, Emacs also defines a ``service'', which is a long-running
+component that the Android system allows to run in the background.
+
+Let us go back and review the definition of ``startEmacsService'':
+
+  public static void
+  startEmacsService (Context context)
+  {
+    if (EmacsService.SERVICE == null)
+      {
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+         /* Start the Emacs service now.  */
+         context.startService (new Intent (context,
+                                           EmacsService.class));
+       else
+         /* Display the permanant notification and start Emacs as a
+            foreground service.  */
+         context.startForegroundService (new Intent (context,
+                                                     EmacsService.class));
+      }
+  }
+
+If ``EmacsService.SERVICE'' does not yet exist, what this does is to
+tell the ``context'' (the equivalent of an Xlib Display *) to start a
+service defined by the class ``EmacsService''. Eventually, this
+results in ``EmacsService.onCreate'' being called:
+
+  @Override
+  public void
+  onCreate ()
+  {
+    AssetManager manager;
+    Context app_context;
+    String filesDir, libDir, cacheDir, classPath;
+    double pixelDensityX;
+    double pixelDensityY;
+
+Here is what this function does, step-by-step:
+
+    SERVICE = this;
+
+First, it sets the special static variable ``SERVICE'' to ``this'',
+which is a pointer to the ``EmacsService' object that was created.
+
+    handler = new Handler (Looper.getMainLooper ());
+
+Next, it creates a ``Handler'' object for the ``main looper''.
+This is a helper structure which allows executing code on the Android
+user interface thread.
+
+    manager = getAssets ();
+    app_context = getApplicationContext ();
+    metrics = getResources ().getDisplayMetrics ();
+    pixelDensityX = metrics.xdpi;
+    pixelDensityY = metrics.ydpi;
+
+Finally, it obtains:
+
+  - the asset manager, which is used to retrieve assets packaged
+    into the Emacs application package.
+
+  - the application context, used to obtain application specific
+    information.
+
+  - the display metrics, and from them, the X and Y densities in dots
+    per inch.
+
+Then, inside a ``try'' block:
+
+    try
+      {
+       /* Configure Emacs with the asset manager and other necessary
+          parameters.  */
+       filesDir = app_context.getFilesDir ().getCanonicalPath ();
+       libDir = getLibraryDirectory ();
+       cacheDir = app_context.getCacheDir ().getCanonicalPath ();
+
+It obtains the names of the Emacs home, shared library, and temporary
+file directories.
+
+       /* Now provide this application's apk file, so a recursive
+          invocation of app_process (through android-emacs) can
+          find EmacsNoninteractive.  */
+       classPath = getApkFile ();
+
+The name of the Emacs application package.
+
+       Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+              + ", libDir = " + libDir + ", and classPath = " + classPath);
+
+Prints a debug message to the Android system log with this
+information.
+
+       EmacsNative.setEmacsParams (manager, filesDir, libDir,
+                                   cacheDir, (float) pixelDensityX,
+                                   (float) pixelDensityY,
+                                   classPath, this);
+
+And calls the native function ``setEmacsParams'' (defined in
+android.c) to configure Emacs with this information.
+
+       /* Start the thread that runs Emacs.  */
+       thread = new EmacsThread (this, needDashQ);
+       thread.start ();
+
+Then, it allocates an ``EmacsThread'' object, and starts that thread.
+Inside that thread is where Emacs's C code runs.
+
+      }
+    catch (IOException exception)
+      {
+       EmacsNative.emacsAbort ();
+       return;
+
+And here is the purpose of the ``try'' block.  Functions related to
+file names in Java will signal errors of various types upon failure.
+
+This ``catch'' block means that the Java virtual machine will abort
+execution of the contents of the ``try'' block as soon as an error of
+type ``IOException'' is encountered, and begin executing the contents
+of the ``catch'' block.
+
+Any failure of that type here is a crash, and
+``EmacsNative.emacsAbort'' is called to quickly abort the process to
+get a useful backtrace.
+      }
+  }
+
+Now, let us look at the definition of the class ``EmacsThread'', found
+in org/gnu/emacs/EmacsThread.java:
+
+public class EmacsThread extends Thread
+{
+  /* Whether or not Emacs should be started -Q.         */
+  private boolean startDashQ;
+
+  public
+  EmacsThread (EmacsService service, boolean startDashQ)
+  {
+    super ("Emacs main thread");
+    this.startDashQ = startDashQ;
+  }
+
+  @Override
+  public void
+  run ()
+  {
+    String args[];
+
+    if (!startDashQ)
+      args = new String[] { "libandroid-emacs.so", };
+    else
+      args = new String[] { "libandroid-emacs.so", "-Q", };
+
+    /* Run the native code now.         */
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+  }
+};
+
+The class itself defines a single field, ``startDashQ'', a constructor
+with an unused argument of the type ``EmacsService'' (which is useful
+while debugging) and a flag ``startDashQ'', and a single function
+``run'', overriding the same function in the class ``Thread''.
+
+When ``thread.start'' is called, the Java virtual machine creates a
+new thread, and then calls the function ``run'' within that thread.
+
+This function then computes a suitable argument vector, and calls
+``EmacsNative.initEmacs'' (defined in android.c), which then calls a
+modified version of the regular Emacs ``main'' function.
+
+At that point, Emacs initialization proceeds as usual:
+Vinitial_window_system is set, loadup.el calls `normal-top-level',
+which calls `command-line', and finally
+`window-system-initialization', which initializes the `android'
+terminal interface as usual.
+
+What happens here is the same as on other platforms.  Now, here is
+what happens when the initial frame is created: Fx_create_frame calls
+`android_create_frame_window' to create a top level window:
+
+static void
+android_create_frame_window (struct frame *f)
+{
+  struct android_set_window_attributes attributes;
+  enum android_window_value_mask attribute_mask;
+
+  attributes.background_pixel = FRAME_BACKGROUND_PIXEL (f);
+  attribute_mask = ANDROID_CW_BACK_PIXEL;
+
+  block_input ();
+  FRAME_ANDROID_WINDOW (f)
+    = android_create_window (FRAME_DISPLAY_INFO (f)->root_window,
+                            f->left_pos,
+                            f->top_pos,
+                            FRAME_PIXEL_WIDTH (f),
+                            FRAME_PIXEL_HEIGHT (f),
+                            attribute_mask, &attributes);
+  unblock_input ();
+}
+
+This calls the function `android_create_window' with some arguments
+whose meanings are identical to the arguments to `XCreateWindow'.
+
+Here is the definition of `android_create_window', in android.c:
+
+android_window
+android_create_window (android_window parent, int x, int y,
+                      int width, int height,
+                      enum android_window_value_mask value_mask,
+                      struct android_set_window_attributes *attrs)
+{
+  static jclass class;
+  static jmethodID constructor;
+  jobject object, parent_object, old;
+  android_window window;
+  android_handle prev_max_handle;
+  bool override_redirect;
+
+What does it do? First, some context:
+
+At any time, there can be at most 65535 Java objects referred to by
+the rest of Emacs through the Java native interface.  Each such object
+is assigned a ``handle'' (similar to an XID on X) and given a unique
+type.  The function `android_resolve_handle' returns the JNI `jobject'
+associated with a given handle.
+
+  parent_object = android_resolve_handle (parent, ANDROID_HANDLE_WINDOW);
+
+Here, it is being used to look up the `jobject' associated with the
+`parent' handle.
+
+  prev_max_handle = max_handle;
+  window = android_alloc_id ();
+
+Next, `max_handle' is saved, and a new handle is allocated for
+`window'.
+
+  if (!window)
+    error ("Out of window handles!");
+
+An error is signalled if Emacs runs out of available handles.
+
+  if (!class)
+    {
+      class = (*android_java_env)->FindClass (android_java_env,
+                                             "org/gnu/emacs/EmacsWindow");
+      assert (class != NULL);
+
+Then, if this initialization has not yet been completed, Emacs
+proceeds to find the Java class named ``EmacsWindow''.
+
+      constructor
+       = (*android_java_env)->GetMethodID (android_java_env, class, "<init>",
+                                           "(SLorg/gnu/emacs/EmacsWindow;"
+                                           "IIIIZ)V");
+      assert (constructor != NULL);
+
+And it tries to look up the constructor, which should take seven
+arguments:
+
+  S                                    - a short.  (the handle ID)
+  Lorg/gnu/Emacs/EmacsWindow;          - an instance of the EmacsWindow
+                                         class.  (the parent)
+  IIII                                 - four ints.  (the window geometry.)
+  Z                                    - a boolean.  (whether or not the
+                                         window is override-redirect; see
+                                         XChangeWindowAttributes.)
+
+      old = class;
+      class = (*android_java_env)->NewGlobalRef (android_java_env, class);
+      (*android_java_env)->ExceptionClear (android_java_env);
+      ANDROID_DELETE_LOCAL_REF (old);
+
+Next, it saves a global reference to the class and deletes the local
+reference.  Global references will never be deallocated by the Java
+virtual machine as long as they still exist.
+
+      if (!class)
+       memory_full (0);
+    }
+
+  /* N.B. that ANDROID_CW_OVERRIDE_REDIRECT can only be set at window
+     creation time.  */
+  override_redirect = ((value_mask
+                       & ANDROID_CW_OVERRIDE_REDIRECT)
+                      && attrs->override_redirect);
+
+  object = (*android_java_env)->NewObject (android_java_env, class,
+                                          constructor, (jshort) window,
+                                          parent_object, (jint) x, (jint) y,
+                                          (jint) width, (jint) height,
+                                          (jboolean) override_redirect);
+
+Then, it creates an instance of the ``EmacsWindow'' class with the
+appropriate arguments and previously determined constructor.
+
+  if (!object)
+    {
+      (*android_java_env)->ExceptionClear (android_java_env);
+
+      max_handle = prev_max_handle;
+      memory_full (0);
+
+If creating the object fails, Emacs clears the ``pending exception''
+and signals that it is out of memory.
+    }
+
+  android_handles[window].type = ANDROID_HANDLE_WINDOW;
+  android_handles[window].handle
+    = (*android_java_env)->NewGlobalRef (android_java_env,
+                                        object);
+  (*android_java_env)->ExceptionClear (android_java_env);
+  ANDROID_DELETE_LOCAL_REF (object);
+
+Otherwise, it associates a new global reference to the object with the
+handle, and deletes the local reference returned from the JNI
+NewObject function.
+
+  if (!android_handles[window].handle)
+    memory_full (0);
+
+If allocating the global reference fails, Emacs signals that it is out
+of memory.
+
+  android_change_window_attributes (window, value_mask, attrs);
+  return window;
+
+Otherwise, it applies the specified window attributes and returns the
+handle of the new window.
+}
diff --git a/java/org/gnu/emacs/EmacsApplication.java 
b/java/org/gnu/emacs/EmacsApplication.java
index 87085c32d6..96328b99d1 100644
--- a/java/org/gnu/emacs/EmacsApplication.java
+++ b/java/org/gnu/emacs/EmacsApplication.java
@@ -22,27 +22,20 @@ package org.gnu.emacs;
 import java.io.File;
 import java.io.FileFilter;
 
+import android.content.Context;
+
 import android.app.Application;
 import android.util.Log;
 
-public class EmacsApplication extends Application implements FileFilter
+public class EmacsApplication extends Application
 {
   private static final String TAG = "EmacsApplication";
 
   /* The name of the dump file to use.  */
   public static String dumpFileName;
 
-  @Override
-  public boolean
-  accept (File file)
-  {
-    return (!file.isDirectory ()
-           && file.getName ().endsWith (".pdmp"));
-  }
-
-  @Override
-  public void
-  onCreate ()
+  public static void
+  findDumpFile (Context context)
   {
     File filesDirectory;
     File[] allFiles;
@@ -52,13 +45,19 @@ public class EmacsApplication extends Application 
implements FileFilter
     wantedDumpFile = ("emacs-" + EmacsNative.getFingerprint ()
                      + ".pdmp");
 
-    Log.d (TAG, "onCreate: looking for " + wantedDumpFile);
-
     /* Obtain a list of all files ending with ``.pdmp''.  Then, look
        for a file named ``emacs-<fingerprint>.pdmp'' and delete the
        rest.  */
-    filesDirectory = getFilesDir ();
-    allFiles = filesDirectory.listFiles (this);
+    filesDirectory = context.getFilesDir ();
+    allFiles = filesDirectory.listFiles (new FileFilter () {
+       @Override
+       public boolean
+       accept (File file)
+       {
+         return (!file.isDirectory ()
+                 && file.getName ().endsWith (".pdmp"));
+       }
+      });
 
     /* Now try to find the right dump file.  */
     for (i = 0; i < allFiles.length; ++i)
@@ -69,9 +68,13 @@ public class EmacsApplication extends Application implements 
FileFilter
          /* Delete this outdated dump file.  */
          allFiles[i].delete ();
       }
+  }
 
-    Log.d (TAG, "onCreate: found " + dumpFileName);
-
+  @Override
+  public void
+  onCreate ()
+  {
+    findDumpFile (this);
     super.onCreate ();
   }
 };
diff --git a/java/org/gnu/emacs/EmacsNative.java 
b/java/org/gnu/emacs/EmacsNative.java
index 9636561a52..a772b96530 100644
--- a/java/org/gnu/emacs/EmacsNative.java
+++ b/java/org/gnu/emacs/EmacsNative.java
@@ -29,8 +29,7 @@ public class EmacsNative
      can be used to determine the dump file name.  */
   public static native String getFingerprint ();
 
-  /* Set certain parameters before initializing Emacs.  This proves
-     that libemacs.so is being loaded from Java code.
+  /* Set certain parameters before initializing Emacs.
 
      assetManager must be the asset manager associated with the
      context that is loading Emacs.  It is saved and remains for the
@@ -48,19 +47,26 @@ public class EmacsNative
      pixelDensityX and pixelDensityY are the DPI values that will be
      used by Emacs.
 
-     emacsService must be the emacsService singleton.  */
+     classPath must be the classpath of this app_process process, or
+     NULL.
+
+     emacsService must be the EmacsService singleton, or NULL.  */
   public static native void setEmacsParams (AssetManager assetManager,
                                            String filesDir,
                                            String libDir,
                                            String cacheDir,
                                            float pixelDensityX,
                                            float pixelDensityY,
+                                           String classPath,
                                            EmacsService emacsService);
 
   /* Initialize Emacs with the argument array ARGV.  Each argument
      must contain a NULL terminated string, or else the behavior is
-     undefined.  */
-  public static native void initEmacs (String argv[]);
+     undefined.
+
+     DUMPFILE is the dump file to use, or NULL if Emacs is to load
+     loadup.el itself.  */
+  public static native void initEmacs (String argv[], String dumpFile);
 
   /* Abort and generate a native core dump.  */
   public static native void emacsAbort ();
diff --git a/java/org/gnu/emacs/EmacsNoninteractive.java 
b/java/org/gnu/emacs/EmacsNoninteractive.java
new file mode 100644
index 0000000000..a3aefee5e0
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsNoninteractive.java
@@ -0,0 +1,164 @@
+/* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
+
+Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+package org.gnu.emacs;
+
+import android.os.Looper;
+import android.os.Build;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/* Noninteractive Emacs.
+
+   This is the class that libandroid-emacs.so starts.
+   libandroid-emacs.so figures out the system classpath, then starts
+   dalvikvm with the framework jars.
+
+   At that point, dalvikvm calls main, which sets up the main looper,
+   creates an ActivityThread and attaches it to the main thread.
+
+   Then, it obtains an application context for the LoadedApk in the
+   application thread.
+
+   Finally, it obtains the necessary context specific objects and
+   initializes Emacs.  */
+
+@SuppressWarnings ("unchecked")
+public class EmacsNoninteractive
+{
+  private static String
+  getLibraryDirectory (Context context)
+  {
+    int apiLevel;
+
+    apiLevel = Build.VERSION.SDK_INT;
+
+    if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
+      return context.getApplicationInfo().nativeLibraryDir;
+    else if (apiLevel >= Build.VERSION_CODES.DONUT)
+      return context.getApplicationInfo().dataDir + "/lib";
+
+    return "/data/data/" + context.getPackageName() + "/lib";
+  }
+
+  public static void
+  main (String[] args)
+  {
+    Object activityThread, loadedApk;
+    Class activityThreadClass, loadedApkClass, contextImplClass;
+    Class compatibilityInfoClass;
+    Method method;
+    Context context;
+    AssetManager assets;
+    String filesDir, libDir, cacheDir;
+
+    Looper.prepare ();
+    context = null;
+    assets = null;
+    filesDir = libDir = cacheDir = null;
+
+    try
+      {
+       /* Get the activity thread.  */
+       activityThreadClass = Class.forName ("android.app.ActivityThread");
+
+       /* Get the systemMain method.  */
+       method = activityThreadClass.getMethod ("systemMain");
+
+       /* Create and attach the activity thread.  */
+       activityThread = method.invoke (null);
+
+       /* Now get an LoadedApk.  */
+       loadedApkClass = Class.forName ("android.app.LoadedApk");
+
+       /* Get a LoadedApk.  How to do this varies by Android version.
+          On Android 2.3.3 and earlier, there is no
+          ``compatibilityInfo'' argument to getPackageInfo.  */
+
+       if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD)
+         {
+           method
+             = activityThreadClass.getMethod ("getPackageInfo",
+                                              String.class,
+                                              int.class);
+           loadedApk = method.invoke (activityThread, "org.gnu.emacs",
+                                      0);
+         }
+       else
+         {
+           compatibilityInfoClass
+             = Class.forName ("android.content.res.CompatibilityInfo");
+
+           method
+             = activityThreadClass.getMethod ("getPackageInfo",
+                                              String.class,
+                                              compatibilityInfoClass,
+                                              int.class);
+           loadedApk = method.invoke (activityThread, "org.gnu.emacs", null,
+                                      0);
+         }
+
+       if (loadedApk == null)
+         throw new RuntimeException ("getPackageInfo returned NULL");
+
+       /* Now, get a context.  */
+       contextImplClass = Class.forName ("android.app.ContextImpl");
+       method = contextImplClass.getDeclaredMethod ("createAppContext",
+                                                    activityThreadClass,
+                                                    loadedApkClass);
+       method.setAccessible (true);
+       context = (Context) method.invoke (null, activityThread, loadedApk);
+
+       /* Don't actually start the looper or anything.  Instead, obtain
+          an AssetManager.  */
+       assets = context.getAssets ();
+
+       /* Now configure Emacs.  The class path should already be set.  */
+
+       filesDir = context.getFilesDir ().getCanonicalPath ();
+       libDir = getLibraryDirectory (context);
+       cacheDir = context.getCacheDir ().getCanonicalPath ();
+      }
+    catch (Exception e)
+      {
+       System.err.println ("Internal error: " + e);
+       System.err.println ("This means that the Android platform changed,");
+       System.err.println ("and that Emacs needs adjustments in order to");
+       System.err.println ("obtain required system internal resources.");
+       System.err.println ("Please report this bug to bug-gnu-emacs@gnu.org.");
+
+       System.exit (1);
+      }
+
+    EmacsNative.setEmacsParams (assets, filesDir,
+                               libDir, cacheDir, 0.0f,
+                               0.0f, null, null);
+
+    /* Now find the dump file that Emacs should use, if it has already
+       been dumped.  */
+    EmacsApplication.findDumpFile (context);
+
+    /* Start Emacs.  */
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
+  }
+};
diff --git a/java/org/gnu/emacs/EmacsService.java 
b/java/org/gnu/emacs/EmacsService.java
index 4db1ea5359..91db76b08e 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -41,6 +41,9 @@ import android.app.Service;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 
 import android.net.Uri;
@@ -118,8 +121,38 @@ public class EmacsService extends Service
     return null;
   }
 
+  @SuppressWarnings ("deprecation")
+  private String
+  getApkFile ()
+  {
+    PackageManager manager;
+    ApplicationInfo info;
+
+    manager = getPackageManager ();
+
+    try
+      {
+       if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
+         info = manager.getApplicationInfo ("org.gnu.emacs", 0);
+       else
+         info = manager.getApplicationInfo ("org.gnu.emacs",
+                                            ApplicationInfoFlags.of (0));
+
+       /* Return an empty string upon failure.  */
+
+       if (info.sourceDir != null)
+         return info.sourceDir;
+
+       return "";
+      }
+    catch (Exception e)
+      {
+       return "";
+      }
+  }
+
   @TargetApi (Build.VERSION_CODES.GINGERBREAD)
-  String
+  private String
   getLibraryDirectory ()
   {
     int apiLevel;
@@ -142,7 +175,7 @@ public class EmacsService extends Service
   {
     AssetManager manager;
     Context app_context;
-    String filesDir, libDir, cacheDir;
+    String filesDir, libDir, cacheDir, classPath;
     double pixelDensityX;
     double pixelDensityY;
 
@@ -162,13 +195,18 @@ public class EmacsService extends Service
        libDir = getLibraryDirectory ();
        cacheDir = app_context.getCacheDir ().getCanonicalPath ();
 
+       /* Now provide this application's apk file, so a recursive
+          invocation of app_process (through android-emacs) can
+          find EmacsNoninteractive.  */
+       classPath = getApkFile ();
+
        Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
-              + " and libDir = " + libDir);
+              + ", libDir = " + libDir + ", and classPath = " + classPath);
 
        EmacsNative.setEmacsParams (manager, filesDir, libDir,
                                    cacheDir, (float) pixelDensityX,
                                    (float) pixelDensityY,
-                                   this);
+                                   classPath, this);
 
        /* Start the thread that runs Emacs.  */
        thread = new EmacsThread (this, needDashQ);
@@ -491,8 +529,6 @@ public class EmacsService extends Service
   public static void
   startEmacsService (Context context)
   {
-    PendingIntent intent;
-
     if (EmacsService.SERVICE == null)
       {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
diff --git a/java/org/gnu/emacs/EmacsThread.java 
b/java/org/gnu/emacs/EmacsThread.java
index 5b76d11db4..f5e9d54044 100644
--- a/java/org/gnu/emacs/EmacsThread.java
+++ b/java/org/gnu/emacs/EmacsThread.java
@@ -33,30 +33,18 @@ public class EmacsThread extends Thread
     this.startDashQ = startDashQ;
   }
 
+  @Override
   public void
   run ()
   {
     String args[];
 
-    if (EmacsApplication.dumpFileName == null)
-      {
-       if (!startDashQ)
-         args = new String[] { "libandroid-emacs.so", };
-       else
-         args = new String[] { "libandroid-emacs.so", "-Q", };
-      }
+    if (!startDashQ)
+      args = new String[] { "libandroid-emacs.so", };
     else
-      {
-       if (!startDashQ)
-         args = new String[] { "libandroid-emacs.so", "--dump-file",
-                               EmacsApplication.dumpFileName, };
-       else
-         args = new String[] { "libandroid-emacs.so", "-Q",
-                               "--dump-file",
-                               EmacsApplication.dumpFileName, };
-      }
+      args = new String[] { "libandroid-emacs.so", "-Q", };
 
     /* Run the native code now.  */
-    EmacsNative.initEmacs (args);
+    EmacsNative.initEmacs (args, EmacsApplication.dumpFileName);
   }
 };
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 8d2e4bbb7c..1747d1d960 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -560,22 +560,23 @@ lost after dumping")))
       ;; different build fingerprint upon being created, which happens
       ;; the moment the Android system starts Emacs.  Then, it passes
       ;; the appropriate "--dump-file" to libemacs.so as it starts.
-      (let ((temp-dir (getenv "TEMP"))
-            (dump-file-name (format "%semacs-%s.pdmp"
-                                    (file-name-as-directory "~")
-                                    pdumper-fingerprint))
-            (dump-temp-file-name (format "%s~emacs-%s.pdmp"
-                                         (file-name-as-directory "~")
-                                         pdumper-fingerprint)))
-        (unless (pdumper-stats)
-          (condition-case ()
-              (progn
-                (dump-emacs-portable dump-temp-file-name)
-                ;; Move the dumped file to the actual dump file name.
-                (rename-file dump-temp-file-name dump-file-name)
-                ;; Continue with loadup.
-                nil)
-            (error nil)))))
+      (when (not noninteractive)
+        (let ((temp-dir (getenv "TEMP"))
+              (dump-file-name (format "%semacs-%s.pdmp"
+                                      (file-name-as-directory "~")
+                                      pdumper-fingerprint))
+              (dump-temp-file-name (format "%s~emacs-%s.pdmp"
+                                           (file-name-as-directory "~")
+                                           pdumper-fingerprint)))
+          (unless (pdumper-stats)
+            (condition-case ()
+                (progn
+                  (dump-emacs-portable dump-temp-file-name)
+                  ;; Move the dumped file to the actual dump file name.
+                  (rename-file dump-temp-file-name dump-file-name)
+                  ;; Continue with loadup.
+                  nil)
+              (error nil))))))
   (if dump-mode
       (let ((output (cond ((equal dump-mode "pdump") "emacs.pdmp")
                           ((equal dump-mode "dump") "emacs")
diff --git a/lisp/shell.el b/lisp/shell.el
index 5cf108bfa3..29f7d5c02d 100644
--- a/lisp/shell.el
+++ b/lisp/shell.el
@@ -1332,7 +1332,12 @@ Returns t if successful."
     (while path-dirs
       (setq dir (file-name-as-directory (comint-directory (or (car path-dirs) 
".")))
            comps-in-dir (and (file-accessible-directory-p dir)
-                             (file-name-all-completions filenondir dir)))
+                             (condition-case nil
+                                  (file-name-all-completions filenondir dir)
+                                ;; Systems such as Android sometimes
+                                ;; put inaccessible directories in
+                                ;; PATH.
+                                (permission-denied nil))))
       ;; Go thru each completion found, to see whether it should be used.
       (while comps-in-dir
        (setq file (car comps-in-dir)
diff --git a/src/Makefile.in b/src/Makefile.in
index 29beb519ab..fe745770b9 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -768,7 +768,7 @@ libemacs.so: $(ALLOBJS) $(LIBEGNU_ARCHIVE) $(EMACSRES) \
 
 android-emacs: libemacs.so android-emacs.o
        $(AM_V_CCLD)$(CC) -o $@ $(ALL_CFLAGS) $(LDFLAGS)        \
-         -L. "-l:libemacs.so" android-emacs.o
+         $(LIBEGNU_ARCHIVE) -L. "-l:libemacs.so" android-emacs.o
 endif
 
 ## The following oldxmenu-related rules are only (possibly) used if
diff --git a/src/android-emacs.c b/src/android-emacs.c
index d4fa14e39f..c1f2a6f43b 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
 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include <config.h>
-#include "android.h"
+#include <stdio.h>
+#include <alloca.h>
+#include <string.h>
+#include <unistd.h>
 
-/* android-emacs is a wrapper around libemacs.  It simply calls
-   android_emacs_init with the argv and argc given to it.  */
+/* android-emacs is a wrapper around /system/bin/app_process(64).
+   It invokes app_process(64) with the right class path and then
+   starts org.gnu.emacs.EmacsNoninteractive.
+
+   The main function in that class tries to load an activity thread
+   and obtain a context and asset manager before calling
+   android_emacs_init, which is required for Emacs to find required
+   preloaded Lisp.  */
 
 int
 main (int argc, char **argv)
 {
-  return android_emacs_init (argc, argv);
+  char **args;
+  int i;
+  char *bootclasspath, *emacs_class_path;
+
+  /* Allocate enough to hold the arguments to app_process.  */
+  args = alloca ((10 + argc) * sizeof *args);
+
+  /* Clear args.  */
+  memset (args, 0, (10 + argc) * sizeof *args);
+
+  /* First, figure out what program to start.  */
+#if defined __x86_64__ || defined __aarch64__
+  args[0] = (char *) "/system/bin/app_process64";
+#else
+  args[0] = (char *) "/system/bin/app_process";
+#endif
+
+  /* Next, obtain the boot class path.  */
+  bootclasspath = getenv ("BOOTCLASSPATH");
+
+  /* And the Emacs class path.  */
+  emacs_class_path = getenv ("EMACS_CLASS_PATH");
+
+  if (!bootclasspath)
+    {
+      fprintf (stderr, "The BOOTCLASSPATH environment variable"
+              " is not set.  As a result, Emacs does not know"
+              " how to start app_process.\n"
+              "This is likely a change in the Android platform."
+              "  Please report this to bug-gnu-emacs@gnu.org.\n");
+      return 1;
+    }
+
+  if (!emacs_class_path)
+    {
+      fprintf (stderr, "EMACS_CLASS_PATH not set."
+              "  Please make sure Emacs is being started"
+              " from within a running copy of Emacs.\n");
+      return 1;
+    }
+
+  if (asprintf (&bootclasspath, "-Djava.class.path=%s:%s",
+               bootclasspath, emacs_class_path) < 0)
+    {
+      perror ("asprintf");
+      return 1;
+    }
+
+  args[1] = bootclasspath;
+  args[2] = (char *) "/system/bin";
+  args[3] = (char *) "--nice-name=emacs";
+  args[4] = (char *) "org.gnu.emacs.EmacsNoninteractive";
+
+  /* Arguments from here on are passed to main in
+     EmacsNoninteractive.java.  */
+  args[5] = argv[0];
+
+  /* Now copy the rest of the arguments over.  */
+  for (i = 1; i < argc; ++i)
+    args[5 + i] = argv[i];
+
+  /* Finally, try to start the app_process.  */
+  execvp (args[0], args);
+
+  /* If exit fails, return an error indication.  */
+  perror ("exec");
+  return 1;
 }
diff --git a/src/android.c b/src/android.c
index c186b46236..8c4442e339 100644
--- a/src/android.c
+++ b/src/android.c
@@ -145,6 +145,10 @@ char *android_game_path;
 /* The directory used to store temporary files.  */
 char *android_cache_dir;
 
+/* The list of archive files within which the Java virtual macine
+   looks for class files.  */
+char *android_class_path;
+
 /* The display's pixel densities.  */
 double android_pixel_density_x, android_pixel_density_y;
 
@@ -1315,6 +1319,7 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
                              jobject cache_dir,
                              jfloat pixel_density_x,
                              jfloat pixel_density_y,
+                             jobject class_path,
                              jobject emacs_service_object)
 {
   int pipefd[2];
@@ -1372,19 +1377,30 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
      object from being deleted.  */
   (*env)->NewGlobalRef (env, local_asset_manager);
 
-  /* Create a pipe and duplicate it to stdout and stderr.  Next, make
-     a thread that prints stderr to the system log.  */
+  if (emacs_service_object)
+    {
+      /* Create a pipe and duplicate it to stdout and stderr.  Next,
+        make a thread that prints stderr to the system log.
 
-  if (pipe2 (pipefd, O_CLOEXEC) < 0)
-    emacs_abort ();
+         Notice that this function is called in one of two ways.  The
+         first is when Emacs is being started as a GUI application by
+         the system, and the second is when Emacs is being started by
+         libandroid-emacs.so as an ordinary noninteractive Emacs.
 
-  if (dup2 (pipefd[1], 2) < 0)
-    emacs_abort ();
-  close (pipefd[1]);
+         In the second case, stderr is usually connected to a PTY, so
+         this is unnecessary.  */
 
-  if (pthread_create (&thread, NULL, android_run_debug_thread,
-                     (void *) (intptr_t) pipefd[0]))
-    emacs_abort ();
+      if (pipe2 (pipefd, O_CLOEXEC) < 0)
+       emacs_abort ();
+
+      if (dup2 (pipefd[1], 2) < 0)
+       emacs_abort ();
+      close (pipefd[1]);
+
+      if (pthread_create (&thread, NULL, android_run_debug_thread,
+                         (void *) (intptr_t) pipefd[0]))
+       emacs_abort ();
+    }
 
   /* Now set the path to the site load directory.  */
 
@@ -1430,6 +1446,23 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
   (*env)->ReleaseStringUTFChars (env, (jstring) cache_dir,
                                 java_string);
 
+  if (class_path)
+    {
+      java_string = (*env)->GetStringUTFChars (env, (jstring) class_path,
+                                              NULL);
+
+      if (!java_string)
+       emacs_abort ();
+
+      android_class_path = strdup ((const char *) java_string);
+
+      if (!android_files_dir)
+       emacs_abort ();
+
+      (*env)->ReleaseStringUTFChars (env, (jstring) class_path,
+                                    java_string);
+    }
+
   /* Calculate the site-lisp path.  */
 
   android_site_load_path = malloc (PATH_MAX + 1);
@@ -1450,16 +1483,32 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject 
object,
                       "Site-lisp directory: %s\n"
                       "Files directory: %s\n"
                       "Native code directory: %s\n"
-                      "Game score path: %s\n",
+                      "Game score path: %s\n"
+                      "Class path: %s\n",
                       android_site_load_path,
                       android_files_dir,
-                      android_lib_dir, android_game_path);
+                      android_lib_dir, android_game_path,
+                      (android_class_path
+                       ? android_class_path
+                       : "None"));
+
+  if (android_class_path)
+    /* Set EMACS_CLASS_PATH to the class path where
+       EmacsNoninteractive can be found.  */
+    setenv ("EMACS_CLASS_PATH", android_class_path, 1);
+
+  /* Set LD_LIBRARY_PATH to an appropriate value.  */
+  setenv ("LD_LIBRARY_PATH", android_lib_dir, 1);
 
   /* Make a reference to the Emacs service.  */
-  emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
 
-  if (!emacs_service)
-    emacs_abort ();
+  if (emacs_service_object)
+    {
+      emacs_service = (*env)->NewGlobalRef (env, emacs_service_object);
+
+      if (!emacs_service)
+       emacs_abort ();
+    }
 
   /* Set up events.  */
   android_init_events ();
@@ -1660,12 +1709,14 @@ android_init_emacs_window (void)
 }
 
 extern JNIEXPORT void JNICALL
-NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
+NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv,
+                        jobject dump_file_object)
 {
   char **c_argv;
   jsize nelements, i;
   jobject argument;
   const char *c_argument;
+  char *dump_file;
 
   android_java_env = env;
 
@@ -1705,9 +1756,34 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, 
jarray argv)
     __android_log_print (ANDROID_LOG_WARN, __func__,
                         "chdir: %s", strerror (errno));
 
-  /* Initialize the Android GUI.  */
-  android_init_gui = true;
-  android_emacs_init (nelements, c_argv);
+  /* Initialize the Android GUI as long as the service object was
+     set.  */
+
+  if (emacs_service)
+    android_init_gui = true;
+
+  /* Now see if a dump file has been specified and should be used.  */
+  dump_file = NULL;
+
+  if (dump_file_object)
+    {
+      c_argument
+       = (*env)->GetStringUTFChars (env, (jstring) dump_file_object,
+                                    NULL);
+
+      /* Copy the Java string data once.  */
+      dump_file = strdup (c_argument);
+
+      /* Release the Java string data.  */
+      (*env)->ReleaseStringUTFChars (env, (jstring) dump_file_object,
+                                    c_argument);
+    }
+
+  /* Delete local references to objects that are no longer needed.  */
+  ANDROID_DELETE_LOCAL_REF (argv);
+  ANDROID_DELETE_LOCAL_REF (dump_file_object);
+
+  android_emacs_init (nelements, c_argv, dump_file);
   /* android_emacs_init should never return.  */
   emacs_abort ();
 }
diff --git a/src/android.h b/src/android.h
index ce0ccfc433..6c20995e4a 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/>.  */
 #include "lisp.h"
 #endif
 
-/* This must be used in every symbol declaration to export it to the
-   JNI Emacs wrapper.  */
-#define ANDROID_EXPORT __attribute__ ((visibility ("default")))
-
-extern bool ANDROID_EXPORT android_init_gui;
-extern int ANDROID_EXPORT android_emacs_init (int, char **);
+extern bool android_init_gui;
 
 #ifndef ANDROID_STUBIFY
 
+extern int android_emacs_init (int, char **, char *);
 extern int android_select (int, fd_set *, fd_set *, fd_set *,
                           struct timespec *);
 
diff --git a/src/androidfont.c b/src/androidfont.c
index bec5a59ca7..55f4575835 100644
--- a/src/androidfont.c
+++ b/src/androidfont.c
@@ -1104,6 +1104,9 @@ syms_of_androidfont (void)
 void
 init_androidfont (void)
 {
+  if (!android_init_gui)
+    return;
+
   android_init_font_driver ();
   android_init_font_spec ();
   android_init_font_metrics ();
diff --git a/src/androidselect.c b/src/androidselect.c
index 2e2f2d7e48..4585d64b7e 100644
--- a/src/androidselect.c
+++ b/src/androidselect.c
@@ -240,6 +240,9 @@ init_androidselect (void)
   jobject tem;
   jmethodID make_clipboard;
 
+  if (!android_init_gui)
+    return;
+
   android_init_emacs_clipboard ();
 
   make_clipboard = clipboard_class.make_clipboard;
diff --git a/src/emacs.c b/src/emacs.c
index b0c19fe007..a24f996049 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -875,19 +875,23 @@ dump_error_to_string (int result)
     }
 }
 
-/* This function returns the Emacs executable.  */
+/* This function returns the Emacs executable.  DUMP_FILE is ignored
+   outside of Android.  Otherwise, it is the name of the dump file to
+   use, or NULL if Emacs should look for a ``--dump-file'' argument
+   instead.  */
+
 static char *
-load_pdump (int argc, char **argv)
+load_pdump (int argc, char **argv, char *dump_file)
 {
 #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
-  char *dump_file = NULL;
   int skip_args = 0, result;
 
   while (skip_args < argc - 1)
     {
-      if (argmatch (argv, argc, "-dump-file", "--dump-file", 6,
-                   &dump_file, &skip_args)
-         || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args))
+      if (argmatch (argv, argc, "-dump-file", "--dump-file",
+                   6, &dump_file, &skip_args)
+         || argmatch (argv, argc, "--", NULL, 2, NULL,
+                      &skip_args))
        break;
       skip_args++;
     }
@@ -933,7 +937,7 @@ load_pdump (int argc, char **argv)
 
   /* Look for an explicitly-specified dump file.  */
   const char *path_exec = PATH_EXEC;
-  char *dump_file = NULL;
+  dump_file = NULL;
   int skip_args = 0;
   while (skip_args < argc - 1)
     {
@@ -1276,7 +1280,7 @@ maybe_load_seccomp (int argc, char **argv)
 
 #if defined HAVE_ANDROID && !defined ANDROID_STUBIFY
 int
-android_emacs_init (int argc, char **argv)
+android_emacs_init (int argc, char **argv, char *dump_file)
 #else
 int
 main (int argc, char **argv)
@@ -1286,6 +1290,12 @@ main (int argc, char **argv)
      for pointers.  */
   void *stack_bottom_variable;
   int old_argc;
+#if !(defined HAVE_ANDROID && !defined ANDROID_STUBIFY)
+  char *dump_file;
+
+  /* This is just a dummy argument used to avoid extra defines.  */
+  dump_file = NULL;
+#endif
 
   /* First, check whether we should apply a seccomp filter.  This
      should come at the very beginning to allow the filter to protect
@@ -1415,7 +1425,7 @@ main (int argc, char **argv)
 
 #ifdef HAVE_PDUMPER
   if (attempt_load_pdump)
-    initial_emacs_executable = load_pdump (argc, argv);
+    initial_emacs_executable = load_pdump (argc, argv, dump_file);
 #else
   ptrdiff_t bufsize;
   initial_emacs_executable = find_emacs_executable (argv[0], &bufsize);
diff --git a/src/sfntfont-android.c b/src/sfntfont-android.c
index bc8f1b35b8..ab90a2e4ec 100644
--- a/src/sfntfont-android.c
+++ b/src/sfntfont-android.c
@@ -729,6 +729,9 @@ syms_of_sfntfont_android_for_pdumper (void)
 void
 init_sfntfont_android (void)
 {
+  if (!android_init_gui)
+    return;
+
   /* Make sure to pick the right Sans Serif font depending on what
      version of Android the device is running.  */
 #if HAVE_DECL_ANDROID_GET_DEVICE_API_LEVEL



reply via email to

[Prev in Thread] Current Thread [Next in Thread]