From 451994a345d562f70ad7b6935268d81df8c8bab7 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Fri, 17 Jul 2026 14:51:31 +0200 Subject: [PATCH] app: route back through the AndroidX dispatcher At targetSdk 36 enableOnBackInvokedCallback defaults to true and the system stops calling onBackPressed(), so both overrides would quietly stop running. Neither failure announces itself. OptionsActivity loses the most: its override calls save(), and its else branch is the whole "leave a sub-tab" navigation. Bypassed, back would drop the settings and walk out of the options screen instead of returning to the menu. It extended the framework Activity, which has no dispatcher at all, hence FragmentActivity; it uses no fragments, so nothing else moves. The branch that called super.onBackPressed() then finish() now just finishes, which is what that pair amounted to, and finish() is overridden here to carry the settings back. HTTrackActivity is a FragmentActivity already, but the dispatcher does not re-enter a subclass override either, so its guard against finishing mid-crawl was equally dead. Rather than intercept every back and re-dispatch the ones it does not want, the callback is enabled only on the progress pane, which is the condition it was really expressing. One path now serves both: with no override left, ComponentActivity routes today's back through the same dispatcher that OnBackInvokedCallback will reach at 36. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Xavier Roche --- .../com/httrack/android/HTTrackActivity.java | 20 +++++---- .../com/httrack/android/OptionsActivity.java | 45 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/httrack/android/HTTrackActivity.java b/app/src/main/java/com/httrack/android/HTTrackActivity.java index a9e5c56..f0806e9 100755 --- a/app/src/main/java/com/httrack/android/HTTrackActivity.java +++ b/app/src/main/java/com/httrack/android/HTTrackActivity.java @@ -89,6 +89,7 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; @@ -497,6 +498,8 @@ protected void onCreate(final Bundle savedInstanceState) { Log.d(getClass().getSimpleName(), "onCreate"); super.onCreate(savedInstanceState); + getOnBackPressedDispatcher().addCallback(this, stayAliveWhileMirroring); + // Attempt to load the native library. // Fetch httrack engine version if (HTTrackLib.loadLibraries()) { @@ -2159,6 +2162,7 @@ private void setPane(final int position) { // Switch pane pane_id = position; + stayAliveWhileMirroring.setEnabled(pane_id == LAYOUT_MIRROR_PROGRESS); setContentView(layouts[pane_id]); // Entering a new pane: restore data @@ -2827,14 +2831,14 @@ private void goToHome() { startActivity(intent); } - @Override - public void onBackPressed() { - // Downloading data - if (pane_id == LAYOUT_MIRROR_PROGRESS) { - // Do not go home, or our fragment will die. + /* + * Enabled only on the progress pane, where finishing would take the retained fragment, and + * the crawl with it, down. Everywhere else it stays disabled so back does its usual thing. + */ + private final OnBackPressedCallback stayAliveWhileMirroring = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { goToHome(); - } else { - super.onBackPressed(); } - } + }; } diff --git a/app/src/main/java/com/httrack/android/OptionsActivity.java b/app/src/main/java/com/httrack/android/OptionsActivity.java index 9940131..53a8076 100755 --- a/app/src/main/java/com/httrack/android/OptionsActivity.java +++ b/app/src/main/java/com/httrack/android/OptionsActivity.java @@ -44,10 +44,16 @@ import android.widget.EditText; import android.widget.LinearLayout; +import androidx.activity.OnBackPressedCallback; +import androidx.fragment.app.FragmentActivity; + /** * The options activity. + * + * FragmentActivity rather than Activity: predictive back is dispatched through the AndroidX + * OnBackPressedDispatcher, which a plain Activity does not have. */ -public class OptionsActivity extends Activity implements View.OnClickListener { +public class OptionsActivity extends FragmentActivity implements View.OnClickListener { /* List of all tabs. */ @SuppressWarnings("unchecked") protected static Class[] tabClasses = new Class[] { @@ -335,6 +341,8 @@ private void setViewMenu() { protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getOnBackPressedDispatcher().addCallback(this, backCallback); + // Large screen ? Enable special tablet features in such case... // "xlarge screens are at least 960dp x 720dp" isTabletMode = getResources().getBoolean(R.bool.isTablet) @@ -388,27 +396,28 @@ public void finish() { } /** - * Override back to propagate settings.
- * FIXME: we should probably do better than that! + * Back propagates the settings, and on a sub-tab returns to the menu rather than leaving. + * Always enabled: neither branch wants the default, which would finish without saving. */ - @Override - public void onBackPressed() { - // Back from activity - if (isTabletMode || activityClass == null) { - if (isTabletMode && activityClass != null) { + private final OnBackPressedCallback backCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + // Back from activity. finish() is overridden above to carry the settings back. + if (isTabletMode || activityClass == null) { + if (isTabletMode && activityClass != null) { + save(); + } + finish(); + } + // Leave sub-activity (activityClass != null) + else { + notifyHideTab(); save(); + activityClass = null; + setViewMenu(); } - super.onBackPressed(); - finish(); } - // Leave sub-activity (activityClass != null) - else { - notifyHideTab(); - save(); - activityClass = null; - setViewMenu(); - } - } + }; /* * Return current field ID's for the activity view.