1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-25 23:10:47 -08:00

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.
This commit is contained in:
Po Lu 2023-01-25 18:44:47 +08:00
parent 6f9a2a8f29
commit 0900bfbcc5
17 changed files with 1306 additions and 109 deletions

View file

@ -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 ();
}
};

View file

@ -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 ();

View file

@ -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);
}
};

View file

@ -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)

View file

@ -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);
}
};