1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-29 08:31:35 -08:00
emacs/java/org/gnu/emacs/EmacsNoninteractive.java
Paul Eggert 4da38c6321 Update copyright year to 2025
Run "TZ=UTC0 admin/update-copyright".
2025-01-01 07:39:17 +00:00

227 lines
7.2 KiB
Java

/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
Copyright (C) 2023-2025 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.
When started, libandroid-emacs.so invokes `app_process(64)' with a
command line placing Emacs's classes.dex file in the JVM class path,
which in turn transfers control to `main'. `main' creates a context,
which may be likened to a connection to the system server, and a
class loader derived from Emacs's application package, which it loads
beforehand. From this class loader, it loads another instance of
itself, and invokes `main1', to ensure the execution of
`EmacsNative''s static initializer within the application class
loader, where a proper library search path is in effect. */
@SuppressWarnings ("unchecked")
public final class EmacsNoninteractive
{
/* Prepare Emacs for startup and call `initEmacs'. This function is
called in an instance of `EmacsNoninteractive' loaded by the APK
ClassLoader acquired in `main', which guarantees that shared
libraries in the APK will be considered in resolving shared
libraries for `EmacsNative'. */
public static void
main1 (String[] args, Context context)
throws Exception
{
AssetManager assets;
String filesDir, libDir, cacheDir;
/* 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 = EmacsService.getLibraryDirectory (context);
cacheDir = context.getCacheDir ().getCanonicalPath ();
EmacsNative.setEmacsParams (assets, filesDir,
libDir, cacheDir, 0.0f,
0.0f, 0.0f, null, null,
Build.VERSION.SDK_INT);
/* 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);
}
public static void
main (String[] args)
{
Object activityThread, loadedApk;
Class activityThreadClass, loadedApkClass, contextImplClass;
Class compatibilityInfoClass, emacsNoninteractiveClass;
Method method;
Context context;
ClassLoader classLoader;
Looper.prepare ();
context = null;
loadedApkClass = null;
classLoader = 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. */
try
{
loadedApkClass = Class.forName ("android.app.LoadedApk");
}
catch (ClassNotFoundException exception)
{
/* Android 2.2 has no LoadedApk class; the several following
statements will load a context and an
ActivityThread.PackageInfo, as is appropriate on this
system. */
}
/* Get a LoadedApk or ActivityThread.PackageInfo. How to do
this varies by Android version. On Android 3.0 and earlier,
there is no ``compatibilityInfo'' argument to
getPackageInfo. */
try
{
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
(Context.CONTEXT_INCLUDE_CODE
| Context.CONTEXT_IGNORE_SECURITY));
}
catch (NoSuchMethodException exception)
{
compatibilityInfoClass
= Class.forName ("android.content.res.CompatibilityInfo");
method
= activityThreadClass.getMethod ("getPackageInfo",
String.class,
compatibilityInfoClass,
int.class);
loadedApk = method.invoke (activityThread, "org.gnu.emacs",
null, (Context.CONTEXT_INCLUDE_CODE
| Context.CONTEXT_IGNORE_SECURITY));
}
if (loadedApk == null)
throw new RuntimeException ("getPackageInfo returned NULL");
/* If loadedApkClass remains NULL, substitute the class of
the object returned by getPackageInfo. */
if (loadedApkClass == null)
loadedApkClass = loadedApk.getClass ();
/* Now, get a context. */
contextImplClass = Class.forName ("android.app.ContextImpl");
try
{
method
= contextImplClass.getDeclaredMethod ("createAppContext",
activityThreadClass,
loadedApkClass);
method.setAccessible (true);
context = (Context) method.invoke (null, activityThread,
loadedApk);
}
catch (NoSuchMethodException exception)
{
/* Older Android versions don't have createAppContext, but
instead require creating a ContextImpl, and then
calling createPackageContext. */
method
= activityThreadClass.getDeclaredMethod ("getSystemContext");
context = (Context) method.invoke (activityThread);
method
= contextImplClass.getDeclaredMethod ("createPackageContext",
String.class,
int.class);
method.setAccessible (true);
context = (Context) method.invoke (context, "org.gnu.emacs",
0);
}
/* Retrieve the LoadedApk's class loader and execute the
remaining portion of the start-up process within its version
of EmacsNoninteractive, which will indicate to the system
that it must load shared libraries from the APK's library
search path. */
method = loadedApkClass.getDeclaredMethod ("getClassLoader");
classLoader = (ClassLoader) method.invoke (loadedApk);
}
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.");
e.printStackTrace ();
System.exit (1);
}
try
{
emacsNoninteractiveClass
= classLoader.loadClass ("org.gnu.emacs.EmacsNoninteractive");
method = emacsNoninteractiveClass.getMethod ("main1", String[].class,
Context.class);
method.setAccessible (true);
method.invoke (null, args, context);
}
catch (Exception e)
{
System.err.println ("Internal error during startup: " + e);
e.printStackTrace ();
System.exit (1);
}
}
};