diff --git a/.gitignore b/.gitignore
index 3bc40c67489..6494e4e8f39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,7 @@ src/emacs-module.h
# Built by recursive call to `configure'.
*.android
!INSTALL.android
+!verbose.mk.android
# Built by `java'.
java/install_temp/*
diff --git a/doc/emacs/Makefile.in b/doc/emacs/Makefile.in
index 161bdcb1c59..c7415312753 100644
--- a/doc/emacs/Makefile.in
+++ b/doc/emacs/Makefile.in
@@ -146,6 +146,8 @@ EMACSSOURCES= \
${srcdir}/glossary.texi \
${srcdir}/ack.texi \
${srcdir}/kmacro.texi \
+ ${srcdir}/android.texi \
+ ${srcdir}/input.texi \
$(EMACS_XTRA)
## Disable implicit rules.
diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi
index 01a2e68a97e..ba5d2ca3e09 100644
--- a/doc/emacs/android.texi
+++ b/doc/emacs/android.texi
@@ -10,8 +10,8 @@ Alliance. This section describes the peculiarities of using Emacs on
an Android device running Android 2.2 or later.
Android devices commonly rely on user input through a touch screen
-or digitizer device. For more information about using them with
-Emacs, @pxref{other Input Devices}.
+or digitizer device and on-screen keyboard. For more information
+about using such devices with Emacs, @pxref{Other Input Devices}.
@menu
* What is Android?:: Preamble.
@@ -82,6 +82,16 @@ command on that other system:
$ adb logcat | grep -E "(android_run_debug_thread|[Ee]macs)"
@end example
+@cindex emacs -Q, android
+Since Android has no command line, there is normally no way to specify
+command-line arguments. However, Emacs can be started with the
+equivalent of the @code{--quick} option (@pxref{Initial Options})
+through a special preferences screen, which can be accessed through
+the Emacs ``app info'' page in the system settings application.
+
+Consult the manufacturer of your device for more details, as how to do
+this varies by device.
+
@node Android File System
@section What files Emacs can access under Android
@cindex /assets directory, android
@@ -149,7 +159,7 @@ which is the app data directory (@pxref{Android File System}.)
directories, and the app data directories of other applications. In
recent versions of Android, the system also prohibits, for security
reasons, even Emacs itself from running executables inside the app
-data directory!
+data directory.
Emacs comes with several binaries. While being executable files,
they are packaged as libraries in the library directory, because
@@ -173,9 +183,28 @@ within itself.
Application processes are treated as disposable entities by the
system. When all Emacs frames move to the background, Emacs is liable
to be killed by the system at any time, for the purpose of saving
-resources. There is currently no easy way to bypass these
-restrictions, aside from keeping Emacs constantly running in the
-foreground.
+system resources.
+
+ On Android 7.1 and earlier, Emacs tells the system to treat it as a
+``background service''. The system will try to avoid killing Emacs
+unless the device is under memory stress.
+
+ Android 8.0 removed the ability for background services to receive
+such special treatment. However, Emacs applies a workaround: the
+system considers applications that create a permanent notification to
+be performing active work, and will avoid killing such applications.
+Thus, on those systems, Emacs displays a permanant notification for as
+long as it is running. Once the notification is displayed, it can be
+safely hidden through the system settings without resulting in Emacs
+being killed.
+
+ However, it is not guaranteed that the system will not kill Emacs,
+even if the notification is being displayed. While the Open Handset
+Alliance's sample implementation of Android behaves correctly, many
+manufacturers place additional restrictions on program execution in
+the background in their proprietary versions of Android. There is a
+list of such troublesome manufacturers and sometimes workarounds, at
+@url{https://dontkillmyapp.com/}.
@section Android permissions
@cindex external storage, android
@@ -272,14 +301,14 @@ maximized or full-screen, and only one window can be displayed at a
time. On larger devices, the system allows up to four windows to be
tiled on the screen at any time.
-Windows on Android do not continue to exist indefinitely after they
+ Windows on Android do not continue to exist indefinitely after they
are created. Instead, the system may choose to terminate windows that
are not on screen in order to save memory, with the assumption that
the program will save its contents to disk and restore them later,
when the user asks to open it again. As this is obvious not possible
with Emacs, Emacs separates a frame from a system window.
-Each system window created (including the initial window created
+ Each system window created (including the initial window created
during Emacs startup) is appended to a list of windows that do not
have associated frames. When a frame is created, Emacs looks up any
window within that list, and displays the contents of the frame
@@ -287,11 +316,28 @@ within; if there is no window at all, then one is created. Likewise,
when a new window is created by the system, Emacs places the contents
of any frame that is not already displayed within a window inside.
When a frame is closed, the corresponding system window is also
-closed.
+closed. Upon startup, the system creates a window itself (within
+which Emacs displays the first window system frame shortly
+thereafter.) Emacs differentiates between that window and windows
+created on behalf of other frames to determine what to do when the
+system window associated with a frame is closed:
-This strategy works as long as one window is in the foreground.
-Otherwise, Emacs can only run in the background for a limited amount
-of time before the process is killed completely.
+@itemize @bullet
+@item
+When the system closes the window created during application startup
+in order to save memory, Emacs retains the frame for when that window
+is created later.
+
+@item
+When the user closes the window created during application startup,
+and the window was not previously closed by the system in order to
+save resources, Emacs deletes any frame displayed within that window.
+
+@item
+When the user or the system closes any window created by Emacs on
+behalf of a specific frame, Emacs deletes the frame displayed within
+that window.
+@end itemize
@cindex windowing limitations, android
@cindex frame parameters, android
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index 9fd169cd5cc..0cb454e5294 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -1269,6 +1269,7 @@ Emacs and Android
Emacs and unconventional input devices
* Touchscreens:: Using Emacs on touchscreens.
+* On-Screen Keyboards:: Using Emacs with virtual keyboards.
Emacs and Microsoft Windows/MS-DOS
diff --git a/doc/emacs/input.texi b/doc/emacs/input.texi
index 0e029c401e3..1a58d1ca0ac 100644
--- a/doc/emacs/input.texi
+++ b/doc/emacs/input.texi
@@ -16,6 +16,7 @@ input devices, which is detailed here.
@menu
* Touchscreens:: Using Emacs on touchscreens.
+* On-Screen Keyboards:: Using Emacs with virtual keyboards.
@end menu
@node Touchscreens
@@ -58,3 +59,38 @@ were to be held down. @xref{Mouse Commands}.
By default, Emacs considers a tool as having been left on the
display for a while after 0.7 seconds, but this can be changed by
customizing the variable @code{touch-screen-delay}.
+
+@node On-Screen Keyboards
+@section Using Emacs with virtual keyboards
+@cindex virtual keyboards
+@cindex on-screen keyboards
+
+ When there is no physical keyboard attached to a system, the
+windowing system typically provides an on-screen keyboard, more often
+known as a ``virtual keyboard'', containing rows of clickable buttons
+that send keyboard input to the application, much like a real keyboard
+would. This virtual keyboard is hidden by default, as it uses up
+valuable on-screen real estate, and must be opened once the program
+being used is ready to accept keyboard input.
+
+ Under the X Window System, the client that provides the on-screen
+keyboard typically detects when the application is ready to accept
+keyboard input through a set of complex heuristics, and automatically
+displays the keyboard when necessary.
+
+ On other systems such as Android, Emacs must tell the system when it
+is ready to accept keyboard input. Typically, this is done in
+response to a touchscreen ``tap'' gesture (@pxref{Touchscreens}), or
+once to the minibuffer becomes in use (@pxref{Minibuffer}.)
+
+@vindex touch-screen-set-point-commands
+ When a ``tap'' gesture results in a command being executed, Emacs
+checks to see whether or not the command is supposed to set the point
+by looking for it in the list @code{touch-screen-set-point-commands}.
+If it is, then Emacs looks up whether or not the text under the point
+is read-only; if not, it activates the on-screen keyboard, assuming
+that the user is about to enter text in to the current buffer.
+
+ Emacs also provides a set of functions to show or hide the on-screen
+keyboard. For more details, @pxref{On-Screen Keyboards,,, elisp, The
+Emacs Lisp Reference Manual}.
diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 59367a2cecc..484c7dc2a06 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -2055,9 +2055,11 @@ The caller should not perform any action in that case.
@defun touch-screen-track-drag event update &optional data
This function is used to track a single ``drag'' gesture originating
-from the @code{touchscreen-begin} event @code{event}. Currently, it
-behaves identically to @code{touch-screen-track-tap}, but differences
-are anticipated in the future.
+from the @code{touchscreen-begin} event @code{event}.
+
+It behaves like @code{touch-screen-track-tap}, except that it returns
+@code{no-drag} if the touchpoint in @code{event} did not move far
+enough to qualify as an actual drag.
@end defun
@node Focus Events
diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi
index 2ceafab7a6b..497715bdb19 100644
--- a/doc/lispref/frames.texi
+++ b/doc/lispref/frames.texi
@@ -104,6 +104,7 @@ window of another Emacs frame. @xref{Child Frames}.
* Mouse Tracking:: Getting events that say when the mouse moves.
* Mouse Position:: Asking where the mouse is, or moving it.
* Pop-Up Menus:: Displaying a menu for the user to select from.
+* On-Screen Keyboards:: Displaying the virtual keyboard.
* Dialog Boxes:: Displaying a box to ask yes or no.
* Pointer Shape:: Specifying the shape of the mouse pointer.
* Window System Selections:: Transferring text to and from other X clients.
@@ -3812,6 +3813,26 @@ keymap. It won't be called if @code{x-popup-menu} returns for some
other reason without displaying a pop-up menu.
@end defvar
+@node On-Screen Keyboards
+@section On-Screen Keyboards
+
+ An on-screen keyboard is a special kind of pop up provided by the
+system, with rows of clickable buttons that act as a real keyboard.
+
+ On certain systems (@pxref{On-Screen Keyboards,,,emacs, The Emacs
+Manual}), Emacs is supposed to display and hide the on screen keyboard
+depending on whether or not the user is about to type something.
+
+@defun frame-toggle-on-screen-keyboard frame hide
+This function displays or hides the on-screen keyboard on behalf of
+the frame @var{frame}. If @var{hide} is non-@code{nil}, then the
+on-screen keyboard is hidden; otherwise, it is displayed.
+
+This has no effect if the system automatically detects when to display
+the on-screen keyboard, or when it does not provide any on-screen
+keyboard.
+@end defun
+
@node Dialog Boxes
@section Dialog Boxes
@cindex dialog boxes
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.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 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
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 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
}
}
}
+
+
+
+ /* 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 @@
public class EmacsSurfaceView extends SurfaceView
{
private static final String TAG = "EmacsSurfaceView";
-
public Object surfaceChangeLock;
private boolean created;
+ private EmacsView view;
+
+ /* This is the callback used on Android 8 to 25. */
+
+ 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, "surfaceCreated: " + view);
+ created = true;
+ }
+
+ /* Drop the lock when doing this, or a deadlock can
+ result. */
+ view.swapBuffers ();
+ }
+
+ @Override
+ public void
+ surfaceDestroyed (SurfaceHolder holder)
+ {
+ synchronized (surfaceChangeLock)
+ {
+ 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 ());
- surfaceChangeLock = new Object ();
+ this.surfaceChangeLock = new Object ();
+ this.view = view;
- getHolder ().addCallback (new SurfaceHolder.Callback () {
- @Override
- public void
- surfaceChanged (SurfaceHolder holder, int format,
- int width, int height)
- {
- Log.d (TAG, "surfaceChanged: " + view);
- view.swapBuffers ();
- }
-
- @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 ();
- }
-
- @Override
- public void
- surfaceDestroyed (SurfaceHolder holder)
- {
- synchronized (surfaceChangeLock)
- {
- Log.d (TAG, "surfaceDestroyed: " + view);
- created = false;
- }
- }
- });
+ 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 @@
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 @@
package org.gnu.emacs;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.view.ContextMenu;
@@ -27,6 +28,8 @@
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 @@ else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
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 @@ else if (child.getVisibility () != GONE)
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 @@ private class Coordinate
return attached;
}
- public void
+ public long
viewLayout (int left, int top, int right, int bottom)
{
int rectWidth, rectHeight;
@@ -249,10 +249,10 @@ private class Coordinate
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 @@ private class Coordinate
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 @@ else if (EmacsWindow.this.isMapped)
/* 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 interface WindowConsumer
}
public void
- removeWindowConsumer (WindowConsumer consumer)
+ removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
{
EmacsWindow window;
@@ -147,7 +147,7 @@ public interface WindowConsumer
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
- window.onActivityDetached ();
+ window.onActivityDetached (isFinishing);
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);
diff --git a/lisp/frame.el b/lisp/frame.el
index f21c0c369bd..f8ef17325ec 100644
--- a/lisp/frame.el
+++ b/lisp/frame.el
@@ -2553,6 +2553,23 @@ symbols."
((string= name "Virtual core keyboard")
'core-keyboard))))))
+
+;;;; On-screen keyboard management.
+
+(declare-function android-toggle-on-screen-keyboard "androidfns.c")
+
+(defun frame-toggle-on-screen-keyboard (frame hide)
+ "Display or hide the on-screen keyboard.
+On systems with an on-screen keyboard, display the on screen
+keyboard on behalf of the frame FRAME if HIDE is nil. Else, hide
+the on screen keyboard.
+
+FRAME must already have the input focus for this to work
+ reliably."
+ (let ((frame-type (framep-on-display frame)))
+ (cond ((eq frame-type 'android)
+ (android-toggle-on-screen-keyboard frame hide)))))
+
;;;; Frame geometry values
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 21d4607e7cf..3c42f29cd1f 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -4576,6 +4576,29 @@ is included in the return value."
default)))
": "))
+
+;;; On screen keyboard support.
+;; Try to display the on screen keyboard whenever entering the
+;; mini-buffer, and hide it whenever leaving.
+
+(defun minibuffer-setup-on-screen-keyboard ()
+ "Maybe display the on-screen keyboard in the current frame.
+Display the on-screen keyboard in the current frame if the
+last device to have sent an input event is not a keyboard.
+This is run upon minibuffer setup."
+ (when (not (memq (device-class last-event-frame
+ last-event-device)
+ '(keyboard core-keyboard)))
+ (frame-toggle-on-screen-keyboard (selected-frame) nil)))
+
+(defun minibuffer-exit-on-screen-keyboard ()
+ "Hide the on-screen keyboard if it was displayed.
+This is run upon minibuffer exit."
+ (frame-toggle-on-screen-keyboard (selected-frame) t))
+
+(add-hook 'minibuffer-setup-hook #'minibuffer-setup-on-screen-keyboard)
+(add-hook 'minibuffer-exit-hook #'minibuffer-exit-on-screen-keyboard)
+
(provide 'minibuffer)
;;; minibuffer.el ends here
diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el
index bcc4f5e9be3..a1c9e0b4afd 100644
--- a/lisp/touch-screen.el
+++ b/lisp/touch-screen.el
@@ -38,6 +38,12 @@ touch point, and the initial position of the touchpoint. See
`touch-screen-handle-point-update' for the meanings of the fourth
element.")
+(defvar touch-screen-set-point-commands '(mouse-set-point)
+ "List of commands known to set the point.
+This is used to determine whether or not to display the on-screen
+keyboard after a mouse command is executed in response to a
+`touchscreen-end' event.")
+
(defvar touch-screen-current-timer nil
"Timer used to track long-presses.
This is always cleared upon any significant state change.")
@@ -54,20 +60,28 @@ However, return the coordinates relative to WINDOW.
If (posn-window posn) is the same as window, simply return the
coordinates in POSN. Otherwise, convert them to the frame, and
-then back again."
- (if (eq (posn-window posn) window)
+then back again.
+
+If WINDOW is the symbol `frame', simply convert the coordinates
+to the frame that they belong in."
+ (if (or (eq (posn-window posn) window)
+ (and (eq window 'frame)
+ (framep (posn-window posn))))
(posn-x-y posn)
(let ((xy (posn-x-y posn))
- (edges (window-inside-pixel-edges window)))
+ (edges (and (windowp window)
+ (window-inside-pixel-edges window))))
;; Make the X and Y positions frame relative.
(when (windowp (posn-window posn))
(let ((edges (window-inside-pixel-edges
(posn-window posn))))
(setq xy (cons (+ (car xy) (car edges))
(+ (cdr xy) (cadr edges))))))
- ;; Make the X and Y positions window relative again.
- (cons (- (car xy) (car edges))
- (- (cdr xy) (cadr edges))))))
+ (if (eq window 'frame)
+ xy
+ ;; Make the X and Y positions window relative again.
+ (cons (- (car xy) (car edges))
+ (- (cdr xy) (cadr edges)))))))
(defun touch-screen-handle-scroll (dx dy)
"Scroll the display assuming that a touch point has moved by DX and DY."
@@ -252,7 +266,12 @@ If the fourth argument of `touch-screen-current-tool' is nil,
move point to the position of POINT, selecting the window under
POINT as well, and deactivate the mark; if there is a button or
link at POINT, call the command bound to `mouse-2' there.
-Otherwise, call the command bound to `mouse-1'."
+Otherwise, call the command bound to `mouse-1'.
+
+If the command being executed is listed in
+`touch-screen-set-point-commands' also display the on-screen
+keyboard if the current buffer and the character at the new point
+is not read-only."
(let ((what (nth 3 touch-screen-current-tool)))
(cond ((null what)
(when (windowp (posn-window (cdr point)))
@@ -283,9 +302,17 @@ Otherwise, call the command bound to `mouse-1'."
(deactivate-mark)
;; This is necessary for following links.
(goto-char (posn-point (cdr point)))
+ ;; Figure out if the on screen keyboard needs to be
+ ;; displayed.
(when command
(call-interactively command nil
- (vector event)))))))))
+ (vector event))
+ (when (memq command touch-screen-set-point-commands)
+ (if (not (or buffer-read-only
+ (get-text-property (point) 'read-only)))
+ (frame-toggle-on-screen-keyboard (selected-frame) nil)
+ ;; Otherwise, hide the on screen keyboard now.
+ (frame-toggle-on-screen-keyboard (selected-frame) t))))))))))
(defun touch-screen-handle-touch (event)
"Handle a single touch EVENT, and perform associated actions.
@@ -379,22 +406,34 @@ touch point with the same ID as in EVENT, call UPDATE with the
touch point in event and DATA.
Return nil immediately if any other kind of event is received;
-otherwise, return t once the `touchscreen-end' event arrives."
- (catch 'finish
- (while t
- (let ((new-event (read-event)))
- (cond
- ((eq (car-safe new-event) 'touchscreen-update)
- (let ((tool (assq (caadr event) (nth 1 new-event))))
- (when (and update tool)
- (funcall update new-event data))))
- ((eq (car-safe new-event) 'touchscreen-end)
- (throw 'finish
- ;; Now determine whether or not the `touchscreen-end'
- ;; event has the same ID as EVENT. If it doesn't,
- ;; then this is another touch, so return nil.
- (eq (caadr event) (caadr new-event))))
- (t (throw 'finish nil)))))))
+otherwise, return either t or `no-drag' once the
+`touchscreen-end' event arrives; return `no-drag' returned if the
+touch point in EVENT did not move significantly, and t otherwise."
+ (let ((return-value 'no-drag)
+ (start-xy (touch-screen-relative-xy (cdadr event)
+ 'frame)))
+ (catch 'finish
+ (while t
+ (let ((new-event (read-event)))
+ (cond
+ ((eq (car-safe new-event) 'touchscreen-update)
+ (when-let* ((tool (assq (caadr event) (nth 1 new-event)))
+ (xy (touch-screen-relative-xy (cdr tool) 'frame)))
+ (when (or (> (- (car xy) (car start-xy)) 5)
+ (< (- (car xy) (car start-xy)) -5)
+ (> (- (cdr xy) (cdr start-xy)) 5)
+ (< (- (cdr xy) (cdr start-xy)) -5))
+ (setq return-value t))
+ (when (and update tool)
+ (funcall update new-event data))))
+ ((eq (car-safe new-event) 'touchscreen-end)
+ (throw 'finish
+ ;; Now determine whether or not the `touchscreen-end'
+ ;; event has the same ID as EVENT. If it doesn't,
+ ;; then this is another touch, so return nil.
+ (and (eq (caadr event) (caadr new-event))
+ return-value)))
+ (t (throw 'finish nil))))))))
@@ -402,45 +441,8 @@ otherwise, return t once the `touchscreen-end' event arrives."
(defun touch-screen-drag-mode-line-1 (event)
"Internal helper for `touch-screen-drag-mode-line'.
-This is called when that function determines it need not execute
-any keymaps on the mode line at that particular spot."
- ;; Find the window that should be dragged and the starting position.
- (let* ((window (posn-window (cdadr event)))
- (relative-xy (touch-screen-relative-xy
- (cdadr event) window))
- (last-position (cdr relative-xy)))
- (when (window-resizable window 0)
- (touch-screen-track-drag
- event (lambda (new-event &optional _data)
- ;; Find the position of the touchpoint in NEW-EVENT.
- (let* ((touchpoint (assq (caadr event) (cadr new-event)))
- (new-relative-xy
- (touch-screen-relative-xy (cdr touchpoint)
- window))
- (position (cdr new-relative-xy))
- growth)
- ;; Now set the new height of the window.
- ;; If new-relative-y is above relative-xy, then
- ;; make the window that much shorter. Otherwise,
- ;; make it bigger.
- (unless (or (zerop (setq growth (- position last-position)))
- (and (> growth 0)
- (< position (+ (window-pixel-top window)
- (window-pixel-height window))))
- (and (< growth 0)
- (> position (+ (window-pixel-top window)
- (window-pixel-height window)))))
- (adjust-window-trailing-edge window growth nil t))
- (setq last-position position)))))))
-
-(defun touch-screen-drag-mode-line (event)
- "Begin dragging the mode line in response to a touch EVENT.
-If EVENT lies on top of text with a mouse command bound, run
-that command instead.
-
-Change the height of the window based on where the touch point
-in EVENT moves."
- (interactive "e")
+This is called when that function determines that no drag really
+happened. EVENT is the same as in `touch-screen-drag-mode-line'."
;; If there is an object at EVENT, then look either a keymap bound
;; to [down-mouse-1] or a command bound to [mouse-1]. Then, if a
;; keymap was found, pop it up as a menu. Otherwise, wait for a tap
@@ -457,21 +459,66 @@ in EVENT moves."
(keymap (lookup-key object-keymap [mode-line down-mouse-1]))
(command (or (lookup-key object-keymap [mode-line mouse-1])
keymap)))
- (if (or (keymapp keymap) command)
- (if (keymapp keymap)
- (when (touch-screen-track-tap event)
- (when-let* ((command (x-popup-menu event keymap))
- (tem (lookup-key keymap
- (if (consp command)
- (apply #'vector command)
- (vector command))
- t)))
- (call-interactively tem)))
- (when (and (commandp command)
- (touch-screen-track-tap event))
- (call-interactively command nil
- (vector (list 'mouse-1 (cdadr event))))))
- (touch-screen-drag-mode-line-1 event))))
+ (when (or (keymapp keymap) command)
+ (if (keymapp keymap)
+ (when-let* ((command (x-popup-menu event keymap))
+ (tem (lookup-key keymap
+ (if (consp command)
+ (apply #'vector command)
+ (vector command))
+ t)))
+ (call-interactively tem))
+ (when (commandp command)
+ (call-interactively command nil
+ (vector (list 'mouse-1 (cdadr event)))))))))
+
+(defun touch-screen-drag-mode-line (event)
+ "Begin dragging the mode line in response to a touch EVENT.
+Change the height of the window based on where the touch point in
+EVENT moves.
+
+If it does not actually move anywhere and the touch point is
+removed, and EVENT lies on top of text with a mouse command
+bound, run that command instead."
+ (interactive "e")
+ ;; Find the window that should be dragged and the starting position.
+ (let* ((window (posn-window (cdadr event)))
+ (relative-xy (touch-screen-relative-xy
+ (cdadr event) window))
+ (last-position (cdr relative-xy)))
+ (when (window-resizable window 0)
+ (when (eq
+ (touch-screen-track-drag
+ event (lambda (new-event &optional _data)
+ ;; Find the position of the touchpoint in
+ ;; NEW-EVENT.
+ (let* ((touchpoint (assq (caadr event)
+ (cadr new-event)))
+ (new-relative-xy
+ (touch-screen-relative-xy (cdr touchpoint)
+ window))
+ (position (cdr new-relative-xy))
+ growth)
+ ;; Now set the new height of the window. If
+ ;; new-relative-y is above relative-xy, then
+ ;; make the window that much shorter.
+ ;; Otherwise, make it bigger.
+ (unless (or (zerop (setq growth
+ (- position last-position)))
+ (and (> growth 0)
+ (< position
+ (+ (window-pixel-top window)
+ (window-pixel-height window))))
+ (and (< growth 0)
+ (> position
+ (+ (window-pixel-top window)
+ (window-pixel-height window)))))
+ (adjust-window-trailing-edge window growth nil t))
+ (setq last-position position))))
+ 'no-drag)
+ ;; Dragging did not actually happen, so try to run any command
+ ;; necessary.
+ (touch-screen-drag-mode-line-1 event)))))
(global-set-key [mode-line touchscreen-begin]
#'touch-screen-drag-mode-line)
diff --git a/src/android.c b/src/android.c
index eb9c404f1a3..43ac3b3f754 100644
--- a/src/android.c
+++ b/src/android.c
@@ -121,6 +121,8 @@ struct android_emacs_window
{
jclass class;
jmethodID swap_buffers;
+ jmethodID toggle_on_screen_keyboard;
+ jmethodID window_updated;
};
/* The asset manager being used. */
@@ -193,6 +195,10 @@ static struct android_emacs_drawable drawable_class;
/* Various methods associated with the EmacsWindow class. */
static struct android_emacs_window window_class;
+/* The last event serial used. This is a 32 bit value, but it is
+ stored in unsigned long to be consistent with X. */
+static unsigned int event_serial;
+
/* Event handling functions. Events are stored on a (circular) queue
@@ -1435,6 +1441,9 @@ android_init_emacs_window (void)
assert (window_class.c_name);
FIND_METHOD (swap_buffers, "swapBuffers", "()V");
+ FIND_METHOD (toggle_on_screen_keyboard,
+ "toggleOnScreenKeyboard", "(Z)V");
+ FIND_METHOD (window_updated, "windowUpdated", "(J)V");
#undef FIND_METHOD
}
@@ -1497,7 +1506,7 @@ NATIVE_NAME (emacsAbort) (JNIEnv *env, jobject object)
emacs_abort ();
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint x, jint y, jint width,
@@ -1506,6 +1515,7 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xconfigure.type = ANDROID_CONFIGURE_NOTIFY;
+ event.xconfigure.serial = ++event_serial;
event.xconfigure.window = window;
event.xconfigure.time = time;
event.xconfigure.x = x;
@@ -1514,9 +1524,10 @@ NATIVE_NAME (sendConfigureNotify) (JNIEnv *env, jobject object,
event.xconfigure.height = height;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
@@ -1525,6 +1536,7 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
union android_event event;
event.xkey.type = ANDROID_KEY_PRESS;
+ event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
@@ -1532,9 +1544,10 @@ NATIVE_NAME (sendKeyPress) (JNIEnv *env, jobject object,
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
jshort window, jlong time,
jint state, jint keycode,
@@ -1543,6 +1556,7 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
union android_event event;
event.xkey.type = ANDROID_KEY_RELEASE;
+ event.xkey.serial = ++event_serial;
event.xkey.window = window;
event.xkey.time = time;
event.xkey.state = state;
@@ -1550,48 +1564,55 @@ NATIVE_NAME (sendKeyRelease) (JNIEnv *env, jobject object,
event.xkey.unicode_char = unicode_char;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusIn) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
union android_event event;
- event.xkey.type = ANDROID_FOCUS_IN;
- event.xkey.window = window;
- event.xkey.time = time;
+ event.xfocus.type = ANDROID_FOCUS_IN;
+ event.xfocus.serial = ++event_serial;
+ event.xfocus.window = window;
+ event.xfocus.time = time;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendFocusOut) (JNIEnv *env, jobject object,
jshort window, jlong time)
{
union android_event event;
- event.xkey.type = ANDROID_FOCUS_OUT;
- event.xkey.window = window;
- event.xkey.time = time;
+ event.xfocus.type = ANDROID_FOCUS_OUT;
+ event.xfocus.serial = ++event_serial;
+ event.xfocus.window = window;
+ event.xfocus.time = time;
android_write_event (&event);
+ return ++event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWindowAction) (JNIEnv *env, jobject object,
jshort window, jint action)
{
union android_event event;
event.xaction.type = ANDROID_WINDOW_ACTION;
+ event.xaction.serial = ++event_serial;
event.xaction.window = window;
event.xaction.action = action;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@@ -1599,15 +1620,17 @@ NATIVE_NAME (sendEnterNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xcrossing.type = ANDROID_ENTER_NOTIFY;
+ event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@@ -1615,15 +1638,17 @@ NATIVE_NAME (sendLeaveNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xcrossing.type = ANDROID_LEAVE_NOTIFY;
+ event.xcrossing.serial = ++event_serial;
event.xcrossing.window = window;
event.xcrossing.x = x;
event.xcrossing.y = y;
event.xcrossing.time = time;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time)
@@ -1631,15 +1656,17 @@ NATIVE_NAME (sendMotionNotify) (JNIEnv *env, jobject object,
union android_event event;
event.xmotion.type = ANDROID_MOTION_NOTIFY;
+ event.xmotion.serial = ++event_serial;
event.xmotion.window = window;
event.xmotion.x = x;
event.xmotion.y = y;
event.xmotion.time = time;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@@ -1648,6 +1675,7 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
union android_event event;
event.xbutton.type = ANDROID_BUTTON_PRESS;
+ event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
@@ -1656,9 +1684,10 @@ NATIVE_NAME (sendButtonPress) (JNIEnv *env, jobject object,
event.xbutton.button = button;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@@ -1667,6 +1696,7 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
union android_event event;
event.xbutton.type = ANDROID_BUTTON_RELEASE;
+ event.xbutton.serial = ++event_serial;
event.xbutton.window = window;
event.xbutton.x = x;
event.xbutton.y = y;
@@ -1675,9 +1705,10 @@ NATIVE_NAME (sendButtonRelease) (JNIEnv *env, jobject object,
event.xbutton.button = button;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@@ -1685,6 +1716,7 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_DOWN;
+ event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@@ -1692,9 +1724,10 @@ NATIVE_NAME (sendTouchDown) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@@ -1702,6 +1735,7 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_UP;
+ event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@@ -1709,9 +1743,10 @@ NATIVE_NAME (sendTouchUp) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint pointer_id)
@@ -1719,6 +1754,7 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
union android_event event;
event.touch.type = ANDROID_TOUCH_MOVE;
+ event.touch.serial = ++event_serial;
event.touch.window = window;
event.touch.x = x;
event.touch.y = y;
@@ -1726,9 +1762,10 @@ NATIVE_NAME (sendTouchMove) (JNIEnv *env, jobject object,
event.touch.pointer_id = pointer_id;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
jshort window, jint x, jint y,
jlong time, jint state,
@@ -1737,6 +1774,7 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
union android_event event;
event.wheel.type = ANDROID_WHEEL;
+ event.wheel.serial = ++event_serial;
event.wheel.window = window;
event.wheel.x = x;
event.wheel.y = y;
@@ -1746,43 +1784,50 @@ NATIVE_NAME (sendWheel) (JNIEnv *env, jobject object,
event.wheel.y_delta = y_delta;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object,
jshort window)
{
union android_event event;
event.iconified.type = ANDROID_ICONIFIED;
+ event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object,
jshort window)
{
union android_event event;
event.iconified.type = ANDROID_DEICONIFIED;
+ event.iconified.serial = ++event_serial;
event.iconified.window = window;
android_write_event (&event);
+ return event_serial;
}
-extern JNIEXPORT void JNICALL
+extern JNIEXPORT jlong JNICALL
NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object,
jshort window, jint menu_event_id)
{
union android_event event;
event.menu.type = ANDROID_CONTEXT_MENU;
+ event.menu.serial = ++event_serial;
event.menu.window = window;
event.menu.menu_event_id = menu_event_id;
android_write_event (&event);
+ return event_serial;
}
#ifdef __clang__
@@ -3599,6 +3644,15 @@ android_translate_coordinates (android_window src, int x,
ANDROID_DELETE_LOCAL_REF (coordinates);
}
+void
+android_sync (void)
+{
+ (*android_java_env)->CallVoidMethod (android_java_env,
+ emacs_service,
+ service_class.sync);
+ android_exception_check ();
+}
+
/* Low level drawing primitives. */
@@ -3795,12 +3849,45 @@ android_get_keysym_name (int keysym, char *name_return, size_t size)
ANDROID_DELETE_LOCAL_REF (string);
}
+/* Display the on screen keyboard on window WINDOW, or hide it if SHOW
+ is false. Ask the system to bring up or hide the on-screen
+ keyboard on behalf of WINDOW. The request may be rejected by the
+ system, especially when the window does not have the input
+ focus. */
+
void
-android_sync (void)
+android_toggle_on_screen_keyboard (android_window window, bool show)
{
- (*android_java_env)->CallVoidMethod (android_java_env,
- emacs_service,
- service_class.sync);
+ jobject object;
+ jmethodID method;
+
+ object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+ method = window_class.toggle_on_screen_keyboard;
+
+ /* Now display the on screen keyboard. */
+ (*android_java_env)->CallVoidMethod (android_java_env, object,
+ method, (jboolean) show);
+
+ /* Check for out of memory errors. */
+ android_exception_check ();
+}
+
+/* Tell the window system that all configure events sent to WINDOW
+ have been fully processed, and that it is now okay to display its
+ new contents. SERIAL is the serial of the last configure event
+ processed. */
+
+void
+android_window_updated (android_window window, unsigned long serial)
+{
+ jobject object;
+ jmethodID method;
+
+ object = android_resolve_handle (window, ANDROID_HANDLE_WINDOW);
+ method = window_class.window_updated;
+
+ (*android_java_env)->CallVoidMethod (android_java_env, object,
+ method, (jlong) serial);
android_exception_check ();
}
@@ -3811,7 +3898,7 @@ android_sync (void)
#undef faccessat
/* Replace the system faccessat with one which understands AT_EACCESS.
- Android's faccessat simply fails upon using AT_EACCESS, so repalce
+ Android's faccessat simply fails upon using AT_EACCESS, so replace
it with zero here. This isn't caught during configuration.
This replacement is only done when building for Android 17 or
@@ -3829,7 +3916,7 @@ faccessat (int dirfd, const char *pathname, int mode, int flags)
return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
}
-#endif /* __ANDROID_API__ < 16 */
+#endif /* __ANDROID_API__ >= 17 */
diff --git a/src/android.h b/src/android.h
index 240bc90d831..97818ab4911 100644
--- a/src/android.h
+++ b/src/android.h
@@ -91,6 +91,8 @@ extern void android_exception_check (void);
extern void android_get_keysym_name (int, char *, size_t);
extern void android_wait_event (void);
+extern void android_toggle_on_screen_keyboard (android_window, bool);
+extern void android_window_updated (android_window, unsigned long);
diff --git a/src/androidfns.c b/src/androidfns.c
index bb37c415069..77ee2e8de44 100644
--- a/src/androidfns.c
+++ b/src/androidfns.c
@@ -2332,6 +2332,30 @@ there is no mouse. */)
#endif
}
+DEFUN ("android-toggle-on-screen-keyboard",
+ Fandroid_toggle_on_screen_keyboard,
+ Sandroid_toggle_on_screen_keyboard, 2, 2, 0,
+ doc: /* Display or hide the on-screen keyboard.
+If HIDE is non-nil, hide the on screen keyboard if it is currently
+being displayed. Else, request that the system display it on behalf
+of FRAME. This request may be rejected if FRAME does not have the
+input focus. */)
+ (Lisp_Object frame, Lisp_Object hide)
+{
+#ifndef ANDROID_STUBIFY
+ struct frame *f;
+
+ f = decode_window_system_frame (frame);
+
+ block_input ();
+ android_toggle_on_screen_keyboard (FRAME_ANDROID_WINDOW (f),
+ NILP (hide));
+ unblock_input ();
+#endif
+
+ return Qnil;
+}
+
#ifndef ANDROID_STUBIFY
@@ -2781,6 +2805,7 @@ syms_of_androidfns (void)
defsubr (&Sx_show_tip);
defsubr (&Sx_hide_tip);
defsubr (&Sandroid_detect_mouse);
+ defsubr (&Sandroid_toggle_on_screen_keyboard);
#ifndef ANDROID_STUBIFY
tip_timer = Qnil;
diff --git a/src/androidgui.h b/src/androidgui.h
index 1f28c18ff34..3b9a74dc0b0 100644
--- a/src/androidgui.h
+++ b/src/androidgui.h
@@ -239,6 +239,7 @@ enum android_event_type
struct android_any_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
};
@@ -252,6 +253,7 @@ enum android_modifier_mask
struct android_key_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
unsigned int state;
@@ -271,6 +273,7 @@ struct android_key_event
struct android_configure_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
int x, y;
@@ -280,6 +283,7 @@ struct android_configure_event
struct android_focus_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
};
@@ -287,6 +291,7 @@ struct android_focus_event
struct android_window_action_event
{
enum android_event_type type;
+ unsigned long serial;
/* The window handle. This can be ANDROID_NONE. */
android_window window;
@@ -301,6 +306,7 @@ struct android_window_action_event
struct android_crossing_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@@ -309,6 +315,7 @@ struct android_crossing_event
struct android_motion_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@@ -317,6 +324,7 @@ struct android_motion_event
struct android_button_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
@@ -329,6 +337,9 @@ struct android_touch_event
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
@@ -347,6 +358,9 @@ struct android_wheel_event
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
@@ -368,6 +382,9 @@ struct android_iconify_event
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
};
@@ -377,6 +394,9 @@ struct android_menu_event
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. Always None. */
android_window window;
diff --git a/src/androidterm.c b/src/androidterm.c
index 3c16b542d91..3024890d789 100644
--- a/src/androidterm.c
+++ b/src/androidterm.c
@@ -591,7 +591,17 @@ handle_one_android_event (struct android_display_info *dpyinfo,
android_clear_under_internal_border (f);
SET_FRAME_GARBAGED (f);
cancel_mouse_face (f);
+
+ /* Now stash the serial of this configure event somewhere,
+ and call android_window_updated with it once the redraw
+ completes. */
+ FRAME_OUTPUT_DATA (f)->last_configure_serial
+ = configureEvent.xconfigure.serial;
}
+ else
+ /* Reply to this ConfigureNotify event immediately. */
+ android_window_updated (FRAME_ANDROID_WINDOW (f),
+ configureEvent.xconfigure.serial);
goto OTHER;
@@ -1194,6 +1204,9 @@ handle_one_android_event (struct android_display_info *dpyinfo,
/* Iconification. This is vastly simpler than on X. */
case ANDROID_ICONIFIED:
+ if (!any)
+ goto OTHER;
+
if (FRAME_ICONIFIED_P (any))
goto OTHER;
@@ -1206,6 +1219,9 @@ handle_one_android_event (struct android_display_info *dpyinfo,
case ANDROID_DEICONIFIED:
+ if (!any)
+ goto OTHER;
+
if (!FRAME_ICONIFIED_P (any))
goto OTHER;
@@ -1311,6 +1327,14 @@ android_frame_up_to_date (struct frame *f)
/* The frame is now complete, as its contents have been drawn. */
FRAME_ANDROID_COMPLETE_P (f) = true;
+ /* If there was an outstanding configure event, then tell system
+ that the update has finished and the new contents can now be
+ displayed. */
+ if (FRAME_OUTPUT_DATA (f)->last_configure_serial)
+ android_window_updated (FRAME_ANDROID_WINDOW (f),
+ FRAME_OUTPUT_DATA (f)->last_configure_serial);
+ FRAME_OUTPUT_DATA (f)->last_configure_serial = 0;
+
/* Shrink the scanline buffer used by the font backend. */
sfntfont_android_shrink_scanline_buffer ();
unblock_input ();
diff --git a/src/androidterm.h b/src/androidterm.h
index c0f862e35fb..11b24d40b73 100644
--- a/src/androidterm.h
+++ b/src/androidterm.h
@@ -241,6 +241,11 @@ struct android_output
/* List of all tools (either styluses or fingers) pressed onto the
frame. */
struct android_touch_point *touch_points;
+
+ /* Event serial of the last ConfigureNotify event received that has
+ not yet been drawn. This is used to synchronize resize with the
+ window system. 0 if no such outstanding event exists. */
+ unsigned long last_configure_serial;
};
enum