From a1941cd7a7dc9a6f6b7239ec7d4bd3bdf5d55fc9 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 10 Feb 2023 18:57:51 +0800 Subject: [PATCH] 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. --- doc/emacs/android.texi | 5 - java/debug.sh | 13 +- java/org/gnu/emacs/EmacsCopyArea.java | 24 +++- java/org/gnu/emacs/EmacsSurfaceView.java | 159 +++++++++++------------ java/org/gnu/emacs/EmacsView.java | 93 ++----------- java/org/gnu/emacs/EmacsWindow.java | 23 ---- src/sfntfont.c | 4 + 7 files changed, 116 insertions(+), 205 deletions(-) diff --git a/doc/emacs/android.texi b/doc/emacs/android.texi index 5fdf5c16b4e..5bbb2245a0c 100644 --- a/doc/emacs/android.texi +++ b/doc/emacs/android.texi @@ -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. diff --git a/java/debug.sh b/java/debug.sh index 7008664c049..2e95f9738c7 100755 --- a/java/debug.sh +++ b/java/debug.sh @@ -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. diff --git a/java/org/gnu/emacs/EmacsCopyArea.java b/java/org/gnu/emacs/EmacsCopyArea.java index f8974e17c2e..11dc22e0456 100644 --- a/java/org/gnu/emacs/EmacsCopyArea.java +++ b/java/org/gnu/emacs/EmacsCopyArea.java @@ -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 { diff --git a/java/org/gnu/emacs/EmacsSurfaceView.java b/java/org/gnu/emacs/EmacsSurfaceView.java index f6cb77bb2b8..e9bae623930 100644 --- a/java/org/gnu/emacs/EmacsSurfaceView.java +++ b/java/org/gnu/emacs/EmacsSurfaceView.java @@ -19,127 +19,114 @@ along with GNU Emacs. If not, see . */ 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; - - holder = getHolder (); + /* First, remove the old front buffer. */ - 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; - @Override - protected void - onLayout (boolean changed, int left, int top, int right, - int bottom) - { - Log.d (TAG, ("onLayout: " + left + " " + top + " " + right - + " " + bottom + " -- " + changed + " visibility " - + getVisibility ())); - } + /* Next, create the new front buffer if necessary. */ - /* This method is only used during debugging when it seems damage - isn't working correctly. */ + 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 Canvas - lockCanvas () + public synchronized void + setBitmap (Bitmap bitmap, Rect damageRect) { - SurfaceHolder holder; + if (bitmap != this.bitmap) + reconfigureFrontBuffer (bitmap); + else if (bitmap != null) + copyToFrontBuffer (damageRect); - holder = getHolder (); - return holder.lockCanvas (); + 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 (); + } } - public void - unlockCanvasAndPost (Canvas canvas) + @Override + public synchronized void + onDraw (Canvas canvas) { - SurfaceHolder holder; + /* Paint the view's bitmap; the bitmap might be recycled right + now. */ - holder = getHolder (); - holder.unlockCanvasAndPost (canvas); + if (frontBuffer != null) + canvas.drawBitmap (frontBuffer, 0f, 0f, bitmapPaint); } }; diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index fac11870ebf..0416301101c 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -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 @@ public class EmacsView extends ViewGroup 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 @@ public class EmacsView extends ViewGroup 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 @@ public class EmacsView extends ViewGroup 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 @@ public class EmacsView extends ViewGroup bitmap.recycle (); bitmap = null; canvas = null; + surfaceView.setBitmap (null, null); /* Collect the bitmap storage; it could be large. */ Runtime.getRuntime ().gc (); diff --git a/java/org/gnu/emacs/EmacsWindow.java b/java/org/gnu/emacs/EmacsWindow.java index e921b972c2c..9e2f2f53270 100644 --- a/java/org/gnu/emacs/EmacsWindow.java +++ b/java/org/gnu/emacs/EmacsWindow.java @@ -57,12 +57,6 @@ import android.os.Build; 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 @@ public class EmacsWindow extends EmacsHandleObject } }); } - - /* 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/src/sfntfont.c b/src/sfntfont.c index cc084c79307..a5ed54394a2 100644 --- a/src/sfntfont.c +++ b/src/sfntfont.c @@ -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); -- 2.39.5