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:
parent
6f9a2a8f29
commit
0900bfbcc5
17 changed files with 1306 additions and 109 deletions
|
|
@ -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 ();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
164
java/org/gnu/emacs/EmacsNoninteractive.java
Normal file
164
java/org/gnu/emacs/EmacsNoninteractive.java
Normal 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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue