1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00
emacs/java/org/gnu/emacs/EmacsService.java
Po Lu cf24b61985 Update Android port
* doc/emacs/input.texi (On-Screen Keyboards):
* doc/lispref/commands.texi (Misc Events): Improve documentation
of text conversion stuff.
* java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit)
(endBatchEdit, commitCompletion, commitText, deleteSurroundingText)
(finishComposingText, getSelectedText, getTextAfterCursor)
(EmacsInputConnection, setComposingRegion, performEditorAction)
(getExtractedText): Condition debug code on DEBUG_IC.
* java/org/gnu/emacs/EmacsService.java (EmacsService, updateIC):
Likewise.
* lisp/bindings.el (global-map):
* lisp/electric.el (global-map): Make `text-conversion'
`analyze-text-conversion'.
* lisp/progmodes/prog-mode.el (prog-mode): Enable text
conversion in input methods.
* lisp/simple.el (analyze-text-conversion): New function.
* lisp/textmodes/text-mode.el (text-conversion-style)
(text-mode): Likewise.
* src/androidterm.c (android_handle_ime_event): Handle
set_point_and_mark.
(android_sync_edit): Give Emacs 100 ms instead.
(android_perform_conversion_query): Skip the active region, not
the conversion region.
(getSelectedText): Implement properly.
(android_update_selection): Expose mark to input methods.
(android_reset_conversion): Handle `text-conversion-style'.
* src/buffer.c (init_buffer_once, syms_of_buffer): Add buffer
local variable `text-conversion-style'.
* src/buffer.h (struct buffer, bset_text_conversion_style): New
fields.
* src/emacs.c (android_emacs_init): Call syms_of_textconv.
* src/frame.h (enum text_conversion_operation): Rename
TEXTCONV_SET_POINT.
* src/lisp.h: Export syms_of_textconv.

* src/marker.c (set_marker_internal): Force redisplay when the
mark is set and the buffer is visible on builds that use text
conversion.  Explain why.

* src/textconv.c (copy_buffer): Fix copying past gap.
(get_mark): New function.
(textconv_query): Implement new flag.
(sync_overlay): New function.  Display conversion text in an
overlay.
(record_buffer_change, really_commit_text)
(really_set_composing_text, really_set_composing_region)
(really_delete_surrounding_text, really_set_point)
(handle_pending_conversion_events_1, decrement_inside)
(handle_pending_conversion_events, textconv_set_point)
(get_extracted_text, register_textconv_interface): Various fixes
and improvements.

* src/textconv.h (struct textconv_interface): Update
documentation.
* src/window.h (GCALIGNED_STRUCT): New field `prev_mark'.
* src/xdisp.c (mark_window_display_accurate_1): Handle
prev_mark.
2023-02-15 22:51:44 +08:00

638 lines
14 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import android.graphics.Canvas;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.view.View;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import android.os.IBinder;
import android.os.Handler;
import android.os.Vibrator;
import android.os.VibratorManager;
import android.os.VibrationEffect;
import android.util.Log;
import android.util.DisplayMetrics;
import android.hardware.input.InputManager;
class Holder<T>
{
T thing;
};
/* EmacsService is the service that starts the thread running Emacs
and handles requests by that Emacs instance. */
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;
/* Keep this in synch with androidgui.h. */
public static final int IC_MODE_NULL = 0;
public static final int IC_MODE_ACTION = 1;
public static final int IC_MODE_TEXT = 2;
/* Display metrics used by font backends. */
public DisplayMetrics metrics;
public static final boolean DEBUG_IC = false;
@Override
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;
}
@Override
public IBinder
onBind (Intent intent)
{
return null;
}
@SuppressWarnings ("deprecation")
private String
getApkFile ()
{
PackageManager manager;
ApplicationInfo info;
manager = getPackageManager ();
try
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
info = manager.getApplicationInfo ("org.gnu.emacs", 0);
else
info = manager.getApplicationInfo ("org.gnu.emacs",
ApplicationInfoFlags.of (0));
/* Return an empty string upon failure. */
if (info.sourceDir != null)
return info.sourceDir;
return "";
}
catch (Exception e)
{
return "";
}
}
private String
getLibraryDirectory ()
{
int apiLevel;
Context context;
context = getApplicationContext ();
apiLevel = Build.VERSION.SDK_INT;
if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
return context.getApplicationInfo().nativeLibraryDir;
else if (apiLevel >= Build.VERSION_CODES.DONUT)
return context.getApplicationInfo().dataDir + "/lib";
return "/data/data/" + context.getPackageName() + "/lib";
}
@Override
public void
onCreate ()
{
AssetManager manager;
Context app_context;
String filesDir, libDir, cacheDir, classPath;
double pixelDensityX;
double pixelDensityY;
SERVICE = this;
handler = new Handler (Looper.getMainLooper ());
manager = getAssets ();
app_context = getApplicationContext ();
metrics = getResources ().getDisplayMetrics ();
pixelDensityX = metrics.xdpi;
pixelDensityY = metrics.ydpi;
try
{
/* Configure Emacs with the asset manager and other necessary
parameters. */
filesDir = app_context.getFilesDir ().getCanonicalPath ();
libDir = getLibraryDirectory ();
cacheDir = app_context.getCacheDir ().getCanonicalPath ();
/* Now provide this application's apk file, so a recursive
invocation of app_process (through android-emacs) can
find EmacsNoninteractive. */
classPath = getApkFile ();
Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+ ", libDir = " + libDir + ", and classPath = " + classPath);
EmacsNative.setEmacsParams (manager, filesDir, libDir,
cacheDir, (float) pixelDensityX,
(float) pixelDensityY,
classPath, this);
/* Start the thread that runs Emacs. */
thread = new EmacsThread (this, needDashQ);
thread.start ();
}
catch (IOException exception)
{
EmacsNative.emacsAbort ();
return;
}
}
/* Functions from here on must only be called from the Emacs
thread. */
public void
runOnUiThread (Runnable runnable)
{
handler.post (runnable);
}
public EmacsView
getEmacsView (final EmacsWindow window, final int visibility,
final boolean isFocusedByDefault)
{
Runnable runnable;
final Holder<EmacsView> view;
view = new Holder<EmacsView> ();
runnable = new Runnable () {
public void
run ()
{
synchronized (this)
{
view.thing = new EmacsView (window);
view.thing.setVisibility (visibility);
/* The following function is only present on Android 26
or later. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
view.thing.setFocusedByDefault (isFocusedByDefault);
notify ();
}
}
};
syncRunnable (runnable);
return view.thing;
}
public void
getLocationOnScreen (final EmacsView view, final int[] coordinates)
{
Runnable runnable;
runnable = new Runnable () {
public void
run ()
{
synchronized (this)
{
view.getLocationOnScreen (coordinates);
notify ();
}
}
};
syncRunnable (runnable);
}
public void
fillRectangle (EmacsDrawable drawable, EmacsGC gc,
int x, int y, int width, int height)
{
EmacsFillRectangle.perform (drawable, gc, x, y,
width, height);
}
public void
fillPolygon (EmacsDrawable drawable, EmacsGC gc,
Point points[])
{
EmacsFillPolygon.perform (drawable, gc, points);
}
public void
drawRectangle (EmacsDrawable drawable, EmacsGC gc,
int x, int y, int width, int height)
{
EmacsDrawRectangle.perform (drawable, gc, x, y,
width, height);
}
public void
drawLine (EmacsDrawable drawable, EmacsGC gc,
int x, int y, int x2, int y2)
{
EmacsDrawLine.perform (drawable, gc, x, y,
x2, y2);
}
public void
drawPoint (EmacsDrawable drawable, EmacsGC gc,
int x, int y)
{
EmacsDrawPoint.perform (drawable, gc, x, y);
}
public void
copyArea (EmacsDrawable srcDrawable, EmacsDrawable dstDrawable,
EmacsGC gc,
int srcX, int srcY, int width, int height, int destX,
int destY)
{
EmacsCopyArea.perform (srcDrawable, gc, dstDrawable,
srcX, srcY, width, height, destX,
destY);
}
public void
clearWindow (EmacsWindow window)
{
window.clearWindow ();
}
public void
clearArea (EmacsWindow window, int x, int y, int width,
int height)
{
window.clearArea (x, y, width, height);
}
@SuppressWarnings ("deprecation")
public void
ringBell ()
{
Vibrator vibrator;
VibrationEffect effect;
VibratorManager vibratorManager;
Object tem;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
{
tem = getSystemService (Context.VIBRATOR_MANAGER_SERVICE);
vibratorManager = (VibratorManager) tem;
vibrator = vibratorManager.getDefaultVibrator ();
}
else
vibrator
= (Vibrator) getSystemService (Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
effect
= VibrationEffect.createOneShot (50,
VibrationEffect.DEFAULT_AMPLITUDE);
vibrator.vibrate (effect);
}
else
vibrator.vibrate (50);
}
public short[]
queryTree (EmacsWindow window)
{
short[] array;
List<EmacsWindow> windowList;
int i;
if (window == null)
/* Just return all the windows without a parent. */
windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows ();
else
windowList = window.children;
array = new short[windowList.size () + 1];
i = 1;
array[0] = (window == null
? 0 : (window.parent != null
? window.parent.handle : 0));
for (EmacsWindow treeWindow : windowList)
array[i++] = treeWindow.handle;
return array;
}
public int
getScreenWidth (boolean mmWise)
{
DisplayMetrics metrics;
metrics = getResources ().getDisplayMetrics ();
if (!mmWise)
return metrics.widthPixels;
else
return (int) ((metrics.widthPixels / metrics.xdpi) * 2540.0);
}
public int
getScreenHeight (boolean mmWise)
{
DisplayMetrics metrics;
metrics = getResources ().getDisplayMetrics ();
if (!mmWise)
return metrics.heightPixels;
else
return (int) ((metrics.heightPixels / metrics.ydpi) * 2540.0);
}
public boolean
detectMouse ()
{
InputManager manager;
InputDevice device;
int[] ids;
int i;
if (Build.VERSION.SDK_INT
< Build.VERSION_CODES.JELLY_BEAN)
return false;
manager = (InputManager) getSystemService (Context.INPUT_SERVICE);
ids = manager.getInputDeviceIds ();
for (i = 0; i < ids.length; ++i)
{
device = manager.getInputDevice (ids[i]);
if (device == null)
continue;
if (device.supportsSource (InputDevice.SOURCE_MOUSE))
return true;
}
return false;
}
public String
nameKeysym (int keysym)
{
return KeyEvent.keyCodeToString (keysym);
}
public void
sync ()
{
Runnable runnable;
runnable = new Runnable () {
public void
run ()
{
synchronized (this)
{
notify ();
}
}
};
syncRunnable (runnable);
}
/* 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)
{
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));
}
}
/* Ask the system to open the specified URL.
Value is NULL upon success, or a string describing the error
upon failure. */
public String
browseUrl (String url)
{
Intent intent;
try
{
intent = new Intent (Intent.ACTION_VIEW, Uri.parse (url));
intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity (intent);
}
catch (Exception e)
{
return e.toString ();
}
return null;
}
/* Get a SDK 11 ClipboardManager.
Android 4.0.x requires that this be called from the main
thread. */
public ClipboardManager
getClipboardManager ()
{
final Holder<ClipboardManager> manager;
Runnable runnable;
manager = new Holder<ClipboardManager> ();
runnable = new Runnable () {
public void
run ()
{
Object tem;
synchronized (this)
{
tem = getSystemService (Context.CLIPBOARD_SERVICE);
manager.thing = (ClipboardManager) tem;
notify ();
}
}
};
syncRunnable (runnable);
return manager.thing;
}
public void
restartEmacs ()
{
Intent intent;
intent = new Intent (this, EmacsActivity.class);
intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity (intent);
System.exit (0);
}
/* Wait synchronously for the specified RUNNABLE to complete in the
UI thread. Must be called from the Emacs thread. */
public static void
syncRunnable (Runnable runnable)
{
EmacsNative.beginSynchronous ();
synchronized (runnable)
{
SERVICE.runOnUiThread (runnable);
while (true)
{
try
{
runnable.wait ();
break;
}
catch (InterruptedException e)
{
continue;
}
}
}
EmacsNative.endSynchronous ();
}
public void
updateIC (EmacsWindow window, int newSelectionStart,
int newSelectionEnd, int composingRegionStart,
int composingRegionEnd)
{
if (DEBUG_IC)
Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
+ " " + newSelectionEnd + " "
+ composingRegionStart + " "
+ composingRegionEnd));
window.view.imManager.updateSelection (window.view,
newSelectionStart,
newSelectionEnd,
composingRegionStart,
composingRegionEnd);
}
public void
resetIC (EmacsWindow window, int icMode)
{
if (DEBUG_IC)
Log.d (TAG, "resetIC: " + window);
window.view.setICMode (icMode);
window.view.imManager.restartInput (window.view);
}
};