From 98728d457af159fa18f2b0edc4b7d0f9f19c0250 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Sat, 5 Sep 2026 21:58:17 -0600 Subject: [PATCH 1/2] K2GO-382 fix(ui): deep-op notifications return to the op screen; run-in-background lands on Home Tapping a deep-op foreground notification opened a bare LibraryActivity, which fell back to the last tab (Settings) instead of the running op's screen; opening LibraryActivity mid-op also fights the boot gate. Run in background did a bare finish(), popping to whatever launched the screen instead of Home. Add OpReturnNavigator: the single owner of the op -> its progress screen mapping. It is stateless -- forActiveOp reads the existing run-state repositories (module queue, deep-op), which stay the source of truth for what is live. Notification callers wrap a per-op intent with notify() (one flag policy; also supplies the NEW_TASK clone was missing). - DeepOpService (backup/restore): -> the backup/restore job screen. - InstallService (ongoing): -> install progress (EXTRA_INSTALLING). - CloneShareService: -> the clone tab (now with NEW_TASK). - DashboardRebuildService + Zim/Books/KolibriSeedService: routed through the navigator (behavior unchanged; the reference pattern now shared). - LibraryActivity relaunch re-route: now calls forActiveOp, so the which-op -> which-screen logic lives in one place instead of duplicated here. - SetupProgressActivity Run in background: goHome(false) -- lands on Home and keeps the download sessions running (was finish()). Part C (setProgress on the notification) is deferred: it depends on the progress model (T2/T3). Compiles clean. --- .../k2go/deepop/DeepOpService.java | 8 +- .../install/presentation/InstallService.java | 12 +- .../presentation/KolibriSeedService.java | 14 +-- .../k2go/redesign/BooksDownloadService.java | 6 +- .../k2go/redesign/CloneShareService.java | 7 +- .../redesign/DashboardRebuildService.java | 9 +- .../k2go/redesign/LibraryActivity.java | 26 ++-- .../k2go/redesign/OpReturnNavigator.java | 111 ++++++++++++++++++ .../k2go/redesign/SetupProgressActivity.java | 14 ++- .../k2go/redesign/ZimDownloadService.java | 6 +- 10 files changed, 156 insertions(+), 57 deletions(-) create mode 100644 controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java diff --git a/controller/app/src/main/java/org/appdevforall/k2go/deepop/DeepOpService.java b/controller/app/src/main/java/org/appdevforall/k2go/deepop/DeepOpService.java index 70b169c07..40c157183 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/deepop/DeepOpService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/deepop/DeepOpService.java @@ -55,7 +55,7 @@ import org.appdevforall.k2go.deploy.data.RootfsManifest; import org.appdevforall.k2go.env.EnvironmentControl; import org.appdevforall.k2go.env.EnvironmentLock; -import org.appdevforall.k2go.redesign.LibraryActivity; +import org.appdevforall.k2go.redesign.OpReturnNavigator; import org.appdevforall.k2go.util.AppExecutors; import java.io.File; @@ -648,8 +648,10 @@ private void createNotificationChannel() { } private Notification buildNotification(String text) { - Intent open = new Intent(this, LibraryActivity.class); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, PendingIntent.FLAG_IMMUTABLE); + // K2GO-382: tapping returns to the live backup/restore screen. It was a bare LibraryActivity + // that fell back to the last tab (Settings); opening LibraryActivity mid-op also fights the + // boot gate. The route now has one owner (OpReturnNavigator). + PendingIntent contentIntent = OpReturnNavigator.notify(this, OpReturnNavigator.backupRestore(this, owner)); NotificationCompat.Builder b = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.deepop_notif_title)) .setContentText(text) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/install/presentation/InstallService.java b/controller/app/src/main/java/org/appdevforall/k2go/install/presentation/InstallService.java index 12f63e27c..b57bafe43 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/install/presentation/InstallService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/install/presentation/InstallService.java @@ -1833,11 +1833,13 @@ private void createNotificationChannel() { } private Notification buildNotification(String text) { - // ADFA-4919: return to the modern progress surface — LibraryActivity shows rootfs progress - // (boot gate) and routes to the proot install index when a module is running — unlike legacy - // MainActivity, which shows neither. Reusable for any proot module install (delivery, etc.). - Intent open = new Intent(this, org.appdevforall.k2go.redesign.LibraryActivity.class); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, PendingIntent.FLAG_IMMUTABLE); + // ADFA-4919 / K2GO-382: return to the modern progress surface — LibraryActivity shows rootfs + // progress (boot gate) and routes to the proot install index when a module is running — unlike + // legacy MainActivity, which shows neither. The route now has one owner (OpReturnNavigator); + // EXTRA_INSTALLING makes a fresh/refreshed LibraryActivity land on install progress instead of + // the last tab. + PendingIntent contentIntent = org.appdevforall.k2go.redesign.OpReturnNavigator.notify(this, + org.appdevforall.k2go.redesign.OpReturnNavigator.install(this)); NotificationCompat.Builder b = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.install_notif_title)) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/kolibri/presentation/KolibriSeedService.java b/controller/app/src/main/java/org/appdevforall/k2go/kolibri/presentation/KolibriSeedService.java index 2ba55eed5..50523371b 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/kolibri/presentation/KolibriSeedService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/kolibri/presentation/KolibriSeedService.java @@ -33,7 +33,6 @@ import org.appdevforall.k2go.kolibri.data.KolibriRestClient; import org.appdevforall.k2go.kolibri.domain.ChannelSelection; import org.appdevforall.k2go.kolibri.domain.SeedPlan; -import org.appdevforall.k2go.redesign.SetupProgressActivity; import java.util.ArrayList; import java.util.Collections; @@ -364,14 +363,11 @@ private void createNotificationChannel() { } private Notification buildNotification(String label) { - // ADFA-5074: the index, not this stream's detail — it is the only surface that can end - // the run, and a notification is how someone comes back to ask whether it is going well. - Intent open = new Intent(this, SetupProgressActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_SINGLE_TOP - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + // ADFA-5074 / K2GO-382: the index, not this stream's detail — it is the only surface that can + // end the run, and a notification is how someone comes back to ask whether it is going well. + // The route now has one owner (OpReturnNavigator). + PendingIntent contentIntent = org.appdevforall.k2go.redesign.OpReturnNavigator.notify(this, + org.appdevforall.k2go.redesign.OpReturnNavigator.contentDownload(this)); Intent cancel = new Intent(this, KolibriSeedService.class).setAction(ACTION_CANCEL); PendingIntent cancelIntent = PendingIntent.getService(this, 1, cancel, diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/BooksDownloadService.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/BooksDownloadService.java index 13c2b4a81..64c29a02e 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/BooksDownloadService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/BooksDownloadService.java @@ -171,10 +171,8 @@ private void createNotificationChannel() { } private Notification buildNotification(String title) { - Intent openI = new Intent(this, SetupProgressActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent open = PendingIntent.getActivity(this, 0, openI, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + // K2GO-382: route via the single owner (OpReturnNavigator). + PendingIntent open = OpReturnNavigator.notify(this, OpReturnNavigator.contentDownload(this)); PendingIntent cancel = PendingIntent.getService(this, 1, new Intent(this, BooksDownloadService.class).setAction(ACTION_CANCEL), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/CloneShareService.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/CloneShareService.java index 154fce4c1..6b61798da 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/CloneShareService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/CloneShareService.java @@ -112,10 +112,9 @@ private void createNotificationChannel() { } private Notification buildNotification() { - Intent open = new Intent(this, LibraryActivity.class) - .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_clone) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, PendingIntent.FLAG_IMMUTABLE); + // K2GO-382: route via the single owner (OpReturnNavigator), which also supplies the NEW_TASK + // flag this notification was missing. + PendingIntent contentIntent = OpReturnNavigator.notify(this, OpReturnNavigator.cloneShare(this)); return new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getString(R.string.k2go_clone_notif_title)) .setContentText(getString(R.string.k2go_clone_notif_text)) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardRebuildService.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardRebuildService.java index 6c7a154a8..8bfb8fc48 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardRebuildService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/DashboardRebuildService.java @@ -236,12 +236,9 @@ private void createNotificationChannel() { /** Deep-link to Module management -> Dashboard (the card that shows the in-progress indicator), so the * notification is a way back into the update rather than a dead end. */ private PendingIntent openDashboardDetail() { - Intent openI = new Intent(this, SetupLibraryActivity.class) - .putExtra(SetupLibraryActivity.EXTRA_MODULE_MGMT, true) - .putExtra(SetupLibraryActivity.EXTRA_DASHBOARD_DETAIL, true) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - return PendingIntent.getActivity(this, 0, openI, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + // K2GO-382: the route now has one owner (OpReturnNavigator). This service was the reference + // pattern; it now shares the mapping with every other deep-op instead of spelling its own. + return OpReturnNavigator.notify(this, OpReturnNavigator.dashboardRebuild(this)); } /** Ongoing "updating…" notification. Not dismissible and does NOT auto-cancel on tap — while the diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryActivity.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryActivity.java index 9d6852e6f..8e55153c2 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryActivity.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryActivity.java @@ -252,23 +252,15 @@ public android.graphics.Typeface fetchFont(String fontFamily) { // ADFA-4919 (2c): a proot module install is live (its queue is RUNNING = the service is up). // Reopening the app (fresh LibraryActivity, e.g. from the notification) must land on the // progress index, not the empty home — open it over the gate; the index drives completion. - if (!installing && !recovering - && org.appdevforall.k2go.install.presentation.ModuleQueueRepository.get().isRunning()) { - startActivity(new android.content.Intent(this, SetupProgressActivity.class)); - } - - // ADFA-4957: same idea for a live deep-env op (backup/restore). A fresh LibraryActivity — from - // the notification, or a swipe-away relaunch — must land back on the op screen, not Home/Library - // (which fights the gate and would try to boot the server mid-op). Route straight to the - // backup/restore index; BackupJobFragment re-binds to the live op from DeepOpProgressRepository. - if (!installing && !recovering - && org.appdevforall.k2go.deepop.DeepOpProgressRepository.get().isRunning()) { - org.appdevforall.k2go.deepop.DeepOpState dop = org.appdevforall.k2go.deepop.DeepOpProgressRepository.get().current(); - String brMode = dop.owner == org.appdevforall.k2go.env.EnvironmentLock.Owner.RESTORE - ? BackupJobFragment.MODE_RESTORE : BackupJobFragment.MODE_BACKUP; - startActivity(new android.content.Intent(this, SetupLibraryActivity.class) - .putExtra(SetupLibraryActivity.EXTRA_BACKUP_RESTORE, true) - .putExtra(SetupLibraryActivity.EXTRA_BR_JOB_MODE, brMode)); + // ADFA-4957 / K2GO-382: a fresh LibraryActivity — from a deep-op notification, or a swipe-away + // relaunch — must land back on a live op's own screen, not Home/Library (which fights the boot + // gate and would try to boot the server mid-op). The "which op -> which screen" mapping has one + // owner now (OpReturnNavigator.forActiveOp), shared with the deep-op notifications so the two + // cannot drift; the op screens (SetupProgressActivity for a proot queue, BackupJobFragment for a + // deep-env op) re-bind to the live op from their repositories. + if (!installing && !recovering) { + android.content.Intent op = OpReturnNavigator.forActiveOp(this); + if (op != null) startActivity(op); } serverController = new ServerController(this, this); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java new file mode 100644 index 000000000..2b631d49a --- /dev/null +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java @@ -0,0 +1,111 @@ +/* + * ============================================================================ + * Name : OpReturnNavigator.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : K2GO-382. ONE source for "return to a running operation's own screen." + * + * Every deep-op foreground notification (install / backup / restore / dashboard + * rebuild / clone) and every content-download stream (kiwix / books / kolibri) must, + * when tapped, land on that op's own progress screen -- not on Home/Settings. That + * "op -> screen" mapping used to be spelled out separately in each service's + * contentIntent AND in LibraryActivity's relaunch re-route, so the two could drift + * (they did: several services opened a bare LibraryActivity that fell back to the last + * tab). This class is the single owner of the mapping. + * + * Lifecycle / state: this class is STATELESS. It holds no "what is running" flag of its + * own; {@link #forActiveOp} reads the existing run-state repositories (the module queue + * and the deep-op repository), which stay the single source of truth for what is live. + * + * Flags: the per-op builders return the target + extras only (the "where"). Notification + * callers wrap them with {@link #notify} (which adds the from-a-service task flags, so a + * tap brings an existing screen forward instead of stacking a duplicate). Activity + * callers start the bare intent in their own task and add flags as their context needs. + * ============================================================================ + */ +package org.appdevforall.k2go.redesign; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import org.appdevforall.k2go.R; +import org.appdevforall.k2go.deepop.DeepOpProgressRepository; +import org.appdevforall.k2go.deepop.DeepOpState; +import org.appdevforall.k2go.env.EnvironmentLock; +import org.appdevforall.k2go.install.presentation.ModuleQueueRepository; + +public final class OpReturnNavigator { + private OpReturnNavigator() {} + + /** Content downloads (kiwix / books / kolibri): the shared provisioning progress screen. */ + public static Intent contentDownload(Context ctx) { + return new Intent(ctx, SetupProgressActivity.class); + } + + /** Backup / restore live op: the backup/restore job screen; BackupJobFragment re-binds to the + * running op from DeepOpProgressRepository (ADFA-4957). */ + public static Intent backupRestore(Context ctx, EnvironmentLock.Owner owner) { + String mode = owner == EnvironmentLock.Owner.RESTORE + ? BackupJobFragment.MODE_RESTORE : BackupJobFragment.MODE_BACKUP; + return new Intent(ctx, SetupLibraryActivity.class) + .putExtra(SetupLibraryActivity.EXTRA_BACKUP_RESTORE, true) + .putExtra(SetupLibraryActivity.EXTRA_BR_JOB_MODE, mode); + } + + /** Dashboard rebuild: the module-management dashboard card with its in-progress indicator. */ + public static Intent dashboardRebuild(Context ctx) { + return new Intent(ctx, SetupLibraryActivity.class) + .putExtra(SetupLibraryActivity.EXTRA_MODULE_MGMT, true) + .putExtra(SetupLibraryActivity.EXTRA_DASHBOARD_DETAIL, true); + } + + /** Rootfs / module install: LibraryActivity shows the rootfs progress (boot gate) and, when a + * proot module queue is live, routes on to the install index (see {@link #forActiveOp}). */ + public static Intent install(Context ctx) { + return new Intent(ctx, LibraryActivity.class) + .putExtra(LibraryActivity.EXTRA_INSTALLING, true); + } + + /** Clone / share: the Connect/clone tab. */ + public static Intent cloneShare(Context ctx) { + return new Intent(ctx, LibraryActivity.class) + .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_clone); + } + + /** Home / Library -- where "Run in background" lands deliberately (not a back-stack pop). */ + public static Intent home(Context ctx) { + return new Intent(ctx, LibraryActivity.class) + .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_library); + } + + /** + * The live op's screen, chosen from the run-state repositories -- the single place that decides + * "which op is running." Returns {@code null} when nothing tracked here is live (the caller keeps + * its normal destination). Mirrors the order LibraryActivity used: a proot module queue first, + * then a deep-env op (they are mutually exclusive by ContentAdmission, but the order is kept). + */ + public static Intent forActiveOp(Context ctx) { + if (ModuleQueueRepository.get().isRunning()) { + return contentDownload(ctx); // the module install index lives on the progress screen + } + if (DeepOpProgressRepository.get().isRunning()) { + DeepOpState dop = DeepOpProgressRepository.get().current(); + if (dop != null) return backupRestore(ctx, dop.owner); + } + return null; + } + + /** + * Wrap a per-op intent as a notification contentIntent. Adds the from-a-service task flags + * (NEW_TASK is required when a Service launches an Activity; SINGLE_TOP|CLEAR_TOP bring an + * existing op screen forward instead of stacking a duplicate) -- one policy for every deep-op + * notification, so none can drift (this also supplies the NEW_TASK that clone was missing). + */ + public static PendingIntent notify(Context ctx, Intent target) { + target.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + return PendingIntent.getActivity(ctx, 0, target, + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + } +} diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java index c4ebde5ec..bdea5feb1 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java @@ -150,7 +150,10 @@ protected void onCreate(@Nullable Bundle s) { detailRoot = findViewById(R.id.k2go_sp_detail); finishBtn.setOnClickListener(v -> goHome(true)); - runBgBtn.setOnClickListener(v -> finish()); // leave; the Library keeps provisioning going + // K2GO-382: land deliberately on Home (goHome), not a bare finish() that pops to whatever + // launched this (the wizard/hub). goHome(false) keeps the download sessions alive, so the + // Library keeps provisioning in the background. + runBgBtn.setOnClickListener(v -> goHome(false)); cancel.setOnClickListener(v -> { redirectCancelled = true; cancelRedirect(); render(); }); detailBackBtn = findViewById(R.id.k2go_sp_back); @@ -161,7 +164,7 @@ protected void onCreate(@Nullable Bundle s) { // configure and any non-module detail. detailBackBtn.setOnClickListener(v -> backToIndex()); detailRunBgBtn.setText(R.string.k2go_zim_run_bg); // in a detail, secondary = leave (never abort) - detailRunBgBtn.setOnClickListener(v -> finish()); + detailRunBgBtn.setOnClickListener(v -> goHome(false)); // K2GO-382: land on Home, keep provisioning // ADFA-4842: own a ServerController so the index can restart the server after a module batch // (it was pdsm-stopped for the runroles) and keep ServerStateRepository fresh so the start @@ -1282,8 +1285,9 @@ private void goHome(boolean clearSessions) { // ADFA-4919: the natural end of installing is the Library — go there directly and clear the // install screens above it. Both the wizard and Get More launch from LibraryActivity, so - // CLEAR_TOP + SINGLE_TOP lands on the existing Library (dropping Get More + this index). Only - // success/Finish reach here; "Run in background" (REST) still finish()es in place. ADFA-5343: a + // CLEAR_TOP + SINGLE_TOP lands on the existing Library (dropping Get More + this index). + // K2GO-382: "Run in background" now reaches here too via goHome(false) — same Home landing, but + // clearSessions=false leaves the download sessions running. ADFA-5343: a // module batch set desired=UP; the reconciler brings the server up and keeps re-driving it wherever // the app is, so the reused Library is (or becomes) live on arrival — even Finish under a slow/flap // start lands on a Home the reconciler drives up, not a dead one (5336). @@ -1378,7 +1382,7 @@ private void configureDetailBar() { detailBackBtn.setText(R.string.k2go_setup_back); detailBackBtn.setOnClickListener(v -> backToIndex()); detailRunBgBtn.setText(R.string.k2go_zim_run_bg); - detailRunBgBtn.setOnClickListener(v -> finish()); + detailRunBgBtn.setOnClickListener(v -> goHome(false)); // K2GO-382: land on Home, keep provisioning detailRunBgBtn.setVisibility(isLiveDetail(detailKey) ? View.VISIBLE : View.GONE); } } diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ZimDownloadService.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ZimDownloadService.java index ab322782c..c1425a1b5 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ZimDownloadService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ZimDownloadService.java @@ -173,10 +173,8 @@ private void createNotificationChannel() { } private Notification buildNotification(String currentLabel) { - Intent open = new Intent(this, SetupProgressActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, open, - PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + // K2GO-382: route via the single owner (OpReturnNavigator). + PendingIntent contentIntent = OpReturnNavigator.notify(this, OpReturnNavigator.contentDownload(this)); Intent cancel = new Intent(this, ZimDownloadService.class).setAction(ACTION_CANCEL); PendingIntent cancelIntent = PendingIntent.getService(this, 1, cancel, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); From 13ee09b580ad6f2ee9db6da8a9fb34c1ff7b1928 Mon Sep 17 00:00:00 2001 From: Luis Guzman Date: Sat, 5 Sep 2026 22:03:12 -0600 Subject: [PATCH 2/2] K2GO-382 fix(ui): review fixes -- keep module-install routing; drop dead home() Two-pass review of the deep-op notification change: - install() no longer sets EXTRA_INSTALLING. That flag forced LibraryActivity.installing=true, which suppressed the !installing module-queue re-route, so a proot/module install's notification tap stranded on the boot gate instead of the install index. A bare LibraryActivity is correct: a rootfs install is detected by InstallProgressRepository.isRunning(); a module install is forwarded by forActiveOp. - Remove OpReturnNavigator.home(): it was unused (Run-in-background uses SetupProgressActivity.goHome) and was a second builder of the leave-to-Home intent -- the duplicated truth the one-source refactor set out to remove. --- .../k2go/redesign/OpReturnNavigator.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java index 2b631d49a..d6bcb1b5d 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/OpReturnNavigator.java @@ -60,11 +60,13 @@ public static Intent dashboardRebuild(Context ctx) { .putExtra(SetupLibraryActivity.EXTRA_DASHBOARD_DETAIL, true); } - /** Rootfs / module install: LibraryActivity shows the rootfs progress (boot gate) and, when a - * proot module queue is live, routes on to the install index (see {@link #forActiveOp}). */ + /** Rootfs / module install: a bare LibraryActivity. It detects a live rootfs install itself + * (InstallProgressRepository.isRunning -> the boot-gate progress) and, when a proot module queue + * is live instead, its relaunch re-route ({@link #forActiveOp}) forwards to the install index. + * Do NOT set EXTRA_INSTALLING here: it forces LibraryActivity.installing=true, which suppresses + * that module re-route and would strand a module install's notification on the gate. */ public static Intent install(Context ctx) { - return new Intent(ctx, LibraryActivity.class) - .putExtra(LibraryActivity.EXTRA_INSTALLING, true); + return new Intent(ctx, LibraryActivity.class); } /** Clone / share: the Connect/clone tab. */ @@ -73,12 +75,6 @@ public static Intent cloneShare(Context ctx) { .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_clone); } - /** Home / Library -- where "Run in background" lands deliberately (not a back-stack pop). */ - public static Intent home(Context ctx) { - return new Intent(ctx, LibraryActivity.class) - .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_library); - } - /** * The live op's screen, chosen from the run-state repositories -- the single place that decides * "which op is running." Returns {@code null} when nothing tracked here is live (the caller keeps