Update Android port

* doc/emacs/android.texi (Android Windowing): Remove yet another
limitation.
* java/debug.sh: Make this work on systems which prohibit
attaching to app processes from adbd.
* java/org/gnu/emacs/EmacsCopyArea.java (perform): Avoid
creating copies whenever possible.
* java/org/gnu/emacs/EmacsSurfaceView.java (EmacsSurfaceView):
Remove SurfaceView based implementation and use manual double
buffering with invalidate instead.
* java/org/gnu/emacs/EmacsView.java (EmacsView, handleDirtyBitmap)
(raise, lower, onDetachedFromWindow): Adjust accordingly.
* java/org/gnu/emacs/EmacsWindow.java (windowUpdated): Remove
function.
* src/sfntfont.c (sfntfont_open): Set font->max_width correctly.
This commit is contained in:
Po Lu 2023-02-10 18:57:51 +08:00
parent 60270d8ee3
commit a1941cd7a7
7 changed files with 120 additions and 209 deletions

View file

@ -418,11 +418,6 @@ Due to the unusual nature of the Android windowing environment, Emacs
only supports a limited subset of GUI features. Here is a list of
known limitations, and features which are not implemented:
@itemize @bullet
@item
The functions @code{raise-frame} and @code{lower-frame} are
non-functional, because of bugs in the window system.
@item
Scroll bars are not supported, as they are close to useless on Android
devices.

View file

@ -267,10 +267,14 @@ if [ -z "$gdbserver" ]; then
gdbserver_bin=/system/bin/gdbserver
else
gdbserver_bin=/data/local/tmp/gdbserver
gdbserver_cat="cat $gdbserver_bin | run-as $package sh -c \
\"tee gdbserver > /dev/null\""
# Upload the specified gdbserver binary to the device.
adb -s $device push "$gdbserver" "$gdbserver_bin"
adb -s $device shell chmod +x "$gdbserver_bin"
# Copy it to the user directory.
adb -s $device shell "$gdbserver_cat"
adb -s $device shell "run-as $package chmod +x gdbserver"
fi
# Now start gdbserver on the device asynchronously.
@ -286,10 +290,9 @@ if [ -z "$gdbserver" ]; then
else
# Normally the program cannot access $gdbserver_bin when it is
# placed in /data/local/tmp.
adb -s $device shell $gdbserver_bin --once \
"+/data/local/tmp/debug.$package.socket" \
--attach $pid >&5 &
gdb_socket="localfilesystem:/data/local/tmp/debug.$package.socket"
adb -s $device shell run-as $package "./gdbserver" --once \
"0.0.0.0:7654" --attach $pid >&5 &
gdb_socket="tcp:7654"
fi
# Wait until gdbserver successfully runs.

View file

@ -110,11 +110,25 @@ public class EmacsCopyArea
if (gc.clip_mask == null)
{
bitmap = Bitmap.createBitmap (srcBitmap,
src_x, src_y, width,
height);
canvas.drawBitmap (bitmap, null, rect, paint);
bitmap.recycle ();
if (source == destination)
{
/* Create a copy of the bitmap, since Android can't handle
overlapping copies. */
bitmap = Bitmap.createBitmap (srcBitmap,
src_x, src_y, width,
height);
canvas.drawBitmap (bitmap, null, rect, paint);
bitmap.recycle ();
}
else
{
/* But here the bitmaps are known to not overlap, so avoid
that extra consing overhead. */
srcRect = new Rect (src_x, src_y, src_x + width,
src_y + height);
canvas.drawBitmap (srcBitmap, null, rect, paint);
}
}
else
{

View file

@ -19,127 +19,114 @@
package org.gnu.emacs;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.view.View;
import android.os.Build;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Paint;
import android.util.Log;
/* This originally extended SurfaceView. However, doing so proved to
be too slow, and Android's surface view keeps up to three of its
own back buffers, which use too much memory (up to 96 MB for a
single frame.) */
public class EmacsSurfaceView extends SurfaceView
public class EmacsSurfaceView extends View
{
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)
{
Canvas canvas;
Log.d (TAG, "surfaceChanged: " + view + ", ");
view.swapBuffers (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 (true);
}
@Override
public void
surfaceDestroyed (SurfaceHolder holder)
{
synchronized (surfaceChangeLock)
{
Log.d (TAG, "surfaceDestroyed: " + view);
created = false;
}
}
}
private Bitmap frontBuffer;
private Canvas bitmapCanvas;
private Bitmap bitmap;
private Paint bitmapPaint;
public
EmacsSurfaceView (final EmacsView view)
{
super (view.getContext ());
this.surfaceChangeLock = new Object ();
this.view = view;
getHolder ().addCallback (new Callback ());
this.bitmapPaint = new Paint ();
}
public boolean
isCreated ()
private void
copyToFrontBuffer (Rect damageRect)
{
return created;
if (damageRect != null)
bitmapCanvas.drawBitmap (bitmap, damageRect, damageRect,
bitmapPaint);
else
bitmapCanvas.drawBitmap (bitmap, 0f, 0f, bitmapPaint);
}
public Canvas
lockCanvas (Rect damage)
private void
reconfigureFrontBuffer (Bitmap bitmap)
{
SurfaceHolder holder;
/* First, remove the old front buffer. */
holder = getHolder ();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
if (frontBuffer != null)
{
damage.setEmpty ();
return holder.lockHardwareCanvas ();
frontBuffer.recycle ();
frontBuffer = null;
bitmapCanvas = null;
}
return holder.lockCanvas (damage);
this.bitmap = bitmap;
/* Next, create the new front buffer if necessary. */
if (bitmap != null && frontBuffer == null)
{
frontBuffer = Bitmap.createBitmap (bitmap.getWidth (),
bitmap.getHeight (),
Bitmap.Config.ARGB_8888,
false);
bitmapCanvas = new Canvas (frontBuffer);
/* And copy over the bitmap contents. */
copyToFrontBuffer (null);
}
else if (bitmap != null)
/* Just copy over the bitmap contents. */
copyToFrontBuffer (null);
}
public synchronized void
setBitmap (Bitmap bitmap, Rect damageRect)
{
if (bitmap != this.bitmap)
reconfigureFrontBuffer (bitmap);
else if (bitmap != null)
copyToFrontBuffer (damageRect);
if (bitmap != null)
{
/* In newer versions of Android, the invalid rectangle is
supposedly internally calculated by the system. How that
is done is unknown, but calling `invalidateRect' is now
deprecated.
Fortunately, nobody has deprecated the version of
`postInvalidate' that accepts a dirty rectangle. */
if (damageRect != null)
postInvalidate (damageRect.left, damageRect.top,
damageRect.right, damageRect.bottom);
else
postInvalidate ();
}
}
@Override
protected void
onLayout (boolean changed, int left, int top, int right,
int bottom)
public synchronized void
onDraw (Canvas canvas)
{
Log.d (TAG, ("onLayout: " + left + " " + top + " " + right
+ " " + bottom + " -- " + changed + " visibility "
+ getVisibility ()));
}
/* Paint the view's bitmap; the bitmap might be recycled right
now. */
/* This method is only used during debugging when it seems damage
isn't working correctly. */
public Canvas
lockCanvas ()
{
SurfaceHolder holder;
holder = getHolder ();
return holder.lockCanvas ();
}
public void
unlockCanvasAndPost (Canvas canvas)
{
SurfaceHolder holder;
holder = getHolder ();
holder.unlockCanvasAndPost (canvas);
if (frontBuffer != null)
canvas.drawBitmap (frontBuffer, 0f, 0f, bitmapPaint);
}
};

View file

@ -103,14 +103,6 @@ public class EmacsView extends ViewGroup
displayed whenever possible. */
public boolean isCurrentlyTextEditor;
/* An empty rectangle. */
public static final Rect emptyRect;
static
{
emptyRect = new Rect ();
};
public
EmacsView (EmacsWindow window)
{
@ -127,14 +119,8 @@ public class EmacsView extends ViewGroup
/* Create the surface view. */
this.surfaceView = new EmacsSurfaceView (this);
this.surfaceView.setZOrderMediaOverlay (true);
addView (this.surfaceView);
/* Not sure exactly what this does but it makes things magically
work. Why is something as simple as XRaiseWindow so involved
on Android? */
setChildrenDrawingOrderEnabled (true);
/* Get rid of the default focus highlight. */
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
setDefaultFocusHighlightEnabled (false);
@ -191,8 +177,12 @@ public class EmacsView extends ViewGroup
bitmapDirty = false;
/* Explicitly free the old bitmap's memory. */
if (oldBitmap != null)
oldBitmap.recycle ();
{
oldBitmap.recycle ();
surfaceView.setBitmap (null, null);
}
/* Some Android versions still don't free the bitmap until the
next GC. */
@ -342,67 +332,27 @@ else if (child.getVisibility () != GONE)
thread. */
public void
swapBuffers (boolean force)
swapBuffers ()
{
Canvas canvas;
Rect damageRect;
Bitmap bitmap;
/* Code must always take damageRegion, and then surfaceChangeLock,
never the other way around! */
damageRect = null;
synchronized (damageRegion)
{
if (!force && damageRegion.isEmpty ())
if (damageRegion.isEmpty ())
return;
bitmap = getBitmap ();
/* Emacs must take the following lock to ensure the access to the
canvas occurs with the surface created. Otherwise, Android
will throttle calls to lockCanvas. */
synchronized (surfaceView.surfaceChangeLock)
{
if (!force)
damageRect = damageRegion.getBounds ();
else
damageRect = emptyRect;
if (!surfaceView.isCreated ())
return;
if (bitmap == null)
return;
/* Lock the canvas with the specified damage. */
canvas = surfaceView.lockCanvas (damageRect);
/* Return if locking the canvas failed. */
if (canvas == null)
return;
/* Copy from the back buffer to the canvas. If damageRect was
made empty, then draw the entire back buffer. */
if (damageRect.isEmpty ())
canvas.drawBitmap (bitmap, 0f, 0f, paint);
else
canvas.drawBitmap (bitmap, damageRect, damageRect, paint);
/* Unlock the canvas and clear the damage. */
surfaceView.unlockCanvasAndPost (canvas);
damageRegion.setEmpty ();
}
/* Transfer the bitmap to the surface view, then invalidate
it. */
surfaceView.setBitmap (bitmap, damageRect);
}
}
public void
swapBuffers ()
{
swapBuffers (false);
}
@Override
public boolean
onKeyDown (int keyCode, KeyEvent event)
@ -486,16 +436,6 @@ else if (child.getVisibility () != GONE)
return;
parent.bringChildToFront (this);
/* Yes, all of this is really necessary! */
parent.requestLayout ();
parent.invalidate ();
requestLayout ();
invalidate ();
/* The surface view must be destroyed and recreated. */
removeView (surfaceView);
addView (surfaceView, 0);
}
public void
@ -511,16 +451,6 @@ else if (child.getVisibility () != GONE)
return;
parent.moveChildToBack (this);
/* Yes, all of this is really necessary! */
parent.requestLayout ();
parent.invalidate ();
requestLayout ();
invalidate ();
/* The surface view must be removed and attached again. */
removeView (surfaceView);
addView (surfaceView, 0);
}
@Override
@ -574,6 +504,7 @@ else if (child.getVisibility () != GONE)
bitmap.recycle ();
bitmap = null;
canvas = null;
surfaceView.setBitmap (null, null);
/* Collect the bitmap storage; it could be large. */
Runtime.getRuntime ().gc ();

View file

@ -57,12 +57,6 @@ their views are attached to the parent activity (if any), else
Views are also drawables, meaning they can accept drawing
requests. */
/* Help wanted. What does not work includes `EmacsView.raise',
`EmacsView.lower', reparenting a window onto another window.
All three are likely undocumented restrictions within
EmacsSurface. */
public class EmacsWindow extends EmacsHandleObject
implements EmacsDrawable
{
@ -1111,21 +1105,4 @@ else if (EmacsWindow.this.isMapped)
}
});
}
/* 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);
}
});
}
};

View file

@ -2152,6 +2152,10 @@ sfntfont_open (struct frame *f, Lisp_Object font_entity,
* pixel_size * 1.0 / font_info->head->units_per_em);
font->height = font->ascent + font->descent;
/* Set font->max_width to the maximum advance width. */
font->max_width = (font_info->hhea->advance_width_max
* pixel_size * 1.0 / font_info->head->units_per_em);
/* Set generic attributes such as type and style. */
ASET (font_object, FONT_TYPE_INDEX, sfnt_vendor_name);