From d44b60c2f001d57b010f0e9b82f798fbad9a23d6 Mon Sep 17 00:00:00 2001
From: Po Lu
Date: Fri, 20 Jan 2023 19:06:32 +0800
Subject: Update Android port
* .gitignore: Don't ignore verbose.mk.android.
* doc/emacs/Makefile.in (EMACSSOURCES): Add android.texi and
input.texi.
* doc/emacs/android.texi (Android): Document support for the
on-screen keyboard.
(Android Startup): Document how to start Emacs with -Q on
Android.
(Android Environment): Document how Emacs works around the
system ``task killer''. Document changes to frame deletion
behavior.
* doc/emacs/emacs.texi (Top):
* doc/emacs/input.texi (Other Input Devices, On-Screen
Keyboards): Document how to use Emacs with virtual keyboards.
* doc/lispref/commands.texi (Touchscreen Events): Document
changes to `touch-screen-track-drag'.
* doc/lispref/frames.texi (Frames, On-Screen Keyboards): New
node.
* java/AndroidManifest.xml.in: Add settings activity and
appropriate OSK adjustment mode.
* java/org/gnu/emacs/EmacsActivity.java (onCreate): Allow
creating Emacs with -Q.
(onDestroy): Don't remove if killed by the system.
* java/org/gnu/emacs/EmacsContextMenu.java (inflateMenuItems):
Fix context menus again.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative): Make all
event sending functions return long.
* java/org/gnu/emacs/EmacsPreferencesActivity.java
(EmacsPreferencesActivity): New class.
* java/org/gnu/emacs/EmacsService.java (EmacsService)
(onStartCommand, onCreate, startEmacsService): Start as a
foreground service if necessary to bypass system restrictions.
* java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
* java/org/gnu/emacs/EmacsThread.java (EmacsThread, run):
* java/org/gnu/emacs/EmacsView.java (EmacsView, onLayout)
(onDetachedFromWindow):
* java/org/gnu/emacs/EmacsWindow.java (EmacsWindow, viewLayout):
Implement frame resize synchronization..
* java/org/gnu/emacs/EmacsWindowAttachmentManager.java
(EmacsWindowAttachmentManager, removeWindowConsumer): Adjust
accordingly for changes to frame deletion behavior.
* lisp/frame.el (android-toggle-on-screen-keyboard)
(frame-toggle-on-screen-keyboard): New function.
* lisp/minibuffer.el (minibuffer-setup-on-screen-keyboard)
(minibuffer-exit-on-screen-keyboard): New functions.
(minibuffer-setup-hook, minibuffer-exit-hook): Add new functions
to hooks.
* lisp/touch-screen.el (touch-screen-relative-xy): Accept new
value of window `frame'. Return frame coordinates in that case.
(touch-screen-set-point-commands): New variable.
(touch-screen-handle-point-up): Respect that variable.
(touch-screen-track-drag): Return `no-drag' where appropriate.
(touch-screen-drag-mode-line-1, touch-screen-drag-mode-line):
Refactor to use `no-drag'.
* src/android.c (struct android_emacs_window): New methods.
Make all event sending functions return the event serial.
(android_toggle_on_screen_keyboard, android_window_updated): New
functions.
* src/android.h: Update prototypes.
* src/androidfns.c (Fandroid_toggle_on_screen_keyboard)
(syms_of_androidfns): New function.
* src/androidgui.h (struct android_any_event)
(struct android_key_event, struct android_configure_event)
(struct android_focus_event, struct android_window_action_event)
(struct android_crossing_event, struct android_motion_event)
(struct android_button_event, struct android_touch_event)
(struct android_wheel_event, struct android_iconify_event)
(struct android_menu_event): Add `serial' fields.
* src/androidterm.c (handle_one_android_event)
(android_frame_up_to_date):
* src/androidterm.h (struct android_output): Implement frame
resize synchronization.
---
java/AndroidManifest.xml.in | 12 ++
java/org/gnu/emacs/EmacsActivity.java | 20 +++-
java/org/gnu/emacs/EmacsContextMenu.java | 1 +
java/org/gnu/emacs/EmacsNative.java | 39 ++++---
java/org/gnu/emacs/EmacsPreferencesActivity.java | 98 ++++++++++++++++
java/org/gnu/emacs/EmacsService.java | 62 +++++++++-
java/org/gnu/emacs/EmacsSurfaceView.java | 127 +++++++++++++++------
java/org/gnu/emacs/EmacsThread.java | 12 +-
java/org/gnu/emacs/EmacsView.java | 55 ++++++++-
java/org/gnu/emacs/EmacsWindow.java | 58 ++++++++--
.../gnu/emacs/EmacsWindowAttachmentManager.java | 4 +-
11 files changed, 412 insertions(+), 76 deletions(-)
create mode 100644 java/org/gnu/emacs/EmacsPreferencesActivity.java
(limited to 'java')
diff --git a/java/AndroidManifest.xml.in b/java/AndroidManifest.xml.in
index b680137a9d0..74f69d2a8e5 100644
--- a/java/AndroidManifest.xml.in
+++ b/java/AndroidManifest.xml.in
@@ -62,8 +62,10 @@ along with GNU Emacs. If not, see . -->
android:theme="@android:style/Theme"
android:debuggable="true"
android:extractNativeLibs="true">
+
@@ -73,8 +75,18 @@ along with GNU Emacs. If not, see . -->
+
+
+
+
+
+
+
. */
+
+package org.gnu.emacs;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import android.R;
+
+/* This module provides a ``preferences'' display for Emacs. It is
+ supposed to be launched from inside the Settings application to
+ perform various actions, such as starting Emacs with the ``-Q''
+ option, which would not be possible otherwise, as there is no
+ command line on Android. */
+
+public class EmacsPreferencesActivity extends Activity
+{
+ /* The linear layout associated with the activity. */
+ private LinearLayout layout;
+
+ /* Restart Emacs with -Q. Call EmacsThread.exit to kill Emacs now, and
+ tell the system to EmacsActivity with some parameters later. */
+
+ private void
+ startEmacsQ ()
+ {
+ Intent intent;
+
+ intent = new Intent (this, EmacsActivity.class);
+ intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra ("org.gnu.emacs.START_DASH_Q", true);
+ startActivity (intent);
+ System.exit (0);
+ }
+
+ @Override
+ public void
+ onCreate (Bundle savedInstanceState)
+ {
+ LinearLayout layout;
+ TextView textView;
+ LinearLayout.LayoutParams params;
+ int resid;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ setTheme (R.style.Theme_DeviceDefault_Settings);
+ else if (Build.VERSION.SDK_INT
+ >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ setTheme (R.style.Theme_DeviceDefault);
+
+ layout = new LinearLayout (this);
+ layout.setOrientation (LinearLayout.VERTICAL);
+ setContentView (layout);
+
+ textView = new TextView (this);
+ textView.setPadding (8, 20, 20, 8);
+
+ params = new LinearLayout.LayoutParams (LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT);
+ textView.setLayoutParams (params);
+ textView.setText ("(Re)start Emacs with -Q");
+ textView.setOnClickListener (new View.OnClickListener () {
+ @Override
+ public void
+ onClick (View view)
+ {
+ startEmacsQ ();
+ }
+ });
+ layout.addView (textView);
+
+ super.onCreate (savedInstanceState);
+ }
+};
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index bcf8d9ff6e8..95f21b211a3 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -32,7 +32,13 @@ import android.view.InputDevice;
import android.view.KeyEvent;
import android.annotation.TargetApi;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.app.Service;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
@@ -63,6 +69,7 @@ public class EmacsService extends Service
public static final String TAG = "EmacsService";
public static final int MAX_PENDING_REQUESTS = 256;
public static volatile EmacsService SERVICE;
+ public static boolean needDashQ;
private EmacsThread thread;
private Handler handler;
@@ -74,6 +81,31 @@ public class EmacsService extends Service
public int
onStartCommand (Intent intent, int flags, int startId)
{
+ Notification notification;
+ NotificationManager manager;
+ NotificationChannel channel;
+ String infoBlurb;
+ Object tem;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ {
+ tem = getSystemService (Context.NOTIFICATION_SERVICE);
+ manager = (NotificationManager) tem;
+ infoBlurb = ("See (emacs)Android Environment for more"
+ + " details about this notification.");
+ channel
+ = new NotificationChannel ("emacs", "Emacs persistent notification",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ manager.createNotificationChannel (channel);
+ notification = (new Notification.Builder (this, "emacs")
+ .setContentTitle ("Emacs")
+ .setContentText (infoBlurb)
+ .setSmallIcon (android.R.drawable.sym_def_app_icon)
+ .build ());
+ manager.notify (1, notification);
+ startForeground (1, notification);
+ }
+
return START_NOT_STICKY;
}
@@ -137,7 +169,7 @@ public class EmacsService extends Service
this);
/* Start the thread that runs Emacs. */
- thread = new EmacsThread (this);
+ thread = new EmacsThread (this, needDashQ);
thread.start ();
}
catch (IOException exception)
@@ -444,4 +476,32 @@ public class EmacsService extends Service
}
}
}
+
+
+
+ /* Start the Emacs service if necessary. On Android 26 and up,
+ start Emacs as a foreground service with a notification, to avoid
+ it being killed by the system.
+
+ On older systems, simply start it as a normal background
+ service. */
+
+ public static void
+ startEmacsService (Context context)
+ {
+ PendingIntent intent;
+
+ 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));
+ }
+ }
};
diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java
index f713818d4bc..2fe9e103b2b 100644
--- a/java/org/gnu/emacs/EmacsSurfaceView.java
+++ b/java/org/gnu/emacs/EmacsSurfaceView.java
@@ -32,53 +32,106 @@ import android.util.Log;
public class EmacsSurfaceView extends SurfaceView
{
private static final String TAG = "EmacsSurfaceView";
-
public Object surfaceChangeLock;
private boolean created;
+ private EmacsView view;
- public
- EmacsSurfaceView (final EmacsView view)
- {
- super (view.getContext ());
-
- surfaceChangeLock = new Object ();
+ /* This is the callback used on Android 8 to 25. */
- getHolder ().addCallback (new SurfaceHolder.Callback () {
- @Override
- public void
- surfaceChanged (SurfaceHolder holder, int format,
- int width, int height)
+ private class Callback implements SurfaceHolder.Callback
+ {
+ @Override
+ public void
+ surfaceChanged (SurfaceHolder holder, int format,
+ int width, int height)
+ {
+ Log.d (TAG, "surfaceChanged: " + view + ", " + view.pendingConfigure);
+
+ /* Make sure not to swap buffers if there is pending
+ configuration, because otherwise the redraw callback will not
+ run correctly. */
+
+ if (view.pendingConfigure == 0)
+ view.swapBuffers ();
+ }
+
+ @Override
+ public void
+ surfaceCreated (SurfaceHolder holder)
+ {
+ synchronized (surfaceChangeLock)
{
- Log.d (TAG, "surfaceChanged: " + view);
- view.swapBuffers ();
+ Log.d (TAG, "surfaceCreated: " + view);
+ created = true;
}
- @Override
- public void
- surfaceCreated (SurfaceHolder holder)
- {
- synchronized (surfaceChangeLock)
- {
- Log.d (TAG, "surfaceCreated: " + view);
- created = true;
- }
-
- /* Drop the lock when doing this, or a deadlock can
- result. */
- view.swapBuffers ();
- }
+ /* Drop the lock when doing this, or a deadlock can
+ result. */
+ view.swapBuffers ();
+ }
- @Override
- public void
- surfaceDestroyed (SurfaceHolder holder)
+ @Override
+ public void
+ surfaceDestroyed (SurfaceHolder holder)
+ {
+ synchronized (surfaceChangeLock)
{
- synchronized (surfaceChangeLock)
- {
- Log.d (TAG, "surfaceDestroyed: " + view);
- created = false;
- }
+ Log.d (TAG, "surfaceDestroyed: " + view);
+ created = false;
}
- });
+ }
+ }
+
+ /* And this is the callback used on Android 26 and later. It is
+ used because it can tell the system when drawing completes. */
+
+ private class Callback2 extends Callback implements SurfaceHolder.Callback2
+ {
+ @Override
+ public void
+ surfaceRedrawNeeded (SurfaceHolder holder)
+ {
+ /* This version is not supported. */
+ return;
+ }
+
+ @Override
+ public void
+ surfaceRedrawNeededAsync (SurfaceHolder holder,
+ Runnable drawingFinished)
+ {
+ Runnable old;
+
+ Log.d (TAG, "surfaceRedrawNeededAsync: " + view.pendingConfigure);
+
+ /* The system calls this function when it wants to know whether
+ or not Emacs is still configuring itself in response to a
+ resize.
+
+ If the view did not send an outstanding ConfigureNotify
+ event, then call drawingFinish immediately. Else, give it to
+ the view to execute after drawing completes. */
+
+ if (view.pendingConfigure == 0)
+ drawingFinished.run ();
+ else
+ /* And set this runnable to run once drawing completes. */
+ view.drawingFinished = drawingFinished;
+ }
+ }
+
+ public
+ EmacsSurfaceView (final EmacsView view)
+ {
+ super (view.getContext ());
+
+ this.surfaceChangeLock = new Object ();
+ this.view = view;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
+ getHolder ().addCallback (new Callback ());
+ else
+ getHolder ().addCallback (new Callback2 ());
}
public boolean
diff --git a/java/org/gnu/emacs/EmacsThread.java b/java/org/gnu/emacs/EmacsThread.java
index 0882753747f..f9bc132f354 100644
--- a/java/org/gnu/emacs/EmacsThread.java
+++ b/java/org/gnu/emacs/EmacsThread.java
@@ -23,12 +23,13 @@ import java.lang.Thread;
public class EmacsThread extends Thread
{
- EmacsService context;
+ /* Whether or not Emacs should be started -Q. */
+ private boolean startDashQ;
public
- EmacsThread (EmacsService service)
+ EmacsThread (EmacsService service, boolean startDashQ)
{
- context = service;
+ this.startDashQ = startDashQ;
}
public void
@@ -36,7 +37,10 @@ public class EmacsThread extends Thread
{
String args[];
- args = new String[] { "libandroid-emacs.so", };
+ if (!startDashQ)
+ args = new String[] { "libandroid-emacs.so", };
+ else
+ args = new String[] { "libandroid-emacs.so", "-Q", };
/* Run the native code now. */
EmacsNative.initEmacs (args);
diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java
index 82f44acaebe..74bbb7b3ecc 100644
--- a/java/org/gnu/emacs/EmacsView.java
+++ b/java/org/gnu/emacs/EmacsView.java
@@ -19,6 +19,7 @@ along with GNU Emacs. If not, see . */
package org.gnu.emacs;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.view.ContextMenu;
@@ -27,6 +28,8 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -86,11 +89,23 @@ public class EmacsView extends ViewGroup
/* The serial of the last clip rectangle change. */
private long lastClipSerial;
+ /* The InputMethodManager for this view's context. */
+ private InputMethodManager imManager;
+
+ /* Runnable that will run once drawing completes. */
+ public Runnable drawingFinished;
+
+ /* Serial of the last ConfigureNotify event sent that Emacs has not
+ yet responded to. 0 if there is no such outstanding event. */
+ public long pendingConfigure;
+
public
EmacsView (EmacsWindow window)
{
super (EmacsService.SERVICE);
+ Object tem;
+
this.window = window;
this.damageRegion = new Region ();
this.paint = new Paint ();
@@ -111,6 +126,10 @@ public class EmacsView extends ViewGroup
/* Get rid of the default focus highlight. */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
setDefaultFocusHighlightEnabled (false);
+
+ /* Obtain the input method manager. */
+ tem = getContext ().getSystemService (Context.INPUT_METHOD_SERVICE);
+ imManager = (InputMethodManager) tem;
}
private void
@@ -259,7 +278,8 @@ public class EmacsView extends ViewGroup
if (changed || mustReportLayout)
{
mustReportLayout = false;
- window.viewLayout (left, top, right, bottom);
+ pendingConfigure
+ = window.viewLayout (left, top, right, bottom);
}
measuredWidth = right - left;
@@ -538,4 +558,37 @@ public class EmacsView extends ViewGroup
Runtime.getRuntime ().gc ();
}
}
+
+ public void
+ showOnScreenKeyboard ()
+ {
+ /* Specifying no flags at all tells the system the user asked for
+ the input method to be displayed. */
+ imManager.showSoftInput (this, 0);
+ }
+
+ public void
+ hideOnScreenKeyboard ()
+ {
+ imManager.hideSoftInputFromWindow (this.getWindowToken (),
+ 0);
+ }
+
+ public void
+ windowUpdated (long serial)
+ {
+ Log.d (TAG, "windowUpdated: serial is " + serial);
+
+ if (pendingConfigure <= serial
+ /* Detect wraparound. */
+ || pendingConfigure - serial >= 0x7fffffff)
+ {
+ pendingConfigure = 0;
+
+ if (drawingFinished != null)
+ drawingFinished.run ();
+
+ drawingFinished = null;
+ }
+ }
};
diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java
index c5b1522086c..8511af9193e 100644
--- a/java/org/gnu/emacs/EmacsWindow.java
+++ b/java/org/gnu/emacs/EmacsWindow.java
@@ -233,7 +233,7 @@ public class EmacsWindow extends EmacsHandleObject
return attached;
}
- public void
+ public long
viewLayout (int left, int top, int right, int bottom)
{
int rectWidth, rectHeight;
@@ -249,10 +249,10 @@ public class EmacsWindow extends EmacsHandleObject
rectWidth = right - left;
rectHeight = bottom - top;
- EmacsNative.sendConfigureNotify (this.handle,
- System.currentTimeMillis (),
- left, top, rectWidth,
- rectHeight);
+ return EmacsNative.sendConfigureNotify (this.handle,
+ System.currentTimeMillis (),
+ left, top, rectWidth,
+ rectHeight);
}
public void
@@ -589,11 +589,20 @@ public class EmacsWindow extends EmacsHandleObject
EmacsActivity.invalidateFocus ();
}
+ /* Notice that the activity has been detached or destroyed.
+
+ ISFINISHING is set if the activity is not the main activity, or
+ if the activity was not destroyed in response to explicit user
+ action. */
+
public void
- onActivityDetached ()
+ onActivityDetached (boolean isFinishing)
{
- /* Destroy the associated frame when the activity is detached. */
- EmacsNative.sendWindowAction (this.handle, 0);
+ /* Destroy the associated frame when the activity is detached in
+ response to explicit user action. */
+
+ if (isFinishing)
+ EmacsNative.sendWindowAction (this.handle, 0);
}
/* Look through the button state to determine what button EVENT was
@@ -1064,4 +1073,37 @@ public class EmacsWindow extends EmacsHandleObject
/* Return the resulting coordinates. */
return array;
}
+
+ public void
+ toggleOnScreenKeyboard (final boolean on)
+ {
+ EmacsService.SERVICE.runOnUiThread (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ if (on)
+ view.showOnScreenKeyboard ();
+ else
+ view.hideOnScreenKeyboard ();
+ }
+ });
+ }
+
+ /* Notice that outstanding configure events have been processed.
+ SERIAL is checked in the UI thread to verify that no new
+ configure events have been generated in the mean time. */
+
+ public void
+ windowUpdated (final long serial)
+ {
+ EmacsService.SERVICE.runOnUiThread (new Runnable () {
+ @Override
+ public void
+ run ()
+ {
+ view.windowUpdated (serial);
+ }
+ });
+ }
};
diff --git a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
index 15eb3bb65c2..510300571b8 100644
--- a/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
+++ b/java/org/gnu/emacs/EmacsWindowAttachmentManager.java
@@ -134,7 +134,7 @@ public class EmacsWindowAttachmentManager
}
public void
- removeWindowConsumer (WindowConsumer consumer)
+ removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
{
EmacsWindow window;
@@ -147,7 +147,7 @@ public class EmacsWindowAttachmentManager
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
- window.onActivityDetached ();
+ window.onActivityDetached (isFinishing);
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);
--
cgit v1.2.1