* .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.
# Built by recursive call to `configure'.
*.android
!INSTALL.android
+!verbose.mk.android
# Built by `java'.
java/install_temp/*
${srcdir}/glossary.texi \
${srcdir}/ack.texi \
${srcdir}/kmacro.texi \
+ ${srcdir}/android.texi \
+ ${srcdir}/input.texi \
$(EMACS_XTRA)
## Disable implicit rules.
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.
$ 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
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
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
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
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:
+
+@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.
-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.
+@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
Emacs and unconventional input devices
* Touchscreens:: Using Emacs on touchscreens.
+* On-Screen Keyboards:: Using Emacs with virtual keyboards.
Emacs and Microsoft Windows/MS-DOS
@menu
* Touchscreens:: Using Emacs on touchscreens.
+* On-Screen Keyboards:: Using Emacs with virtual keyboards.
@end menu
@node Touchscreens
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}.
@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
* 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.
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
android:theme="@android:style/Theme"
android:debuggable="true"
android:extractNativeLibs="true">
+
<activity android:name="org.gnu.emacs.EmacsActivity"
android:launchMode="singleTop"
+ android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</activity>
<activity android:name="org.gnu.emacs.EmacsMultitaskActivity"
+ android:windowSoftInputMode="adjustResize"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"/>
+ <activity android:autoRemoveFromRecents="true"
+ android:label="Emacs options"
+ android:name=".EmacsPreferencesActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.APPLICATION_PREFERENCES" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<service android:name="org.gnu.emacs.EmacsService"
android:directBootAware="false"
android:enabled="true"
onCreate (Bundle savedInstanceState)
{
FrameLayout.LayoutParams params;
+ Intent intent;
+
+ /* See if Emacs should be started with -Q. */
+ intent = getIntent ();
+ EmacsService.needDashQ
+ = intent.getBooleanExtra ("org.gnu.emacs.START_DASH_Q",
+ false);
/* Set the theme to one without a title bar. */
/* Set it as the content view. */
setContentView (layout);
- if (EmacsService.SERVICE == null)
- /* Start the Emacs service now. */
- startService (new Intent (this, EmacsService.class));
+ /* Maybe start the Emacs service if necessary. */
+ EmacsService.startEmacsService (this);
/* Add this activity to the list of available activities. */
EmacsWindowAttachmentManager.MANAGER.registerWindowConsumer (this);
public void
onDestroy ()
{
+ EmacsWindowAttachmentManager manager;
+ boolean isMultitask;
+
+ manager = EmacsWindowAttachmentManager.MANAGER;
+
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
Log.d (TAG, "onDestroy " + this);
- EmacsWindowAttachmentManager.MANAGER.removeWindowConsumer (this);
+ isMultitask = this instanceof EmacsMultitaskActivity;
+ manager.removeWindowConsumer (this, isMultitask || isFinishing ());
focusedActivities.remove (this);
invalidateFocus ();
super.onDestroy ();
support doing so, create the submenu and add the
contents of the menu to it. */
submenu = menu.addSubMenu (item.itemName);
+ item.subMenu.inflateMenuItems (submenu);
}
catch (UnsupportedOperationException exception)
{
/* Abort and generate a native core dump. */
public static native void emacsAbort ();
- /* Send an ANDROID_CONFIGURE_NOTIFY event. */
- public static native void sendConfigureNotify (short window, long time,
+ /* Send an ANDROID_CONFIGURE_NOTIFY event. The values of all the
+ functions below are the serials of the events sent. */
+ public static native long sendConfigureNotify (short window, long time,
int x, int y, int width,
int height);
/* Send an ANDROID_KEY_PRESS event. */
- public static native void sendKeyPress (short window, long time, int state,
+ public static native long sendKeyPress (short window, long time, int state,
int keyCode, int unicodeChar);
/* Send an ANDROID_KEY_RELEASE event. */
- public static native void sendKeyRelease (short window, long time, int state,
+ public static native long sendKeyRelease (short window, long time, int state,
int keyCode, int unicodeChar);
/* Send an ANDROID_FOCUS_IN event. */
- public static native void sendFocusIn (short window, long time);
+ public static native long sendFocusIn (short window, long time);
/* Send an ANDROID_FOCUS_OUT event. */
- public static native void sendFocusOut (short window, long time);
+ public static native long sendFocusOut (short window, long time);
/* Send an ANDROID_WINDOW_ACTION event. */
- public static native void sendWindowAction (short window, int action);
+ public static native long sendWindowAction (short window, int action);
/* Send an ANDROID_ENTER_NOTIFY event. */
- public static native void sendEnterNotify (short window, int x, int y,
+ public static native long sendEnterNotify (short window, int x, int y,
long time);
/* Send an ANDROID_LEAVE_NOTIFY event. */
- public static native void sendLeaveNotify (short window, int x, int y,
+ public static native long sendLeaveNotify (short window, int x, int y,
long time);
/* Send an ANDROID_MOTION_NOTIFY event. */
- public static native void sendMotionNotify (short window, int x, int y,
+ public static native long sendMotionNotify (short window, int x, int y,
long time);
/* Send an ANDROID_BUTTON_PRESS event. */
- public static native void sendButtonPress (short window, int x, int y,
+ public static native long sendButtonPress (short window, int x, int y,
long time, int state,
int button);
/* Send an ANDROID_BUTTON_RELEASE event. */
- public static native void sendButtonRelease (short window, int x, int y,
+ public static native long sendButtonRelease (short window, int x, int y,
long time, int state,
int button);
/* Send an ANDROID_TOUCH_DOWN event. */
- public static native void sendTouchDown (short window, int x, int y,
+ public static native long sendTouchDown (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_TOUCH_UP event. */
- public static native void sendTouchUp (short window, int x, int y,
+ public static native long sendTouchUp (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_TOUCH_MOVE event. */
- public static native void sendTouchMove (short window, int x, int y,
+ public static native long sendTouchMove (short window, int x, int y,
long time, int pointerID);
/* Send an ANDROID_WHEEL event. */
- public static native void sendWheel (short window, int x, int y,
+ public static native long sendWheel (short window, int x, int y,
long time, int state,
float xDelta, float yDelta);
/* Send an ANDROID_ICONIFIED event. */
- public static native void sendIconified (short window);
+ public static native long sendIconified (short window);
/* Send an ANDROID_DEICONIFIED event. */
- public static native void sendDeiconified (short window);
+ public static native long sendDeiconified (short window);
/* Send an ANDROID_CONTEXT_MENU event. */
- public static native void sendContextMenu (short window, int menuEventID);
+ public static native long sendContextMenu (short window, int menuEventID);
static
{
--- /dev/null
+/* 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.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);
+ }
+};
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;
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;
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;
}
this);
/* Start the thread that runs Emacs. */
- thread = new EmacsThread (this);
+ thread = new EmacsThread (this, needDashQ);
thread.start ();
}
catch (IOException exception)
}
}
}
+
+ \f
+
+ /* 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));
+ }
+ }
};
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
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
{
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);
package org.gnu.emacs;
+import android.content.Context;
import android.content.res.ColorStateList;
import android.view.ContextMenu;
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;
/* 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 ();
/* 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
if (changed || mustReportLayout)
{
mustReportLayout = false;
- window.viewLayout (left, top, right, bottom);
+ pendingConfigure
+ = window.viewLayout (left, top, right, bottom);
}
measuredWidth = right - left;
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;
+ }
+ }
};
return attached;
}
- public void
+ public long
viewLayout (int left, int top, int right, int bottom)
{
int rectWidth, rectHeight;
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
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
/* 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);
+ }
+ });
+ }
};
}
public void
- removeWindowConsumer (WindowConsumer consumer)
+ removeWindowConsumer (WindowConsumer consumer, boolean isFinishing)
{
EmacsWindow window;
Log.d (TAG, "removeWindowConsumer: detaching " + window);
consumer.detachWindow ();
- window.onActivityDetached ();
+ window.onActivityDetached (isFinishing);
}
Log.d (TAG, "removeWindowConsumer: removing " + consumer);
((string= name "Virtual core keyboard")
'core-keyboard))))))
+\f
+;;;; 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)))))
+
\f
;;;; Frame geometry values
default)))
": "))
+\f
+;;; 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
`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.")
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."
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)))
(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.
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))))))))
\f
(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
(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)
{
jclass class;
jmethodID swap_buffers;
+ jmethodID toggle_on_screen_keyboard;
+ jmethodID window_updated;
};
/* The asset manager being used. */
/* 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;
+
\f
/* Event handling functions. Events are stored on a (circular) queue
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
}
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,
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;
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,
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;
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,
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;
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)
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)
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)
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,
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;
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,
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;
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)
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;
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)
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;
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)
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;
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,
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;
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__
ANDROID_DELETE_LOCAL_REF (coordinates);
}
+void
+android_sync (void)
+{
+ (*android_java_env)->CallVoidMethod (android_java_env,
+ emacs_service,
+ service_class.sync);
+ android_exception_check ();
+}
+
\f
/* Low level drawing primitives. */
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 ();
}
#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
return real_faccessat (dirfd, pathname, mode, flags & ~AT_EACCESS);
}
-#endif /* __ANDROID_API__ < 16 */
+#endif /* __ANDROID_API__ >= 17 */
\f
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);
\f
#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;
+}
+
\f
#ifndef ANDROID_STUBIFY
defsubr (&Sx_show_tip);
defsubr (&Sx_hide_tip);
defsubr (&Sandroid_detect_mouse);
+ defsubr (&Sandroid_toggle_on_screen_keyboard);
#ifndef ANDROID_STUBIFY
tip_timer = Qnil;
struct android_any_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
};
struct android_key_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
unsigned int state;
struct android_configure_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
int x, y;
struct android_focus_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
android_time time;
};
struct android_window_action_event
{
enum android_event_type type;
+ unsigned long serial;
/* The window handle. This can be ANDROID_NONE. */
android_window window;
struct android_crossing_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
struct android_motion_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
struct android_button_event
{
enum android_event_type type;
+ unsigned long serial;
android_window window;
int x, y;
unsigned long time;
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
/* Type of the event. */
enum android_event_type type;
+ /* Serial identifying the event. */
+ unsigned long serial;
+
/* Window associated with the event. */
android_window window;
};
/* 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;
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;
/* Iconification. This is vastly simpler than on X. */
case ANDROID_ICONIFIED:
+ if (!any)
+ goto OTHER;
+
if (FRAME_ICONIFIED_P (any))
goto OTHER;
case ANDROID_DEICONIFIED:
+ if (!any)
+ goto OTHER;
+
if (!FRAME_ICONIFIED_P (any))
goto OTHER;
/* 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 ();
/* 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