mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-05 22:20:24 -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
|
|
@ -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
|
||||
|
|
|
|||
824
java/README
824
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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
114
src/android.c
114
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 ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
28
src/emacs.c
28
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue