Prevent Android OS task trimming from deleting Emacs frames

* doc/emacs/android.texi (Android Windowing): Document proviso
on Android 7.0 and later.

* java/org/gnu/emacs/EmacsActivity.java (EmacsActivity)
<timeOfLastInteraction>: New field.
(onStop, onResume): Set and clear timeOfLastInteraction.
(isReallyFinishing): New function.
(onDestroy): Don't delete frame even in the event isFinishing
returns true if more than 4 hours have elapsed since the
activity last moved into the background.
This commit is contained in:
Po Lu 2024-03-28 19:56:31 +08:00
parent 4cee95815b
commit 755665d95a
2 changed files with 66 additions and 1 deletions

View file

@ -858,6 +858,18 @@ 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.
However, on Android 7.0 and later, such frames are not deleted if the
window is closed four or more hours after the window moves into the
background, as the system automatically removes open windows once a
certain period of inactivity elapses when the number of windows retained
by the window manager surpasses a specific threshold, and window
deletion by this mechanism is indistinguishable from window deletion by
the user. Emacs begins to ignore window deletion after two hours less
than the default value of this threshold both to err on the side of
caution, in case the system's record of inactivity and Emacs's differ,
and for the reason that this threshold is open to customization by OS
distributors.
@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

View file

@ -20,9 +20,12 @@
package org.gnu.emacs;
import java.lang.IllegalStateException;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.content.ContentResolver;
@ -31,6 +34,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
@ -78,6 +82,9 @@ public class EmacsActivity extends Activity
/* The last context menu to be closed. */
private static Menu lastClosedMenu;
/* The time of the most recent call to onStop. */
private static long timeOfLastInteraction;
static
{
focusedActivities = new ArrayList<EmacsActivity> ();
@ -271,6 +278,50 @@ children and RESETWHENCHILDLESS is set (implying it is a
syncFullscreenWith (window);
}
@Override
public final void
onStop ()
{
timeOfLastInteraction = SystemClock.elapsedRealtime ();
super.onStop ();
}
/* Return whether the task is being finished in response to explicit
user action. That is to say, Activity.isFinished, but as
documented. */
public final boolean
isReallyFinishing ()
{
long atime, dtime;
int hours;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.NOUGAT)
return isFinishing ();
/* When the number of tasks retained in the recents list exceeds a
threshold, Android 7 and later so destroy activities in trimming
them from recents on the expiry of a timeout that isFinishing
returns true, in direct contradiction to the documentation. This
timeout is generally 6 hours, but admits of customization by
individual system distributors, so to err on the side of the
caution, the timeout Emacs applies is a more conservative figure
of 4 hours. */
if (timeOfLastInteraction == 0)
return isFinishing ();
atime = timeOfLastInteraction;
/* Compare atime with the current system time. */
dtime = SystemClock.elapsedRealtime () - atime;
if (dtime + 1000000 < TimeUnit.HOURS.toMillis (4))
return isFinishing ();
return false;
}
@Override
public final void
onDestroy ()
@ -283,7 +334,8 @@ children and RESETWHENCHILDLESS is set (implying it is a
/* The activity will die shortly hereafter. If there is a window
attached, close it now. */
isMultitask = this instanceof EmacsMultitaskActivity;
manager.removeWindowConsumer (this, isMultitask || isFinishing ());
manager.removeWindowConsumer (this, (isMultitask
|| isReallyFinishing ()));
focusedActivities.remove (this);
invalidateFocus (2);
@ -340,6 +392,7 @@ children and RESETWHENCHILDLESS is set (implying it is a
onResume ()
{
isPaused = false;
timeOfLastInteraction = 0;
EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this);
super.onResume ();