Reinforce bitmap reconfiguration on Android

* java/org/gnu/emacs/EmacsView.java (EmacsView) <unswapped>: New
field in which to record whether the back buffer has received
contents beyond those present at the last buffer swap.
<dimensionsLock>: Delete field.
(onAttachedToWindow, onLayout, handleDirtyBitmap)
(prepareForLayout): Rather, synchronize window dimensions with
the view.
(getCanvas, getBitmap): Do not reconfigure the back buffer
bitmap if such outstanding data exists.
(postSwapBuffers): New function.
(swapBuffers): If such outstanding data exists and the back
bufferis pending reconfiguration, recreate the back buffer and
report exposure.

* src/androidterm.c (handle_one_android_event): Fix indentation.
This commit is contained in:
Po Lu 2024-06-17 17:43:24 +08:00
parent 6aa5068ac7
commit 82f0014273
2 changed files with 97 additions and 72 deletions

View file

@ -88,15 +88,15 @@ public final class EmacsView extends ViewGroup
/* Whether or not a popup is active. */
private boolean popupActive;
/* Whether the back buffer has been updated since the last swap. */
private boolean unswapped;
/* The current context menu. */
private EmacsContextMenu contextMenu;
/* The last measured width and height. */
private int measuredWidth, measuredHeight;
/* Object acting as a lock for those values. */
private Object dimensionsLock;
/* The serial of the last clip rectangle change. */
private long lastClipSerial;
@ -153,9 +153,6 @@ public final class EmacsView extends ViewGroup
/* Add this view as its own global layout listener. */
getViewTreeObserver ().addOnGlobalLayoutListener (this);
/* Create an object used as a lock. */
this.dimensionsLock = new Object ();
}
private void
@ -164,12 +161,9 @@ public final class EmacsView extends ViewGroup
Bitmap oldBitmap;
int measuredWidth, measuredHeight;
synchronized (dimensionsLock)
{
/* Load measuredWidth and measuredHeight. */
measuredWidth = this.measuredWidth;
measuredHeight = this.measuredHeight;
}
/* Load measuredWidth and measuredHeight. */
measuredWidth = this.measuredWidth;
measuredHeight = this.measuredHeight;
if (measuredWidth == 0 || measuredHeight == 0)
return;
@ -231,8 +225,14 @@ public final class EmacsView extends ViewGroup
public synchronized Bitmap
getBitmap ()
{
if (bitmapDirty || bitmap == null)
/* Never alter the bitmap if modifications have been received that
are still to be copied to the front buffer, as this indicates
that redisplay is in the process of copying matrix contents to
the glass, and such events as generally prompt a complete
regeneration of the frame's contents might not be processed. */
if (!unswapped && (bitmapDirty || bitmap == null))
handleDirtyBitmap ();
unswapped = true;
return bitmap;
}
@ -242,11 +242,12 @@ public final class EmacsView extends ViewGroup
{
int i;
if (bitmapDirty || bitmap == null)
if (!unswapped && (bitmapDirty || bitmap == null))
handleDirtyBitmap ();
if (canvas == null)
return null;
unswapped = true;
/* Update clip rectangles if necessary. */
if (gc.clipRectID != lastClipSerial)
@ -266,14 +267,11 @@ public final class EmacsView extends ViewGroup
return canvas;
}
public void
public synchronized void
prepareForLayout (int wantedWidth, int wantedHeight)
{
synchronized (dimensionsLock)
{
measuredWidth = wantedWidth;
measuredHeight = wantedWidth;
}
measuredWidth = wantedWidth;
measuredHeight = wantedWidth;
}
@Override
@ -329,8 +327,8 @@ else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
}
/* Note that the monitor lock for the window must never be held from
within the lock for the view, because the window also locks the
other way around. */
within that for the view, because the window acquires locks in the
opposite direction. */
@Override
protected void
@ -346,7 +344,7 @@ else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
count = getChildCount ();
needExpose = false;
synchronized (dimensionsLock)
synchronized (this)
{
/* Load measuredWidth and measuredHeight. */
oldMeasuredWidth = measuredWidth;
@ -355,48 +353,48 @@ else if (MeasureSpec.getMode (heightMeasureSpec) == MeasureSpec.AT_MOST
/* Set measuredWidth and measuredHeight. */
measuredWidth = right - left;
measuredHeight = bottom - top;
}
/* If oldMeasuredHeight or oldMeasuredWidth are wrong, set changed
to true as well. */
/* If oldMeasuredHeight or oldMeasuredWidth are wrong, set
changed to true as well. */
if (right - left != oldMeasuredWidth
|| bottom - top != oldMeasuredHeight)
changed = true;
if (right - left != oldMeasuredWidth
|| bottom - top != oldMeasuredHeight)
changed = true;
/* Dirty the back buffer if the layout change resulted in the view
being resized. */
/* Dirty the back buffer if the layout change resulted in the view
being resized. */
if (changed)
{
/* Expose the window upon a change in the view's size that
prompts the creation of a new bitmap. */
explicitlyDirtyBitmap ();
needExpose = true;
/* This might return NULL if this view is not attached. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
if (changed)
{
/* If a toplevel view is focused and isCurrentlyTextEditor
is enabled when the IME is hidden, clear
isCurrentlyTextEditor so it isn't shown again if the
user dismisses Emacs before returning. */
rootWindowInsets = getRootWindowInsets ();
/* Expose the window upon a change in the view's size that
prompts the creation of a new bitmap. */
bitmapDirty = needExpose = true;
if (isCurrentlyTextEditor
&& rootWindowInsets != null
&& isAttachedToWindow
&& !rootWindowInsets.isVisible (WindowInsets.Type.ime ())
/* N.B. that the keyboard is dismissed during gesture
navigation under Android 30, but the system is
quite temperamental regarding whether the window is
focused at that point. Ideally
isCurrentlyTextEditor shouldn't be reset in that
case, but detecting that situation appears to be
impossible. Sigh. */
&& (window == EmacsActivity.focusedWindow
&& hasWindowFocus ()))
isCurrentlyTextEditor = false;
/* This might return NULL if this view is not attached. */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
/* If a toplevel view is focused and
isCurrentlyTextEditor is enabled when the IME is
hidden, clear isCurrentlyTextEditor so it isn't shown
again if the user dismisses Emacs before
returning. */
rootWindowInsets = getRootWindowInsets ();
if (isCurrentlyTextEditor
&& rootWindowInsets != null
&& isAttachedToWindow
&& !rootWindowInsets.isVisible (WindowInsets.Type.ime ())
/* N.B. that the keyboard is dismissed during
gesture navigation under Android 30, but the
system is quite temperamental regarding whether
the window is focused at that point. Ideally
isCurrentlyTextEditor shouldn't be reset in that
case, but detecting that situation appears to be
impossible. Sigh. */
&& (window == EmacsActivity.focusedWindow
&& hasWindowFocus ()))
isCurrentlyTextEditor = false;
}
}
}
@ -449,6 +447,33 @@ else if (child.getVisibility () != GONE)
damageRegion.op (left, top, right, bottom, Region.Op.UNION);
}
/* Complete deferred reconfiguration of the front buffer after a
buffer swap completes, and generate Expose events for the same. */
private void
postSwapBuffers ()
{
if (!unswapped)
return;
unswapped = false;
/* If the bitmap is dirty, reconfigure the bitmap and
generate an Expose event to produce its contents. */
if ((bitmapDirty || bitmap == null)
/* Do not generate Expose events if handleDirtyBitmap will
not create a valid bitmap, or the consequent buffer swap
will produce another event, ad infinitum. */
&& isAttachedToWindow && measuredWidth != 0
&& measuredHeight != 0)
{
handleDirtyBitmap ();
EmacsNative.sendExpose (this.window.handle, 0, 0,
measuredWidth, measuredHeight);
}
}
/* This method is called from both the UI thread and the Emacs
thread. */
@ -467,7 +492,10 @@ else if (child.getVisibility () != GONE)
/* Now see if there is a damage region. */
if (damageRegion.isEmpty ())
return;
{
postSwapBuffers ();
return;
}
/* And extract and clear the damage region. */
@ -479,6 +507,7 @@ else if (child.getVisibility () != GONE)
/* Transfer the bitmap to the surface view, then invalidate
it. */
surfaceView.setBitmap (bitmap, damageRect);
postSwapBuffers ();
}
}
@ -758,13 +787,9 @@ else if (child.getVisibility () != GONE)
was called. */
bitmapDirty = true;
synchronized (dimensionsLock)
{
/* Now expose the view contents again. */
EmacsNative.sendExpose (this.window.handle, 0, 0,
measuredWidth, measuredHeight);
}
/* Now expose the view contents again. */
EmacsNative.sendExpose (this.window.handle, 0, 0,
measuredWidth, measuredHeight);
super.onAttachedToWindow ();
}

View file

@ -926,11 +926,11 @@ handle_one_android_event (struct android_display_info *dpyinfo,
XSETFRAME (inev.ie.frame_or_window, f);
}
if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
{
w = XWINDOW (f->selected_window);
android_set_preeditarea (w, w->cursor.x, w->cursor.y);
}
if (f && FRAME_OUTPUT_DATA (f)->need_cursor_updates)
{
w = XWINDOW (f->selected_window);
android_set_preeditarea (w, w->cursor.x, w->cursor.y);
}
}
goto OTHER;