From 6ba6690b070c1289b6c37297265376e5cb300c0a Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 14 Aug 2026 02:54:36 -0600 Subject: [PATCH 1/3] =?UTF-8?q?ADFA-5137=20refactor(state):=20setup=5Fcomp?= =?UTF-8?q?lete=20stops=20existing=20=E2=80=94=20the=20app=20asks=20whethe?= =?UTF-8?q?r=20a=20system=20is=20here?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes findings 3 and 5 of state-spine.svg and the first row of the standing directive's ledger, the one that read: setup_complete | SharedPreferences | 4 sites | nobody | never | no lifecycle. THE FAULT. setup_complete was a claim about the past — "setup happened" — answering a question about the present: "should this person see the wizard?". It was written when an install STARTED and cleared by nobody, so it could say yes while the device had nothing, and the launch then routed past the wizard forever. Finding 3. From there the only way forward was a sentence pointing at a control named "Get more". Finding 5. One bug read twice. WHAT REPLACES IT — nothing stored. The answer was already on the device, in three facts that each die with the thing they describe: a rootfs on disk, the durable install marker, the environment lock's owner. SystemPresence.hereOrOnTheWay() is that disjunction, pure and with its whole eight-row truth table in tests; SystemPresenceReader gathers the three reads, and is the only place that does — four screens each assembling them would be the same duplication arriving by a different door. Deleting the flag rather than giving it a lifecycle is the point. A stored copy has to be kept in agreement with the original forever, and that agreement is what broke. Four boolean facts allow sixteen combinations; three allow eight, and every one of the eight describes something physically true. The dead end is not fixed, it becomes unrepresentable. FOLLOWING THE VARIABLE, NOT THE SCREENS. Nine sites touched the key. Each reader was asked what it was really asking, and only one of the four was asking the question the flag pretended to answer: * LibraryActivity — "is there a system, or one on the way": migrated. * WizardActivity — "where do I resume": the flag half of that condition was always true, because LibraryActivity is the only thing that opens the wizard and only when there is nothing. allPermsGranted() was doing all the work. * SetupActivity — "am I the first-run wizard or am I Settings". A different question entirely, and the reason this step matters: migrating it to the presence rule would have produced a Settings screen that believes it is a wizard. It takes its mode from the Intent now, from the two callers that know. * MainActivity — still reachable, because it runs whenever Settings opens the Terminal. Migrated, and its catch no longer writes the flag to stop itself firing: the condition is re-derived every launch, so a missing Activity is a log line again instead of permanent state. With no readers, all five writers went, and the key with them — including the setup_complete → false that ADFA-5119 added a few hours ago. TWO THINGS THE ANALYSIS DID NOT PREDICT. A race on the happy path. startWizardInstall starts the service and the Activity one after the other with no ordering between them, so LibraryActivity.onCreate can run before InstallService.onStartCommand — and with the flag gone it would find no rootfs, no marker and no lock, and bounce the user back to the wizard on the install they had just asked for. The flag was hiding that. The marker is now planted where the user commits rather than whenever the scheduler reaches the service. If the service then never starts, the marker is left set and the next launch enters recovery — a state with a dialog and a way out, which is the right side to fail on. "Copy from a phone" needed a replacement, not a deletion. It is the one navigation that must land with no system and nothing yet in flight, which is why it wrote the flag. It now says so — EXTRA_SETTING_UP, an Intent extra that dies with the navigation. Leave before scanning and the next launch is back at the wizard, correctly, because nothing was started. THE SAFETY NET. H_NO_LIBRARY gets an action, "Install a system", routing to the tier step. It was the one header state offering nothing while FAILED offers Retry and INSTALLING offers See progress. This ticket also closes the way into that state, so in principle nobody arrives there — the button stays because "in principle" is what the last four dead ends had in common. Ledger: the setup_complete row is deleted rather than turned green. 34 files lose the key; the new string is translated into 33 locales. --- .../org/iiab/controller/MainActivity.java | 13 ++-- .../org/iiab/controller/SetupActivity.java | 19 ++++- .../iiab/controller/SetupSectionFragment.java | 7 +- .../install/presentation/InstallService.java | 20 ++--- .../controller/redesign/LibraryActivity.java | 29 +++++++- .../redesign/LibraryHomeFragment.java | 21 +++++- .../redesign/ModuleHubFragment.java | 8 +- .../redesign/SetupLibraryActivity.java | 24 ++++-- .../controller/redesign/WizardActivity.java | 27 ++++--- .../system/data/SystemPresenceReader.java | 60 +++++++++++++++ .../system/domain/SystemPresence.java | 53 +++++++++++++ .../app/src/main/res/values-ar/strings.xml | 1 - .../src/main/res/values-ar/strings_k2go.xml | 1 + .../app/src/main/res/values-az/strings.xml | 1 - .../src/main/res/values-az/strings_k2go.xml | 1 + .../app/src/main/res/values-bg/strings.xml | 1 - .../src/main/res/values-bg/strings_k2go.xml | 1 + .../app/src/main/res/values-bn/strings.xml | 1 - .../src/main/res/values-bn/strings_k2go.xml | 1 + .../app/src/main/res/values-cs/strings.xml | 1 - .../src/main/res/values-cs/strings_k2go.xml | 1 + .../app/src/main/res/values-de/strings.xml | 1 - .../src/main/res/values-de/strings_k2go.xml | 1 + .../app/src/main/res/values-el/strings.xml | 1 - .../src/main/res/values-el/strings_k2go.xml | 1 + .../app/src/main/res/values-es/strings.xml | 1 - .../src/main/res/values-es/strings_k2go.xml | 1 + .../app/src/main/res/values-fa/strings.xml | 1 - .../src/main/res/values-fa/strings_k2go.xml | 1 + .../app/src/main/res/values-fr/strings.xml | 1 - .../src/main/res/values-fr/strings_k2go.xml | 1 + .../app/src/main/res/values-gu/strings.xml | 1 - .../src/main/res/values-gu/strings_k2go.xml | 1 + .../app/src/main/res/values-hi/strings.xml | 1 - .../src/main/res/values-hi/strings_k2go.xml | 1 + .../app/src/main/res/values-hu/strings.xml | 1 - .../src/main/res/values-hu/strings_k2go.xml | 1 + .../app/src/main/res/values-in/strings.xml | 1 - .../src/main/res/values-in/strings_k2go.xml | 1 + .../app/src/main/res/values-it/strings.xml | 1 - .../src/main/res/values-it/strings_k2go.xml | 1 + .../app/src/main/res/values-ja/strings.xml | 1 - .../src/main/res/values-ja/strings_k2go.xml | 1 + .../app/src/main/res/values-ko/strings.xml | 1 - .../src/main/res/values-ko/strings_k2go.xml | 1 + .../app/src/main/res/values-lt/strings.xml | 1 - .../src/main/res/values-lt/strings_k2go.xml | 1 + .../app/src/main/res/values-nl/strings.xml | 1 - .../src/main/res/values-nl/strings_k2go.xml | 1 + .../app/src/main/res/values-no/strings.xml | 1 - .../src/main/res/values-no/strings_k2go.xml | 1 + .../app/src/main/res/values-pl/strings.xml | 1 - .../src/main/res/values-pl/strings_k2go.xml | 1 + .../app/src/main/res/values-pt/strings.xml | 1 - .../src/main/res/values-pt/strings_k2go.xml | 1 + .../app/src/main/res/values-ro/strings.xml | 1 - .../src/main/res/values-ro/strings_k2go.xml | 1 + .../src/main/res/values-ru-rRU/strings.xml | 1 - .../main/res/values-ru-rRU/strings_k2go.xml | 1 + .../app/src/main/res/values-sk/strings.xml | 1 - .../src/main/res/values-sk/strings_k2go.xml | 1 + .../app/src/main/res/values-sr/strings.xml | 1 - .../src/main/res/values-sr/strings_k2go.xml | 1 + .../app/src/main/res/values-sw/strings.xml | 1 - .../src/main/res/values-sw/strings_k2go.xml | 1 + .../app/src/main/res/values-ta/strings.xml | 1 - .../src/main/res/values-ta/strings_k2go.xml | 1 + .../app/src/main/res/values-tr/strings.xml | 1 - .../src/main/res/values-tr/strings_k2go.xml | 1 + .../app/src/main/res/values-uk/strings.xml | 1 - .../src/main/res/values-uk/strings_k2go.xml | 1 + .../app/src/main/res/values-vi/strings.xml | 1 - .../src/main/res/values-vi/strings_k2go.xml | 1 + .../app/src/main/res/values-yo/strings.xml | 1 - .../src/main/res/values-yo/strings_k2go.xml | 1 + .../src/main/res/values-zh-rCN/strings.xml | 1 - .../main/res/values-zh-rCN/strings_k2go.xml | 1 + .../app/src/main/res/values/strings.xml | 1 - .../app/src/main/res/values/strings_k2go.xml | 1 + .../system/domain/SystemPresenceTest.java | 74 +++++++++++++++++++ 80 files changed, 347 insertions(+), 76 deletions(-) create mode 100644 controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java create mode 100644 controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java create mode 100644 controller/app/src/test/java/org/iiab/controller/system/domain/SystemPresenceTest.java diff --git a/controller/app/src/main/java/org/iiab/controller/MainActivity.java b/controller/app/src/main/java/org/iiab/controller/MainActivity.java index 53ee682f8..8c23c18c0 100644 --- a/controller/app/src/main/java/org/iiab/controller/MainActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/MainActivity.java @@ -264,16 +264,19 @@ public void onReceive(Context context, Intent intent) { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Intercept launch and redirect to Setup Wizard if first time - SharedPreferences internalPrefs = getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE); - if (!internalPrefs.getBoolean(getString(R.string.pref_key_setup_complete), false)) { + // Intercept launch and redirect to the legacy Setup Wizard when there is nothing to run. + // ADFA-5137: asks the device rather than setup_complete, and says which mode it wants. The + // old catch wrote the flag true so this branch would stop firing; with the flag gone there is + // nothing to write, and nothing needs writing — the condition is re-derived every launch, so + // a missing Activity stops being permanent state and goes back to being a log line. + if (!org.iiab.controller.system.data.SystemPresenceReader.hereOrOnTheWay(this)) { try { - startActivity(new Intent(this, SetupActivity.class)); + startActivity(new Intent(this, SetupActivity.class) + .putExtra(SetupActivity.EXTRA_WIZARD_MODE, true)); finish(); return; // We stop the execution of MainActivity right here } catch (android.content.ActivityNotFoundException e) { android.util.Log.w(TAG, "SetupActivity not found. Skipping initial setup."); - internalPrefs.edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply(); } } diff --git a/controller/app/src/main/java/org/iiab/controller/SetupActivity.java b/controller/app/src/main/java/org/iiab/controller/SetupActivity.java index f9e69a506..8a6399bef 100644 --- a/controller/app/src/main/java/org/iiab/controller/SetupActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/SetupActivity.java @@ -31,6 +31,14 @@ */ public class SetupActivity extends AppCompatActivity { + /** + * ADFA-5137: true opens this screen as the first-run wizard, false as Settings. Set by whoever + * opens it, because the mode is a property of the reason for opening it and of nothing else. + * Absent means Settings, which is the safe default: a Settings screen is navigable and a wizard + * blocks Back. + */ + public static final String EXTRA_WIZARD_MODE = "org.iiab.controller.SETUP_WIZARD_MODE"; + private boolean wizardMode; @Override @@ -39,9 +47,14 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_setup); org.iiab.controller.help.TooltipWiring.wireAll(getWindow().getDecorView()); - SharedPreferences prefs = getSharedPreferences( - getString(R.string.pref_file_internal), Context.MODE_PRIVATE); - wizardMode = !prefs.getBoolean(getString(R.string.pref_key_setup_complete), false); + // ADFA-5137: the caller says which mode this is, because only the caller knows. + // + // It used to read setup_complete, and that was the one reader asking a genuinely different + // question: not "is there a system" but "am I the first-run wizard or am I Settings". Two + // callers open this screen for those two reasons — MainActivity's first-run redirect and its + // Settings button — so the answer belongs in the Intent. Migrating this one to the presence + // rule would have produced a Settings screen that believes it is a wizard. + wizardMode = getIntent() != null && getIntent().getBooleanExtra(EXTRA_WIZARD_MODE, false); View rail = findViewById(R.id.setup_rail); if (wizardMode) { diff --git a/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java b/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java index 4e473a108..96d5393dc 100644 --- a/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/SetupSectionFragment.java @@ -240,9 +240,10 @@ private void finishEnrollment(SharedPreferences delivery) { private void completeSetup() { // ADFA-4466 Phase 2: setup funnel completion (no-op unless opted in). org.iiab.controller.analytics.AnalyticsClient.with(requireContext()).logOnboardingCompleted(); - SharedPreferences prefs = requireContext().getSharedPreferences( - getString(R.string.pref_file_internal), Context.MODE_PRIVATE); - prefs.edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply(); + // ADFA-5137: nothing to mark. This screen belongs to the legacy setup shell, and it wrote + // setup_complete on its way out — a fourth writer of a flag nobody cleared. LibraryActivity + // now asks the device instead, so if this path really did install a system it will be found, + // and if it did not, the wizard is where the user should land. startActivity(new Intent(requireContext(), org.iiab.controller.redesign.LibraryActivity.class)); requireActivity().finish(); } diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java index ea00f7b54..c39bea6f5 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java @@ -1523,11 +1523,13 @@ private void doCancel() { * the content chosen for the tier being given up is not drained into the next one. *
  • {@code installed_tier}, which a later "Get more" reads to size content against a * system that was never installed.
  • - *
  • {@code setup_complete}, which is what decides whether the next launch opens the - * library or the wizard. This is the one that keeps the promise: no path ends in the app - * with no system.
  • * * + *

    There used to be a fifth: {@code setup_complete} → false, which was what kept the promise that + * no path ends in the app with no system. ADFA-5137 deleted the flag, so the promise is now kept by + * not having anything to unset — the launch asks whether a rootfs, an install or a deep operation + * is there, and after this cleanup none of the three is. + * *

    The install marker is a sixth, and it is already handled — {@link #teardown()} clears it on * every clean terminal. Left set, the next launch would open in damaged-system recovery over a * system nobody asked for. @@ -1570,16 +1572,16 @@ private void forgetTheAbandonedSystem() { // reinstall. Clearing setup_complete first and dying here would instead send the user to // the wizard while the marker still says an install is running. // - // commit(), not apply(): the state posted below sends the UI - // to the tier selection immediately, and a later cold launch reads setup_complete to - // decide between the library and the wizard. An asynchronous write is a race with both. + // commit(), not apply(): the state posted below sends the UI to the tier selection + // immediately, and an asynchronous write would race it. // - // We are the first writer to set setup_complete false — the other four only ever set it - // true, which is why an abandoned install used to strand the user on an empty library. + // ADFA-5119 added a setup_complete → false here and called itself the first writer of + // false among five. ADFA-5137 removed the flag altogether, so there is nothing to unset: + // the marker cleared by teardown() and the absent rootfs now say the same thing between + // them, and they cannot disagree with each other the way the flag could disagree with both. getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE) .edit() .remove("installed_tier") - .putBoolean(getString(R.string.pref_key_setup_complete), false) .commit(); } catch (Exception e) { // Never leave the UI waiting because a cleanup step failed: the state below is what diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java index f0734e664..db01b7449 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java @@ -40,6 +40,19 @@ public class LibraryActivity extends AppCompatActivity implements ServerControll public static final String EXTRA_INSTALLING = "installing"; /** ADFA-4777: preselect a bottom-nav tab on launch (e.g. from the wizard's "Copy from a phone"). */ public static final String EXTRA_TAB = "tab"; + /** + * ADFA-5137: the caller knows there is no system and is bringing the user here to get one. + * + *

    Only the wizard's "Copy from a phone" sets it. That choice has to land on the Clone tab with + * nothing installed and nothing yet in flight, which is precisely the state that otherwise sends + * the user back to the wizard — so before this ticket the wizard wrote {@code setup_complete} to + * get past the check, and that lie is the entrance to findings 3 and 5. + * + *

    An Intent extra rather than a stored fact, deliberately: it dies with this navigation. If the + * user leaves before scanning anything, the next launch finds no system and no lock and opens the + * wizard — which is the truth, because nothing was started. + */ + public static final String EXTRA_SETTING_UP = "settingUp"; private boolean installing = false; /** ADFA-4799: bottom bar (compact) and rail (medium/expanded) share the NavigationBarView @@ -97,10 +110,18 @@ public class LibraryActivity extends AppCompatActivity implements ServerControll protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Not set up yet? Run the first-run wizard, then it routes back here. - SharedPreferences prefs0 = getSharedPreferences( - getString(R.string.pref_file_internal), MODE_PRIVATE); - if (!prefs0.getBoolean(getString(R.string.pref_key_setup_complete), false)) { + // ADFA-5137: nothing here and nothing coming? Run the first-run wizard, then it routes back. + // + // This used to read setup_complete, a flag written when an install STARTED and cleared by + // nobody — so it could say "set up" while the device had no system, and then this branch + // routed past the wizard forever. That pair is findings 3 and 5 of state-spine.svg. The + // question was never "did setup happen": it is "is there a system, or one on the way", and + // that is answerable from the disk and the two markers, none of which can drift from what + // they describe. + boolean broughtHereToSetUp = getIntent() != null + && getIntent().getBooleanExtra(EXTRA_SETTING_UP, false); + if (!broughtHereToSetUp + && !org.iiab.controller.system.data.SystemPresenceReader.hereOrOnTheWay(this)) { startActivity(new Intent(this, WizardActivity.class)); finish(); return; diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java index a8e543335..4cea8f302 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java @@ -105,6 +105,14 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c requireContext(), SetupProgressActivity.class)); return; } + // ADFA-5137: with no system, the way forward is choosing one — the tier step, the same + // place the wizard sends people. Named, on the header, instead of a sentence pointing + // at a control at the bottom of the screen called "Get more". + if (headerState == H_NO_LIBRARY) { + startActivity(new android.content.Intent( + requireContext(), SetupLibraryActivity.class)); + return; + } // ADFA-4837: retry only when it is genuinely safe — canStartServer guards // against stacking a second proot over a live one. if (headerState != H_FAILED) return; @@ -572,8 +580,19 @@ private void setHeader(int h) { // app working and gets a spinner; the rest are statements and get nothing. The status // colour lives on the dot only — the button wears the brand colour, because it is a // control rather than a severity. + // ADFA-5137: H_NO_LIBRARY gets one too. It was the only state here that offered nothing, and + // that is finding 5 of state-spine.svg: the header said "tap Get more to install" while being + // plain text, pointing at a control at the far bottom of the screen whose name says content + // rather than system. Meanwhile both cards on the way there offer Install and Schedule, and + // both refuse. + // + // ADFA-5137 also closes the way INTO this state, so in principle nobody arrives here any more. + // The button stays anyway, because "in principle" is what the last four dead ends had in + // common: a state with no exit is a bug whoever reaches it, including by a route that does not + // exist yet. One line in a switch that already hands out two other buttons. int action = h == H_FAILED ? R.string.k2go_home_retry - : h == H_INSTALLING ? R.string.k2go_home_see_progress : 0; + : h == H_INSTALLING ? R.string.k2go_home_see_progress + : h == H_NO_LIBRARY ? R.string.k2go_home_install_system : 0; if (homeStatusAction != null) { homeStatusAction.setVisibility(action != 0 ? View.VISIBLE : View.GONE); if (action != 0) homeStatusAction.setText(action); diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java index 64ce9e508..42597ce8e 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java @@ -65,9 +65,11 @@ public class ModuleHubFragment extends Fragment { * with. The screen still listed all six as installable, because it decided that from a probe * — and with no system nothing answers, so everything looked missing. * - *

    Reachable, not theoretical: {@code pref_key_setup_complete} is only ever written true — - * five writers, none of them clears it — so after a reset or a failed restore the app still - * routes to the Library, and Settings still opens this screen over an empty rootfs. + *

    Reachable, not theoretical — and it stays reachable after ADFA-5137. That ticket removed the + * flag this note used to blame ({@code setup_complete}, written true by five sites and cleared by + * none), so the app no longer routes to the Library after a reset or a failed restore. But Settings + * still opens this screen directly, and it can be opened over an empty rootfs whenever the user + * gets there some other way, so the precondition still has to be asked here. * *

    Seeded true so the first frame looks like the ordinary case; the background pass * corrects it before anything is offered. diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java index 5c7c1caf3..88ed51552 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/SetupLibraryActivity.java @@ -277,11 +277,25 @@ public void startWizardInstall() { // Fire exactly once. (The install service also dedupes the actual install via its `started` guard.) if (installStarting) return; installStarting = true; - // ADFA-4982: the real install is starting — mark setup complete NOW (it is no longer set at the - // wizard's "download" choice, so bailing before this resumes the wizard). This also lets the - // install LibraryActivity below show progress instead of redirecting back to the wizard. - getSharedPreferences(getString(R.string.pref_file_internal), MODE_PRIVATE) - .edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply(); + // ADFA-5137: nothing is written here any more. ADFA-4982 set setup_complete at this line so the + // LibraryActivity below would show progress instead of bouncing back to the wizard — which + // worked, and was also the bug: it claimed setup was done at the moment an install BEGAN, and + // then nobody unclaimed it if the install never finished. LibraryActivity now asks whether a + // system is here or on the way, and the install marker this service is about to plant answers + // yes for exactly as long as the install runs. + // + // Which is why the marker is planted HERE, before the service is asked to start. Both the + // service start and the Activity start below are asynchronous dispatches with no ordering + // between them, so LibraryActivity.onCreate can run before InstallService.onStartCommand — and + // it would then find no rootfs, no marker and no lock, and bounce the user back to the wizard + // on the one path that matters most. The marker is a file, so writing it here makes the fact + // true at the moment the user commits rather than whenever the scheduler gets to the service. + // InstallGuard.begin is idempotent, so the service planting it again costs nothing. + // + // The trade-off, stated: if the service never starts at all, the marker is left set with no + // install behind it, and the next launch enters recovery. That is a state with a dialog and a + // way out (ADFA-5119) rather than a silent dead end, which is the right side to fail on. + org.iiab.controller.InstallGuard.begin(this); Intent i = new Intent(this, InstallService.class); i.setAction(InstallService.ACTION_START); i.putExtra(InstallService.EXTRA_TIER, getSelectedTier().name()); diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java index 128cc29b5..2a1c408f4 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/WizardActivity.java @@ -61,10 +61,14 @@ protected void onCreate(Bundle b) { // applied language, so we don't flash back to the welcome step. langTag = AppLocaleController.currentTag(); if (b != null) step = b.getInt("step", 0); - // ADFA-4982: a fresh launch that isn't complete yet but already has permissions means the user - // passed language + permissions and only bailed from the setup choice / edition selection — - // resume at the setup choice (step 3), not welcome/language/permissions all over again. - else if (!prefs().getBoolean(getString(R.string.pref_key_setup_complete), false) && allPermsGranted()) step = 3; + // ADFA-4982: a fresh launch that already has permissions means the user passed language + + // permissions and only bailed from the setup choice / edition selection — resume at the setup + // choice (step 3), not welcome/language/permissions all over again. + // + // ADFA-5137 dropped the setup_complete half of this condition, which was always true here: + // LibraryActivity is the only thing that opens this screen, and it only opens it when there + // is no system and none on the way. The permissions check was doing all the work. + else if (allPermsGranted()) step = 3; title = findViewById(R.id.wiz_title); subtitle = findViewById(R.id.wiz_subtitle); primary = findViewById(R.id.wiz_primary); @@ -99,10 +103,14 @@ protected void onCreate(Bundle b) { finish(); }); findViewById(R.id.setup_copy).setOnClickListener(v -> { - markComplete(); // ADFA-4777: "Copy from a phone" lands directly on the (now functional) Clone tab. + // ADFA-5137: it used to write setup_complete to get past LibraryActivity's check, because + // this is the one navigation that must land with no system and nothing yet in flight. It + // now says that, instead of claiming setup is finished. The difference is that the extra + // dies with this Intent: leave before scanning and the next launch is back here, correctly. startActivity(new Intent(this, LibraryActivity.class) - .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_clone)); + .putExtra(LibraryActivity.EXTRA_TAB, R.id.nav_clone) + .putExtra(LibraryActivity.EXTRA_SETTING_UP, true)); finish(); }); primary.setOnClickListener(v -> onPrimary()); @@ -304,7 +312,8 @@ private void requestBattery() { private SharedPreferences prefs() { return getSharedPreferences(getString(R.string.pref_file_internal), MODE_PRIVATE); } - private void markComplete() { - prefs().edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply(); - } + // ADFA-5137: markComplete() is gone. It wrote setup_complete on the "Copy from a phone" choice — + // before a transfer existed, let alone succeeded — which is the entrance to finding 3 that survived + // ADFA-4982 and ADFA-5116. Nothing replaces it: the clone takes the environment lock while it runs, + // and that is what SystemPresence reads. } diff --git a/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java b/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java new file mode 100644 index 000000000..e517ca67b --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java @@ -0,0 +1,60 @@ +/* + * ============================================================================ + * Name : SystemPresenceReader.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5137. Gathers the three facts SystemPresence needs, from + * the disk and the two markers. The only place that gathers them. + * ============================================================================ + */ +package org.iiab.controller.system.data; + +import android.content.Context; + +import org.iiab.controller.InstallGuard; +import org.iiab.controller.SystemStateEvaluator; +import org.iiab.controller.env.EnvironmentLock; +import org.iiab.controller.system.domain.SystemPresence; + +/** + * Asks the device whether it has a system, or one on the way. + * + *

    The rule is in {@link SystemPresence}, where it can be tested without a device. This is the + * Android half: three reads, in one place. It matters that it is one place — four screens each + * assembling the same three calls would be the duplication ADFA-5137 exists to remove, arriving by a + * different door. + * + *

    Each fact is read from the thing it describes, which is the property the old flag lacked: + * + *

    + * + *

    Touches the filesystem, so it is not free; all three reads are file existence checks, which is + * the same cost the launch path already pays. + */ +public final class SystemPresenceReader { + + private SystemPresenceReader() { + } + + /** False only when the device has nothing and nothing is on its way. */ + public static boolean hereOrOnTheWay(Context ctx) { + if (ctx == null) { + // No context, no evidence. Answering true keeps a caller from routing a user into setup + // on the strength of a missing argument; the wizard is a decision, not a default. + return true; + } + return SystemPresence.hereOrOnTheWay( + SystemStateEvaluator.rootfsPresent(ctx), + InstallGuard.inProgress(ctx), + EnvironmentLock.ownerHeld(ctx)); + } +} diff --git a/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java b/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java new file mode 100644 index 000000000..70f39308c --- /dev/null +++ b/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java @@ -0,0 +1,53 @@ +/* + * ============================================================================ + * Name : SystemPresence.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : ADFA-5137. The rule that replaces setup_complete: does this + * device have a system, or is one on its way? Pure JVM. + * ============================================================================ + */ +package org.iiab.controller.system.domain; + +/** + * Whether this device has a system, or one is being put there right now. + * + *

    What this replaces, and why. {@code setup_complete} was a claim about the past — "setup + * happened" — used to answer a question about the present: "should this person see the wizard?". + * Four sites wrote it, none cleared it, and it was written on intent rather than on a finished + * install, so it could say yes while the device had nothing. That combination is findings 3 and 5 of + * {@code state-spine.svg}: an app that routes past the wizard forever, onto a screen whose only way + * forward has no button. + * + *

    The fix is not to give that flag a lifecycle but to stop storing it. The answer is already on + * the device, in three facts that each die with the thing they describe — the rootfs with the rootfs, + * the install marker with the install, the lock with the operation. A fact that can be derived should + * not be kept, because a kept copy has to be maintained in agreement with the original forever, and + * that agreement is exactly what broke. + * + *

    Why a disjunction and not just "is there a rootfs". Two of the three are about work in + * flight rather than about a system that exists. Without them, a user who is halfway through a first + * install — or halfway through receiving a clone, which has no rootfs yet by definition — would be + * sent back to the wizard on every relaunch, which is the same dead end in the other direction. The + * question is not "is it finished" but "is anything there or coming". + * + *

    Pure: booleans in, boolean out, no Android. {@code SystemPresenceReader} is what gathers the + * three facts, and it is the only place that does. + */ +public final class SystemPresence { + + private SystemPresence() { + } + + /** + * @param rootfsOnDisk a rootfs exists — asked of the disk, not of a flag + * @param installInProgress the durable install marker is set + * @param deepOpInFlight a clone, backup or restore owns the environment lock + * @return false only when the device has nothing and nothing is on its way, which is the one + * case where the wizard is the right place to be + */ + public static boolean hereOrOnTheWay(boolean rootfsOnDisk, boolean installInProgress, + boolean deepOpInFlight) { + return rootfsOnDisk || installInProgress || deepOpInFlight; + } +} diff --git a/controller/app/src/main/res/values-ar/strings.xml b/controller/app/src/main/res/values-ar/strings.xml index 3b97ee2bb..32cb2184d 100644 --- a/controller/app/src/main/res/values-ar/strings.xml +++ b/controller/app/src/main/res/values-ar/strings.xml @@ -133,7 +133,6 @@ الذهاب إلى الإعدادات بدأ التطبيق IIAB_Internal - setup_complete تم الحفظ تم حفظ الإعدادات Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ar/strings_k2go.xml b/controller/app/src/main/res/values-ar/strings_k2go.xml index 86b268fc2..0848a2184 100644 --- a/controller/app/src/main/res/values-ar/strings_k2go.xml +++ b/controller/app/src/main/res/values-ar/strings_k2go.xml @@ -43,6 +43,7 @@ تعذّر البدء عرض التقدم إعادة المحاولة + تثبيت نظام جارٍ إضافة المحتوى diff --git a/controller/app/src/main/res/values-az/strings.xml b/controller/app/src/main/res/values-az/strings.xml index 265a538ec..ee888f40f 100644 --- a/controller/app/src/main/res/values-az/strings.xml +++ b/controller/app/src/main/res/values-az/strings.xml @@ -133,7 +133,6 @@ Tənzimləmələrə keç Tətbiq başladıldı IIAB_Internal - setup_complete Yadda saxlanıldı Tənzimləmələr yadda saxlanıldı Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-az/strings_k2go.xml b/controller/app/src/main/res/values-az/strings_k2go.xml index f041d9414..17759a27f 100644 --- a/controller/app/src/main/res/values-az/strings_k2go.xml +++ b/controller/app/src/main/res/values-az/strings_k2go.xml @@ -43,6 +43,7 @@ Başlamadı Gedişata bax Yenidən cəhd + Sistem qur Məzmun əlavə edilir diff --git a/controller/app/src/main/res/values-bg/strings.xml b/controller/app/src/main/res/values-bg/strings.xml index 798ac599f..29566a1f8 100644 --- a/controller/app/src/main/res/values-bg/strings.xml +++ b/controller/app/src/main/res/values-bg/strings.xml @@ -133,7 +133,6 @@ Към настройките Приложението е стартирано IIAB_Internal - setup_complete Запазено Настройките са запазени Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-bg/strings_k2go.xml b/controller/app/src/main/res/values-bg/strings_k2go.xml index 0420c6810..b176286b8 100644 --- a/controller/app/src/main/res/values-bg/strings_k2go.xml +++ b/controller/app/src/main/res/values-bg/strings_k2go.xml @@ -43,6 +43,7 @@ Неуспешно стартиране Виж напредъка Опитай пак + Инсталирай система Добавяне на съдържание diff --git a/controller/app/src/main/res/values-bn/strings.xml b/controller/app/src/main/res/values-bn/strings.xml index c25574c08..c6b3daadf 100644 --- a/controller/app/src/main/res/values-bn/strings.xml +++ b/controller/app/src/main/res/values-bn/strings.xml @@ -133,7 +133,6 @@ সেটিংসে যান অ্যাপ্লিকেশন শুরু হয়েছে IIAB_Internal - setup_complete সংরক্ষিত হয়েছে সেটিংস সংরক্ষিত হয়েছে Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-bn/strings_k2go.xml b/controller/app/src/main/res/values-bn/strings_k2go.xml index 887217e64..0a1a29284 100644 --- a/controller/app/src/main/res/values-bn/strings_k2go.xml +++ b/controller/app/src/main/res/values-bn/strings_k2go.xml @@ -43,6 +43,7 @@ শুরু করা যায়নি অগ্রগতি দেখুন আবার চেষ্টা + সিস্টেম ইনস্টল করুন কনটেন্ট যোগ করা হচ্ছে diff --git a/controller/app/src/main/res/values-cs/strings.xml b/controller/app/src/main/res/values-cs/strings.xml index d4c58f64f..a2cc78d0e 100644 --- a/controller/app/src/main/res/values-cs/strings.xml +++ b/controller/app/src/main/res/values-cs/strings.xml @@ -133,7 +133,6 @@ Přejít do nastavení Aplikace spuštěna IIAB_Internal - setup_complete Uloženo Nastavení uloženo Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-cs/strings_k2go.xml b/controller/app/src/main/res/values-cs/strings_k2go.xml index c7c27c5fe..df38398f6 100644 --- a/controller/app/src/main/res/values-cs/strings_k2go.xml +++ b/controller/app/src/main/res/values-cs/strings_k2go.xml @@ -43,6 +43,7 @@ Nepodařilo se spustit Zobrazit průběh Zkusit znovu + Nainstalovat systém Přidává se obsah diff --git a/controller/app/src/main/res/values-de/strings.xml b/controller/app/src/main/res/values-de/strings.xml index 1f453e558..603896765 100644 --- a/controller/app/src/main/res/values-de/strings.xml +++ b/controller/app/src/main/res/values-de/strings.xml @@ -133,7 +133,6 @@ Zu den Einstellungen Anwendung gestartet IIAB_Internal - setup_complete Gespeichert Einstellungen gespeichert Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-de/strings_k2go.xml b/controller/app/src/main/res/values-de/strings_k2go.xml index 51ac15ed2..20452cb0c 100644 --- a/controller/app/src/main/res/values-de/strings_k2go.xml +++ b/controller/app/src/main/res/values-de/strings_k2go.xml @@ -44,6 +44,7 @@ Start fehlgeschlagen Fortschritt ansehen Erneut versuchen + System installieren Inhalte werden hinzugefügt diff --git a/controller/app/src/main/res/values-el/strings.xml b/controller/app/src/main/res/values-el/strings.xml index f892dcf15..56e2639df 100644 --- a/controller/app/src/main/res/values-el/strings.xml +++ b/controller/app/src/main/res/values-el/strings.xml @@ -133,7 +133,6 @@ Μετάβαση στις ρυθμίσεις Η εφαρμογή ξεκίνησε IIAB_Internal - setup_complete Αποθηκεύτηκε Οι ρυθμίσεις αποθηκεύτηκαν Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-el/strings_k2go.xml b/controller/app/src/main/res/values-el/strings_k2go.xml index c40af7519..81b49822b 100644 --- a/controller/app/src/main/res/values-el/strings_k2go.xml +++ b/controller/app/src/main/res/values-el/strings_k2go.xml @@ -44,6 +44,7 @@ Δεν ήταν δυνατή η εκκίνηση Δείτε την πρόοδο Επανάληψη + Εγκατάσταση συστήματος Προσθήκη περιεχομένου diff --git a/controller/app/src/main/res/values-es/strings.xml b/controller/app/src/main/res/values-es/strings.xml index b0e3f0ca3..d176185f5 100644 --- a/controller/app/src/main/res/values-es/strings.xml +++ b/controller/app/src/main/res/values-es/strings.xml @@ -147,7 +147,6 @@ Aplicación Iniciada IIAB_Internal - setup_complete Guardado Ajustes Guardados Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-es/strings_k2go.xml b/controller/app/src/main/res/values-es/strings_k2go.xml index 565ea54c2..512cad890 100644 --- a/controller/app/src/main/res/values-es/strings_k2go.xml +++ b/controller/app/src/main/res/values-es/strings_k2go.xml @@ -43,6 +43,7 @@ No se pudo iniciar Ver progreso Reintentar + Instalar un sistema Añadiendo contenido diff --git a/controller/app/src/main/res/values-fa/strings.xml b/controller/app/src/main/res/values-fa/strings.xml index e1630d2fa..10365d00c 100644 --- a/controller/app/src/main/res/values-fa/strings.xml +++ b/controller/app/src/main/res/values-fa/strings.xml @@ -133,7 +133,6 @@ رفتن به تنظیمات برنامه شروع شد IIAB_Internal - setup_complete ذخیره شد تنظیمات ذخیره شد Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-fa/strings_k2go.xml b/controller/app/src/main/res/values-fa/strings_k2go.xml index dd1d825f4..79904f04b 100644 --- a/controller/app/src/main/res/values-fa/strings_k2go.xml +++ b/controller/app/src/main/res/values-fa/strings_k2go.xml @@ -43,6 +43,7 @@ شروع نشد مشاهده پیشرفت تلاش دوباره + نصب سیستم در حال افزودن محتوا diff --git a/controller/app/src/main/res/values-fr/strings.xml b/controller/app/src/main/res/values-fr/strings.xml index 1429b0360..1685702e2 100644 --- a/controller/app/src/main/res/values-fr/strings.xml +++ b/controller/app/src/main/res/values-fr/strings.xml @@ -151,7 +151,6 @@ Application démarrée IIAB_Internal - setup_complete Enregistré Paramètres enregistrés Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-fr/strings_k2go.xml b/controller/app/src/main/res/values-fr/strings_k2go.xml index 20628278a..586875fd1 100644 --- a/controller/app/src/main/res/values-fr/strings_k2go.xml +++ b/controller/app/src/main/res/values-fr/strings_k2go.xml @@ -43,6 +43,7 @@ Échec du démarrage Voir la progression Réessayer + Installer un système Ajout de contenu diff --git a/controller/app/src/main/res/values-gu/strings.xml b/controller/app/src/main/res/values-gu/strings.xml index 67602c5db..8ee3ce70e 100644 --- a/controller/app/src/main/res/values-gu/strings.xml +++ b/controller/app/src/main/res/values-gu/strings.xml @@ -133,7 +133,6 @@ સેટિંગ્સ પર જાઓ ઍપ્લિકેશન શરૂ થઈ IIAB_Internal - setup_complete સાચવ્યું સેટિંગ્સ સાચવ્યાં Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-gu/strings_k2go.xml b/controller/app/src/main/res/values-gu/strings_k2go.xml index f54ad6d3d..54305675a 100644 --- a/controller/app/src/main/res/values-gu/strings_k2go.xml +++ b/controller/app/src/main/res/values-gu/strings_k2go.xml @@ -43,6 +43,7 @@ શરૂ ન થઈ શક્યું પ્રગતિ જુઓ ફરી પ્રયાસ + સિસ્ટમ ઇન્સ્ટોલ કરો સામગ્રી ઉમેરાઈ રહી છે diff --git a/controller/app/src/main/res/values-hi/strings.xml b/controller/app/src/main/res/values-hi/strings.xml index d7e4d6883..9f1acc664 100644 --- a/controller/app/src/main/res/values-hi/strings.xml +++ b/controller/app/src/main/res/values-hi/strings.xml @@ -151,7 +151,6 @@ एप्लिकेशन शुरू हुआ IIAB_Internal - setup_complete सहेजा गया सेटिंग्स सहेजी गईं Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-hi/strings_k2go.xml b/controller/app/src/main/res/values-hi/strings_k2go.xml index 72d09978c..07f70f928 100644 --- a/controller/app/src/main/res/values-hi/strings_k2go.xml +++ b/controller/app/src/main/res/values-hi/strings_k2go.xml @@ -43,6 +43,7 @@ शुरू नहीं हो सका प्रगति देखें पुनः प्रयास + सिस्टम इंस्टॉल करें सामग्री जोड़ी जा रही है diff --git a/controller/app/src/main/res/values-hu/strings.xml b/controller/app/src/main/res/values-hu/strings.xml index 4e7b05b5e..eb749fe53 100644 --- a/controller/app/src/main/res/values-hu/strings.xml +++ b/controller/app/src/main/res/values-hu/strings.xml @@ -133,7 +133,6 @@ Ugrás a beállításokhoz Az alkalmazás elindult IIAB_Internal - setup_complete Mentve Beállítások mentve Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-hu/strings_k2go.xml b/controller/app/src/main/res/values-hu/strings_k2go.xml index 722178ffe..2453b4fcb 100644 --- a/controller/app/src/main/res/values-hu/strings_k2go.xml +++ b/controller/app/src/main/res/values-hu/strings_k2go.xml @@ -43,6 +43,7 @@ Nem sikerült elindítani Folyamat Újra + Rendszer telepítése Tartalom hozzáadása diff --git a/controller/app/src/main/res/values-in/strings.xml b/controller/app/src/main/res/values-in/strings.xml index 33ff3e323..d48b44969 100644 --- a/controller/app/src/main/res/values-in/strings.xml +++ b/controller/app/src/main/res/values-in/strings.xml @@ -133,7 +133,6 @@ Buka Pengaturan Aplikasi Dimulai IIAB_Internal - setup_complete Tersimpan Pengaturan Disimpan Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-in/strings_k2go.xml b/controller/app/src/main/res/values-in/strings_k2go.xml index 9605f4561..1d5a9c5f6 100644 --- a/controller/app/src/main/res/values-in/strings_k2go.xml +++ b/controller/app/src/main/res/values-in/strings_k2go.xml @@ -43,6 +43,7 @@ Tidak bisa memulai Lihat progres Coba lagi + Pasang sistem Menambahkan konten diff --git a/controller/app/src/main/res/values-it/strings.xml b/controller/app/src/main/res/values-it/strings.xml index b407ac146..4cdb47144 100644 --- a/controller/app/src/main/res/values-it/strings.xml +++ b/controller/app/src/main/res/values-it/strings.xml @@ -133,7 +133,6 @@ Vai alle impostazioni Applicazione avviata IIAB_Internal - setup_complete Salvato Impostazioni salvate Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-it/strings_k2go.xml b/controller/app/src/main/res/values-it/strings_k2go.xml index 87ec55c68..cff5b591d 100644 --- a/controller/app/src/main/res/values-it/strings_k2go.xml +++ b/controller/app/src/main/res/values-it/strings_k2go.xml @@ -43,6 +43,7 @@ Avvio non riuscito Vedi progresso Riprova + Installa un sistema Aggiunta di contenuti diff --git a/controller/app/src/main/res/values-ja/strings.xml b/controller/app/src/main/res/values-ja/strings.xml index cf755c535..c5d8446e6 100644 --- a/controller/app/src/main/res/values-ja/strings.xml +++ b/controller/app/src/main/res/values-ja/strings.xml @@ -133,7 +133,6 @@ 設定へ移動 アプリケーションが起動しました IIAB_Internal - setup_complete 保存しました 設定を保存しました Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ja/strings_k2go.xml b/controller/app/src/main/res/values-ja/strings_k2go.xml index 0208a3dab..29995bd65 100644 --- a/controller/app/src/main/res/values-ja/strings_k2go.xml +++ b/controller/app/src/main/res/values-ja/strings_k2go.xml @@ -43,6 +43,7 @@ 起動できませんでした 進行状況 再試行 + システムをインストール コンテンツを追加中 diff --git a/controller/app/src/main/res/values-ko/strings.xml b/controller/app/src/main/res/values-ko/strings.xml index c2cce749b..f36436b5f 100644 --- a/controller/app/src/main/res/values-ko/strings.xml +++ b/controller/app/src/main/res/values-ko/strings.xml @@ -133,7 +133,6 @@ 설정으로 이동 애플리케이션 시작됨 IIAB_Internal - setup_complete 저장됨 설정이 저장되었습니다 Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ko/strings_k2go.xml b/controller/app/src/main/res/values-ko/strings_k2go.xml index 47515348f..2544fb104 100644 --- a/controller/app/src/main/res/values-ko/strings_k2go.xml +++ b/controller/app/src/main/res/values-ko/strings_k2go.xml @@ -43,6 +43,7 @@ 시작할 수 없음 진행 상황 다시 시도 + 시스템 설치 콘텐츠 추가 중 diff --git a/controller/app/src/main/res/values-lt/strings.xml b/controller/app/src/main/res/values-lt/strings.xml index 87c71f8d9..631858caa 100644 --- a/controller/app/src/main/res/values-lt/strings.xml +++ b/controller/app/src/main/res/values-lt/strings.xml @@ -133,7 +133,6 @@ Eiti į nustatymus Programa paleista IIAB_Internal - setup_complete Išsaugota Nustatymai išsaugoti Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-lt/strings_k2go.xml b/controller/app/src/main/res/values-lt/strings_k2go.xml index 55301f650..5ccd9e6f5 100644 --- a/controller/app/src/main/res/values-lt/strings_k2go.xml +++ b/controller/app/src/main/res/values-lt/strings_k2go.xml @@ -43,6 +43,7 @@ Nepavyko paleisti Žiūrėti eigą Bandyti dar kartą + Įdiegti sistemą Pridedamas turinys diff --git a/controller/app/src/main/res/values-nl/strings.xml b/controller/app/src/main/res/values-nl/strings.xml index e660ce28d..89a2ff431 100644 --- a/controller/app/src/main/res/values-nl/strings.xml +++ b/controller/app/src/main/res/values-nl/strings.xml @@ -133,7 +133,6 @@ Naar instellingen Applicatie gestart IIAB_Internal - setup_complete Opgeslagen Instellingen opgeslagen Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-nl/strings_k2go.xml b/controller/app/src/main/res/values-nl/strings_k2go.xml index 53e6cd8e1..36329ce17 100644 --- a/controller/app/src/main/res/values-nl/strings_k2go.xml +++ b/controller/app/src/main/res/values-nl/strings_k2go.xml @@ -43,6 +43,7 @@ Starten mislukt Voortgang Opnieuw + Systeem installeren Inhoud toevoegen diff --git a/controller/app/src/main/res/values-no/strings.xml b/controller/app/src/main/res/values-no/strings.xml index 5343b0c4e..e1f3e019a 100644 --- a/controller/app/src/main/res/values-no/strings.xml +++ b/controller/app/src/main/res/values-no/strings.xml @@ -133,7 +133,6 @@ Gå til innstillinger Applikasjon startet IIAB_Internal - setup_complete Lagret Innstillinger lagret Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-no/strings_k2go.xml b/controller/app/src/main/res/values-no/strings_k2go.xml index 90c6397b4..f3935a5b4 100644 --- a/controller/app/src/main/res/values-no/strings_k2go.xml +++ b/controller/app/src/main/res/values-no/strings_k2go.xml @@ -43,6 +43,7 @@ Kunne ikke starte Se fremdrift Prøv igjen + Installer et system Legger til innhold diff --git a/controller/app/src/main/res/values-pl/strings.xml b/controller/app/src/main/res/values-pl/strings.xml index 61c2a27df..bba0ff6cb 100644 --- a/controller/app/src/main/res/values-pl/strings.xml +++ b/controller/app/src/main/res/values-pl/strings.xml @@ -133,7 +133,6 @@ Przejdź do ustawień Aplikacja uruchomiona IIAB_Internal - setup_complete Zapisano Ustawienia zapisane Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-pl/strings_k2go.xml b/controller/app/src/main/res/values-pl/strings_k2go.xml index 62987a46c..8af358feb 100644 --- a/controller/app/src/main/res/values-pl/strings_k2go.xml +++ b/controller/app/src/main/res/values-pl/strings_k2go.xml @@ -43,6 +43,7 @@ Nie udało się uruchomić Zobacz postęp Ponów + Zainstaluj system Dodawanie treści diff --git a/controller/app/src/main/res/values-pt/strings.xml b/controller/app/src/main/res/values-pt/strings.xml index a589ce895..0eea3a1fa 100644 --- a/controller/app/src/main/res/values-pt/strings.xml +++ b/controller/app/src/main/res/values-pt/strings.xml @@ -151,7 +151,6 @@ Aplicativo Iniciado IIAB_Internal - setup_complete Salvo Configurações salvas Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-pt/strings_k2go.xml b/controller/app/src/main/res/values-pt/strings_k2go.xml index a6f7c8656..42207dd4d 100644 --- a/controller/app/src/main/res/values-pt/strings_k2go.xml +++ b/controller/app/src/main/res/values-pt/strings_k2go.xml @@ -43,6 +43,7 @@ Não foi possível iniciar Ver progresso Tentar novamente + Instalar um sistema Adicionando conteúdo diff --git a/controller/app/src/main/res/values-ro/strings.xml b/controller/app/src/main/res/values-ro/strings.xml index 5c6cef7f4..0ed18e3ab 100644 --- a/controller/app/src/main/res/values-ro/strings.xml +++ b/controller/app/src/main/res/values-ro/strings.xml @@ -133,7 +133,6 @@ Mergi la setări Aplicație pornită IIAB_Internal - setup_complete Salvat Setări salvate Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ro/strings_k2go.xml b/controller/app/src/main/res/values-ro/strings_k2go.xml index 413961042..64b77e38c 100644 --- a/controller/app/src/main/res/values-ro/strings_k2go.xml +++ b/controller/app/src/main/res/values-ro/strings_k2go.xml @@ -43,6 +43,7 @@ Nu a pornit Vezi progresul Reîncearcă + Instalează un sistem Se adaugă conținut diff --git a/controller/app/src/main/res/values-ru-rRU/strings.xml b/controller/app/src/main/res/values-ru-rRU/strings.xml index b39546592..7571a8b0e 100644 --- a/controller/app/src/main/res/values-ru-rRU/strings.xml +++ b/controller/app/src/main/res/values-ru-rRU/strings.xml @@ -151,7 +151,6 @@ Приложение запущено IIAB_Internal - setup_complete Сохранено Настройки сохранены Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml b/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml index 2b8a9259d..28f16a213 100644 --- a/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml +++ b/controller/app/src/main/res/values-ru-rRU/strings_k2go.xml @@ -43,6 +43,7 @@ Не удалось запустить Ход выполнения Повторить + Установить систему Добавление контента diff --git a/controller/app/src/main/res/values-sk/strings.xml b/controller/app/src/main/res/values-sk/strings.xml index ec9b28a9f..c1db7ab1e 100644 --- a/controller/app/src/main/res/values-sk/strings.xml +++ b/controller/app/src/main/res/values-sk/strings.xml @@ -133,7 +133,6 @@ Prejsť do nastavení Aplikácia spustená IIAB_Internal - setup_complete Uložené Nastavenia uložené Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-sk/strings_k2go.xml b/controller/app/src/main/res/values-sk/strings_k2go.xml index 7e89473a8..8049dbf3b 100644 --- a/controller/app/src/main/res/values-sk/strings_k2go.xml +++ b/controller/app/src/main/res/values-sk/strings_k2go.xml @@ -43,6 +43,7 @@ Nepodarilo sa spustiť Zobraziť priebeh Skúsiť znova + Nainštalovať systém Pridáva sa obsah diff --git a/controller/app/src/main/res/values-sr/strings.xml b/controller/app/src/main/res/values-sr/strings.xml index 04204c9c8..1ee145e20 100644 --- a/controller/app/src/main/res/values-sr/strings.xml +++ b/controller/app/src/main/res/values-sr/strings.xml @@ -133,7 +133,6 @@ Иди на подешавања Апликација покренута IIAB_Internal - setup_complete Сачувано Подешавања сачувана Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-sr/strings_k2go.xml b/controller/app/src/main/res/values-sr/strings_k2go.xml index bb69ac34c..08309339d 100644 --- a/controller/app/src/main/res/values-sr/strings_k2go.xml +++ b/controller/app/src/main/res/values-sr/strings_k2go.xml @@ -43,6 +43,7 @@ Покретање није успело Види напредак Покушај поново + Инсталирај систем Додавање садржаја diff --git a/controller/app/src/main/res/values-sw/strings.xml b/controller/app/src/main/res/values-sw/strings.xml index 9d77599c8..8527405bc 100644 --- a/controller/app/src/main/res/values-sw/strings.xml +++ b/controller/app/src/main/res/values-sw/strings.xml @@ -133,7 +133,6 @@ Nenda kwenye Mipangilio Programu Imeanzishwa IIAB_Internal - setup_complete Imehifadhiwa Mipangilio Imehifadhiwa Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-sw/strings_k2go.xml b/controller/app/src/main/res/values-sw/strings_k2go.xml index 27bb071df..54f9e26e2 100644 --- a/controller/app/src/main/res/values-sw/strings_k2go.xml +++ b/controller/app/src/main/res/values-sw/strings_k2go.xml @@ -43,6 +43,7 @@ Haikuweza kuanza Ona maendeleo Jaribu tena + Sakinisha mfumo Inaongeza maudhui diff --git a/controller/app/src/main/res/values-ta/strings.xml b/controller/app/src/main/res/values-ta/strings.xml index dd0189386..b99c9e0b4 100644 --- a/controller/app/src/main/res/values-ta/strings.xml +++ b/controller/app/src/main/res/values-ta/strings.xml @@ -133,7 +133,6 @@ அமைப்புகளுக்குச் செல் பயன்பாடு தொடங்கியது IIAB_Internal - setup_complete சேமிக்கப்பட்டது அமைப்புகள் சேமிக்கப்பட்டன Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-ta/strings_k2go.xml b/controller/app/src/main/res/values-ta/strings_k2go.xml index 0c7e8067d..d0c8340e9 100644 --- a/controller/app/src/main/res/values-ta/strings_k2go.xml +++ b/controller/app/src/main/res/values-ta/strings_k2go.xml @@ -43,6 +43,7 @@ தொடங்க முடியவில்லை முன்னேற்றம் மீண்டும் முயற்சி + கணினியை நிறுவு உள்ளடக்கம் சேர்க்கப்படுகிறது diff --git a/controller/app/src/main/res/values-tr/strings.xml b/controller/app/src/main/res/values-tr/strings.xml index c28152654..4a9d0e51f 100644 --- a/controller/app/src/main/res/values-tr/strings.xml +++ b/controller/app/src/main/res/values-tr/strings.xml @@ -133,7 +133,6 @@ Ayarlara git Uygulama başlatıldı IIAB_Internal - setup_complete Kaydedildi Ayarlar kaydedildi Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-tr/strings_k2go.xml b/controller/app/src/main/res/values-tr/strings_k2go.xml index 06d945e7a..025c3faa1 100644 --- a/controller/app/src/main/res/values-tr/strings_k2go.xml +++ b/controller/app/src/main/res/values-tr/strings_k2go.xml @@ -43,6 +43,7 @@ Başlatılamadı İlerlemeyi gör Yeniden dene + Sistem kur İçerik ekleniyor diff --git a/controller/app/src/main/res/values-uk/strings.xml b/controller/app/src/main/res/values-uk/strings.xml index da646933c..98d368704 100644 --- a/controller/app/src/main/res/values-uk/strings.xml +++ b/controller/app/src/main/res/values-uk/strings.xml @@ -133,7 +133,6 @@ Перейти до налаштувань Застосунок запущено IIAB_Internal - setup_complete Збережено Налаштування збережено Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-uk/strings_k2go.xml b/controller/app/src/main/res/values-uk/strings_k2go.xml index 708bdd381..99918eb34 100644 --- a/controller/app/src/main/res/values-uk/strings_k2go.xml +++ b/controller/app/src/main/res/values-uk/strings_k2go.xml @@ -43,6 +43,7 @@ Не вдалося запустити Переглянути Повторити + Встановити систему Додавання вмісту diff --git a/controller/app/src/main/res/values-vi/strings.xml b/controller/app/src/main/res/values-vi/strings.xml index 356d51190..56b8c07a9 100644 --- a/controller/app/src/main/res/values-vi/strings.xml +++ b/controller/app/src/main/res/values-vi/strings.xml @@ -133,7 +133,6 @@ Đến cài đặt Ứng dụng đã khởi động IIAB_Internal - setup_complete Đã lưu Đã lưu cài đặt Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-vi/strings_k2go.xml b/controller/app/src/main/res/values-vi/strings_k2go.xml index f75fb5db3..80dbb8f32 100644 --- a/controller/app/src/main/res/values-vi/strings_k2go.xml +++ b/controller/app/src/main/res/values-vi/strings_k2go.xml @@ -43,6 +43,7 @@ Không thể khởi động Xem tiến trình Thử lại + Cài đặt hệ thống Đang thêm nội dung diff --git a/controller/app/src/main/res/values-yo/strings.xml b/controller/app/src/main/res/values-yo/strings.xml index da5ec66e1..cd2990d00 100644 --- a/controller/app/src/main/res/values-yo/strings.xml +++ b/controller/app/src/main/res/values-yo/strings.xml @@ -133,7 +133,6 @@ Lọ sí Àwọn Ìṣàgbékalẹ̀ App Ti Bẹ̀rẹ̀ IIAB_Internal - setup_complete A ti Fi Pamọ́ A ti Fi Àwọn Ìṣàgbékalẹ̀ Pamọ́ Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-yo/strings_k2go.xml b/controller/app/src/main/res/values-yo/strings_k2go.xml index 6c311f64f..67c13c05c 100644 --- a/controller/app/src/main/res/values-yo/strings_k2go.xml +++ b/controller/app/src/main/res/values-yo/strings_k2go.xml @@ -43,6 +43,7 @@ Kò lè bẹ̀rẹ̀ Wo ìlọsíwájú Tún gbìyànjú + Fi ẹ̀rọ sílẹ̀ Ń fi àkóónú kún un diff --git a/controller/app/src/main/res/values-zh-rCN/strings.xml b/controller/app/src/main/res/values-zh-rCN/strings.xml index fbd1844fa..76481a43b 100644 --- a/controller/app/src/main/res/values-zh-rCN/strings.xml +++ b/controller/app/src/main/res/values-zh-rCN/strings.xml @@ -133,7 +133,6 @@ 前往设置 应用已启动 IIAB_Internal - setup_complete 已保存 设置已保存 Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml b/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml index 0427bd351..1e2c49b4f 100644 --- a/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml +++ b/controller/app/src/main/res/values-zh-rCN/strings_k2go.xml @@ -43,6 +43,7 @@ 无法启动 查看进度 重试 + 安装系统 正在添加内容 diff --git a/controller/app/src/main/res/values/strings.xml b/controller/app/src/main/res/values/strings.xml index 22147e9ff..e1da9172b 100644 --- a/controller/app/src/main/res/values/strings.xml +++ b/controller/app/src/main/res/values/strings.xml @@ -168,7 +168,6 @@ Application Started IIAB_Internal - setup_complete Saved Settings Saved Knowledge To Go · 2026 · v0.3.xbeta diff --git a/controller/app/src/main/res/values/strings_k2go.xml b/controller/app/src/main/res/values/strings_k2go.xml index 639994186..eaa808858 100644 --- a/controller/app/src/main/res/values/strings_k2go.xml +++ b/controller/app/src/main/res/values/strings_k2go.xml @@ -46,6 +46,7 @@ Couldn\'t start See progress Retry + Install a system Adding content diff --git a/controller/app/src/test/java/org/iiab/controller/system/domain/SystemPresenceTest.java b/controller/app/src/test/java/org/iiab/controller/system/domain/SystemPresenceTest.java new file mode 100644 index 000000000..6d9f9744d --- /dev/null +++ b/controller/app/src/test/java/org/iiab/controller/system/domain/SystemPresenceTest.java @@ -0,0 +1,74 @@ +package org.iiab.controller.system.domain; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * The whole truth table for the rule that replaces {@code setup_complete} (ADFA-5137). Pure JVM. + * + *

    Written as eight explicit rows rather than as three assertions about "or", because the point of + * the change is that every reachable combination now describes something real. The table is the + * verification: if a row ever has to be argued about, the model is wrong again. + */ +public class SystemPresenceTest { + + private static boolean q(boolean rootfs, boolean installing, boolean deepOp) { + return SystemPresence.hereOrOnTheWay(rootfs, installing, deepOp); + } + + /** The one row that sends a user to the wizard — nothing there, nothing coming. */ + @Test + public void nothingHereAndNothingComing() { + assertFalse(q(false, false, false)); + } + + /** A finished system. The ordinary case, and the reason the flag existed at all. */ + @Test + public void aRootfsOnDiskIsEnoughOnItsOwn() { + assertTrue(q(true, false, false)); + } + + /** + * Halfway through a first install: no usable rootfs yet, and sending this user back to the wizard + * on every relaunch is the same dead end from the other side. + */ + @Test + public void anInstallInFlightCounts() { + assertTrue(q(false, true, false)); + } + + /** + * Receiving a clone, or restoring a backup. By definition there is no rootfs yet on a fresh + * clone-receive, which is exactly why "is there a rootfs" alone would have been wrong. + */ + @Test + public void aDeepOperationInFlightCounts() { + assertTrue(q(false, false, true)); + } + + /** The four remaining rows, all overlaps of the three above. */ + @Test + public void theOverlapsAreAllTrue() { + assertTrue(q(true, true, false)); // reinstall over an existing system + assertTrue(q(true, false, true)); // backup or restore on a live system + assertTrue(q(false, true, true)); // an install and a deep op both marked + assertTrue(q(true, true, true)); + } + + /** + * The property that makes the dead end unrepresentable: false has exactly one row. Under the old + * flag, "no system" and "the wizard is done" were independent, so they could disagree; here they + * are the same statement, and there is nowhere for a disagreement to live. + */ + @Test + public void exactlyOneOfTheEightRowsIsFalse() { + int falses = 0; + for (int i = 0; i < 8; i++) { + if (!q((i & 1) != 0, (i & 2) != 0, (i & 4) != 0)) falses++; + } + assertEquals(1, falses); + } +} From 222ac43bfce00da19c07956eb0eab8a1b1d6eb1f Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 14 Aug 2026 04:25:47 -0600 Subject: [PATCH 2/3] =?UTF-8?q?ADFA-5137=20fix(state):=20review=20pass=20?= =?UTF-8?q?=E2=80=94=20one=20reader,=20and=20the=20new=20button=20stops=20?= =?UTF-8?q?being=20a=20way=20to=20break=20things?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second review of the branch. Three findings acted on, and the first one is the kind only the second pass catches. I ADDED THE TENTH ANSWER. SystemPresenceReader stood four files from SystemFactsReader, in the same package, reading the same three sources — and SystemFactsReader's own javadoc says the ADFA-5061 survey found nine answers to "is a system installed" and that "adding a tenth would have been the joke telling itself". Mine claimed to be "the only place that gathers them". Both cannot be true, and the file I duplicated warns against exactly this. The two also disagreed on the null context: SystemFactsReader.read(null) is conservative, mine was permissive. hereOrOnTheWay now lives on SystemFactsReader and the rival class is gone. Why it is a method there rather than a field on SystemFacts is written down, because it was worth verifying rather than assuming: isInstalled() is rootfs && !marker, so an install in flight and a device with nothing produce the identical tuple — installed=false, healthy=true — and telling those apart is the whole question. It is not derivable from what read() returns. SystemPresence stays as the pure rule with its eight-row table. THE SAFETY NET WAS A ONE-TAP ROUTE INTO THE FAILURE IT GUARDED AGAINST. H_NO_LIBRARY is computed from isSystemInstalled(), which is false for the whole time an install marker is set — including while a clone-receive holds both the marker and the lock, which is a state this branch keeps the user in. The gate lifts for ownerHeld, so Home is reachable, the header reads "no library", and the new button offered a tier flow with reinstall=false. InstallService's non-destructive guard would then see the half-received rootfs directory, skip the extract, report success, and its teardown would clear the marker a killed receive needs for recovery. That is the "boot the wreck" failure InstallService's own cleanup comment warns about, reached from the button labelled as the way out. Fixed at the root rather than in my branch: the header now calls openGetMore(), which already re-reads the facts at click time, and the lock check went INTO openGetMore — so the footer control that had the same hole is covered by the same line. ownerHeld rather than isHeld, so a live content download does not block it (ADFA-4957 draws that line for the server toggle). ORDER MATTERS IN MainActivity, AND IT DID NOT USE TO. The first-run redirect runs long before the terminal extras are read. It used to fire on setup_complete, true on any device that had ever started an install, so a terminal launch never met it. Asking the disk instead, a device with no system sent Settings → Terminal and the terminal's keep-alive notification into the legacy setup shell in wizard mode with Back blocked, extras dropped. The redirect now asks first whether this launch came to open a terminal. EXTRA_OPEN_TERMINAL alone, because the notification does not pass EXTRA_TERMINAL_ONLY and both callers came for the same thing. COMMENTS THAT WERE WRONG, WHICH HERE COUNTS AS A DEFECT. EXTRA_SETTING_UP's javadoc said it "dies with this navigation". It does not, in either direction: its lifetime is the task record, so Android replays it when the process is killed and the task is restored, and onNewIntent's setIntent can erase it before then. Both outcomes are truthful — the user lands on Home, which now has a labelled way to install a system — but the lifecycle claim was mine to get right and I did not. ModuleHubFragment said the app "no longer routes to the Library after a failed restore". A failed restore keeps the marker, so it does route there, with the recovery dialog. Corrected to say what actually still lands on that screen. InstallService's cleanup prose still counted five items over a four-item list and credited a setup_complete clear that no longer happens. Also: three imports left unused by the previous commit. LEFT AS A DECISION, NOT CHANGED IN PLACE. The review proposes fixing the startWizardInstall race with EXTRA_INSTALLING — already on the same Intent, already read by LibraryActivity — instead of the early InstallGuard.begin. That version persists nothing and removes the "if the service never starts, the next launch enters recovery" trade-off. Against it: the early marker also suppresses a racing server auto-start, which the extra does not. Both work, the difference is design, so it is recorded rather than swapped. --- .../org/iiab/controller/MainActivity.java | 23 ++++++- .../org/iiab/controller/SetupActivity.java | 2 - .../install/presentation/InstallService.java | 6 +- .../controller/redesign/LibraryActivity.java | 13 ++-- .../redesign/LibraryHomeFragment.java | 23 ++++++- .../redesign/ModuleHubFragment.java | 9 +-- .../system/data/SystemFactsReader.java | 41 +++++++++++++ .../system/data/SystemPresenceReader.java | 60 ------------------- .../system/domain/SystemPresence.java | 5 +- 9 files changed, 103 insertions(+), 79 deletions(-) delete mode 100644 controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java diff --git a/controller/app/src/main/java/org/iiab/controller/MainActivity.java b/controller/app/src/main/java/org/iiab/controller/MainActivity.java index 8c23c18c0..a521e007b 100644 --- a/controller/app/src/main/java/org/iiab/controller/MainActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/MainActivity.java @@ -269,7 +269,8 @@ public void onCreate(Bundle savedInstanceState) { // old catch wrote the flag true so this branch would stop firing; with the flag gone there is // nothing to write, and nothing needs writing — the condition is re-derived every launch, so // a missing Activity stops being permanent state and goes back to being a log line. - if (!org.iiab.controller.system.data.SystemPresenceReader.hereOrOnTheWay(this)) { + if (!terminalOnlyLaunch(getIntent()) + && !org.iiab.controller.system.data.SystemFactsReader.hereOrOnTheWay(this)) { try { startActivity(new Intent(this, SetupActivity.class) .putExtra(SetupActivity.EXTRA_WIZARD_MODE, true)); @@ -561,6 +562,26 @@ protected void onNewIntent(Intent intent) { maybeOpenTerminalFromIntent(intent); } + /** + * ADFA-5137 (review): was this Activity launched only to show the terminal? + * + *

    Asked before the first-run redirect above, because the order matters and it did not use to. + * That redirect fired on {@code setup_complete}, which was true on any device that had ever + * started an install, so a terminal launch never met it. Now it asks the disk — and with no + * system, Settings → Terminal and the terminal's own keep-alive notification would land in the + * legacy setup shell in wizard mode with Back blocked, having dropped the extras that said what + * they came for. + * + *

    Read here rather than deferring to {@code maybeOpenTerminalFromIntent}, which runs much later + * in {@code onCreate}: the redirect happens first, so the question has to be answerable first. + */ + private static boolean terminalOnlyLaunch(Intent intent) { + // EXTRA_OPEN_TERMINAL alone, not paired with EXTRA_TERMINAL_ONLY: the redesign's Settings entry + // sets both, but TerminalSessionService's keep-alive notification sets only the first, and both + // came here to open a terminal. What decides the redirect is what the caller came for. + return intent != null && intent.getBooleanExtra(EXTRA_OPEN_TERMINAL, false); + } + /** Open the full terminal when launched from its keep-alive notification (ADFA-4696). */ private void maybeOpenTerminalFromIntent(Intent intent) { if (intent == null || terminalController == null) return; diff --git a/controller/app/src/main/java/org/iiab/controller/SetupActivity.java b/controller/app/src/main/java/org/iiab/controller/SetupActivity.java index 8a6399bef..6e7e20d58 100644 --- a/controller/app/src/main/java/org/iiab/controller/SetupActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/SetupActivity.java @@ -8,8 +8,6 @@ */ package org.iiab.controller; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; diff --git a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java index c39bea6f5..758ac164f 100644 --- a/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java +++ b/controller/app/src/main/java/org/iiab/controller/install/presentation/InstallService.java @@ -1509,7 +1509,7 @@ private void doCancel() { /** * ADFA-5119: erase every trace of a system the user decided not to build. * - *

    Five things claimed that system existed or was about to. Leaving any one of them behind is + *

    Four things claimed that system existed or was about to. Leaving any one of them behind is * a specific bug, not untidiness: * *

      @@ -1566,7 +1566,7 @@ private void forgetTheAbandonedSystem() { org.iiab.controller.system.data.ContentStateInvalidator.replacementSucceeded(this, org.iiab.controller.system.domain.SystemReplacement.Cause.ABANDONED_INSTALL); - // 4 + 5. The decision itself, and last on purpose. If the process is killed part-way + // 4. The recorded tier, and last on purpose. If the process is killed part-way // through this method, everything above it is disposable wreckage and the install marker // is still set, so the next launch enters damaged-system recovery — which offers a // reinstall. Clearing setup_complete first and dying here would instead send the user to @@ -1604,7 +1604,7 @@ private void teardown() { * no system, so forgetting that an install happened is exactly what let the * app open an empty library. Success, cancellation and every module or reset * path still pass true — a cancellation has already removed the residue and - * cleared setup_complete, so it needs no marker to be recovered from. + * removed the residue and left no rootfs, so it needs no marker to say so. */ private void teardown(boolean clearMarker) { // ADFA-5119: nothing to wait for once this is over — neither the window nor a queued attempt. diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java index db01b7449..9e7b3f3d9 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryActivity.java @@ -3,7 +3,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; @@ -48,9 +47,13 @@ public class LibraryActivity extends AppCompatActivity implements ServerControll * the user back to the wizard — so before this ticket the wizard wrote {@code setup_complete} to * get past the check, and that lie is the entrance to findings 3 and 5. * - *

      An Intent extra rather than a stored fact, deliberately: it dies with this navigation. If the - * user leaves before scanning anything, the next launch finds no system and no lock and opens the - * wizard — which is the truth, because nothing was started. + *

      An Intent extra rather than a stored fact — but its lifetime is the task record, not + * this navigation, and the difference is worth stating because a first draft of this comment got it + * wrong in both directions. Android replays the launching Intent when the process is killed and the + * task is restored, so the extra survives that; swiping the task away is what ends it. And + * {@code onNewIntent} calls {@code setIntent}, so a later arrival carrying no {@code settingUp} + * replaces it. Both outcomes are truthful — the user lands on Home, which since ADFA-5137 has a + * labelled way to install a system — but nobody should read this as "it dies when you navigate". */ public static final String EXTRA_SETTING_UP = "settingUp"; private boolean installing = false; @@ -121,7 +124,7 @@ protected void onCreate(Bundle savedInstanceState) { boolean broughtHereToSetUp = getIntent() != null && getIntent().getBooleanExtra(EXTRA_SETTING_UP, false); if (!broughtHereToSetUp - && !org.iiab.controller.system.data.SystemPresenceReader.hereOrOnTheWay(this)) { + && !org.iiab.controller.system.data.SystemFactsReader.hereOrOnTheWay(this)) { startActivity(new Intent(this, WizardActivity.class)); finish(); return; diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java index 4cea8f302..99159a4d6 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/LibraryHomeFragment.java @@ -109,8 +109,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c // place the wizard sends people. Named, on the header, instead of a sentence pointing // at a control at the bottom of the screen called "Get more". if (headerState == H_NO_LIBRARY) { - startActivity(new android.content.Intent( - requireContext(), SetupLibraryActivity.class)); + openGetMore(); return; } // ADFA-4837: retry only when it is genuinely safe — canStartServer guards @@ -250,6 +249,26 @@ private View makeGetMoreCell(int cardH) { } private void openGetMore() { + // ADFA-5137 (review): refuse while a deep operation owns the environment, and refuse HERE so + // both entrances are covered — the footer control and the header button this ticket added. + // + // The hole is not theoretical and the header made it one tap wide. isSystemInstalled() is false + // for the whole time an install marker is set, and a clone-receive holds both the marker and + // the lock — so during a live receive the header reads "no library" and this method would take + // the Step-1 branch. That branch starts an install with reinstall=false, InstallService's + // non-destructive guard sees the half-received rootfs directory, skips the extract and reports + // success, and its teardown clears the marker that a killed receive needs for recovery. That is + // exactly the "boot the wreck" failure InstallService's own cleanup comment warns about, + // reached from a button labelled as a way out. + // + // ownerHeld, not isHeld: a live content download holds no owner marker and must not block this + // (ADFA-4957 draws the same line for the server toggle). + if (org.iiab.controller.env.EnvironmentLock.ownerHeld(requireContext())) { + if (getView() != null) { + org.iiab.controller.util.Snackbars.make(getView(), R.string.k2go_install_busy).show(); + } + return; + } // If a system is already installed, skip the destructive system step and go straight // to content (Step 2). Otherwise run the full setup from Step 1. Intent i = new Intent(requireContext(), SetupLibraryActivity.class); diff --git a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java index 42597ce8e..983076e00 100644 --- a/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java +++ b/controller/app/src/main/java/org/iiab/controller/redesign/ModuleHubFragment.java @@ -66,10 +66,11 @@ public class ModuleHubFragment extends Fragment { * — and with no system nothing answers, so everything looked missing. * *

      Reachable, not theoretical — and it stays reachable after ADFA-5137. That ticket removed the - * flag this note used to blame ({@code setup_complete}, written true by five sites and cleared by - * none), so the app no longer routes to the Library after a reset or a failed restore. But Settings - * still opens this screen directly, and it can be opened over an empty rootfs whenever the user - * gets there some other way, so the precondition still has to be asked here. + * flag this note used to blame ({@code setup_complete}, written true by four sites and cleared by + * none), so a device that simply has no system now opens the wizard instead of the Library. What + * still lands here is the case where a marker is held: a failed restore keeps it, so the launch + * treats the device as having something on the way, reaches the Library, and this screen can be + * opened over a rootfs that is empty or half-written. The precondition still has to be asked here. * *

      Seeded true so the first frame looks like the ordinary case; the background pass * corrects it before anything is offered. diff --git a/controller/app/src/main/java/org/iiab/controller/system/data/SystemFactsReader.java b/controller/app/src/main/java/org/iiab/controller/system/data/SystemFactsReader.java index 9ab780814..d4d50e6f4 100644 --- a/controller/app/src/main/java/org/iiab/controller/system/data/SystemFactsReader.java +++ b/controller/app/src/main/java/org/iiab/controller/system/data/SystemFactsReader.java @@ -19,6 +19,7 @@ import org.iiab.controller.install.presentation.InstallProgressRepository; import org.iiab.controller.install.presentation.ModuleQueueRepository; import org.iiab.controller.system.domain.SystemFacts; +import org.iiab.controller.system.domain.SystemPresence; import java.io.File; @@ -87,6 +88,46 @@ public static SystemFacts read(Context ctx) { : SystemFacts.serverUnknown(installed, healthy); } + /** + * ADFA-5137: does this device have a system, or is one on its way? + * + *

      The question the launch path asks before deciding between the library and the first-run + * wizard. It replaces {@code setup_complete}, a stored claim about the past that four sites wrote, + * none cleared, and all of them wrote when an install started — so it could answer yes on + * a device with nothing, and the launch then routed past the wizard forever. Findings 3 and 5 of + * {@code state-spine.svg} were both that. + * + *

      Why it lives here and not next door. A first pass put it in a class of its own, which + * made this file's own warning come true: the ADFA-5061 survey found nine answers to "is a system + * installed" and said a tenth would be the joke telling itself. There is one reader for what is + * true about the box, and this is it. + * + *

      Why it is a separate method rather than a field on {@link SystemFacts}. Because it is + * not derivable from what {@link #read} returns, and that is worth stating rather than discovering + * later. {@code isInstalled()} is {@code rootfs && !marker}, so an install in flight and a device + * with nothing at all produce the identical tuple — {@code installed=false, healthy=true} — and + * telling them apart is the whole point of this question. It needs the three raw facts, which + * {@code read} deliberately does not carry. + * + *

      The rule itself is {@link SystemPresence}, pure and with its eight-row truth table in tests. + * This method is the three reads, and each one is taken from the thing it describes: the rootfs + * from the disk, the install from its durable marker, a clone or restore from the lock's owner + * marker. None of the three can drift from what it is about, which is the property the flag lacked. + * + * @return false only when the device has nothing and nothing is coming — the one case where the + * wizard is the right place to be. A null context answers true: the wizard is a decision, + * not something to fall into on a missing argument. + */ + public static boolean hereOrOnTheWay(Context ctx) { + if (ctx == null) { + return true; + } + return SystemPresence.hereOrOnTheWay( + SystemStateEvaluator.rootfsPresent(ctx), + InstallGuard.inProgress(ctx), + EnvironmentLock.ownerHeld(ctx)); + } + /** * ADFA-5061: whether the box is observed to be answering — one fact, no disk. * diff --git a/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java b/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java deleted file mode 100644 index e517ca67b..000000000 --- a/controller/app/src/main/java/org/iiab/controller/system/data/SystemPresenceReader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============================================================================ - * Name : SystemPresenceReader.java - * Author : AppDevForAll - * Copyright : Copyright (c) 2026 AppDevForAll - * Description : ADFA-5137. Gathers the three facts SystemPresence needs, from - * the disk and the two markers. The only place that gathers them. - * ============================================================================ - */ -package org.iiab.controller.system.data; - -import android.content.Context; - -import org.iiab.controller.InstallGuard; -import org.iiab.controller.SystemStateEvaluator; -import org.iiab.controller.env.EnvironmentLock; -import org.iiab.controller.system.domain.SystemPresence; - -/** - * Asks the device whether it has a system, or one on the way. - * - *

      The rule is in {@link SystemPresence}, where it can be tested without a device. This is the - * Android half: three reads, in one place. It matters that it is one place — four screens each - * assembling the same three calls would be the duplication ADFA-5137 exists to remove, arriving by a - * different door. - * - *

      Each fact is read from the thing it describes, which is the property the old flag lacked: - * - *

      - * - *

      Touches the filesystem, so it is not free; all three reads are file existence checks, which is - * the same cost the launch path already pays. - */ -public final class SystemPresenceReader { - - private SystemPresenceReader() { - } - - /** False only when the device has nothing and nothing is on its way. */ - public static boolean hereOrOnTheWay(Context ctx) { - if (ctx == null) { - // No context, no evidence. Answering true keeps a caller from routing a user into setup - // on the strength of a missing argument; the wizard is a decision, not a default. - return true; - } - return SystemPresence.hereOrOnTheWay( - SystemStateEvaluator.rootfsPresent(ctx), - InstallGuard.inProgress(ctx), - EnvironmentLock.ownerHeld(ctx)); - } -} diff --git a/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java b/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java index 70f39308c..95d1f81c6 100644 --- a/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java +++ b/controller/app/src/main/java/org/iiab/controller/system/domain/SystemPresence.java @@ -31,8 +31,9 @@ * sent back to the wizard on every relaunch, which is the same dead end in the other direction. The * question is not "is it finished" but "is anything there or coming". * - *

      Pure: booleans in, boolean out, no Android. {@code SystemPresenceReader} is what gathers the - * three facts, and it is the only place that does. + *

      Pure: booleans in, boolean out, no Android. {@code SystemFactsReader.hereOrOnTheWay} gathers the + * three facts — the one reader for what is true about the box, so this question does not stand up a + * second one beside it. */ public final class SystemPresence { From 34d87a46e99fb298be5e87c4b374f1837e9405dd Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 14 Aug 2026 04:43:53 -0600 Subject: [PATCH 3/3] =?UTF-8?q?ADFA-5137=20docs:=20the=20diagrams=20catch?= =?UTF-8?q?=20up=20=E2=80=94=20three=20dead=20ends=20closed,=20one=20new?= =?UTF-8?q?=20one=20named?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both diagrams had fallen behind the code, and the spine had started contradicting itself: its map still listed setup_complete as live state while its findings said the flag was gone. STATE SPINE. Findings 3 and 5 turn green beside 1. Three of the seven original dead ends are closed, 4 has lost one of its three blind spots, and the ranking line says so. The map above them stopped disagreeing. S0 and S2 no longer describe a flag that does not exist, the "Copy from a phone" edge no longer claims to mark anything, and S1 is marked UNREACHABLE with the reason the box is still drawn: a LibraryActivity instance that is already alive never re-runs the launch check, because onNewIntent skips it — which is why the header keeps its Install button rather than relying on the entrance being shut. Finding 8 is new and was found on a device, by navigating, not by reading: while a clone is being shared the server is stopped on purpose, the header reads !alive and concludes "Couldn't start — tap to retry", and canStartServer() does not know about CloneSendSession, so that Retry starts the server on top of a live transfer. ADFA-5143. ROADMAP. The ledger's first row does not turn green, it disappears: setup_complete is DELETED, lives nowhere, is written by nobody and has nothing to clear. And the standing directive gains the move it was missing. It named two good patterns, both of them "the fact dies with the thing it describes"; the third is that a fact which can be derived is deleted rather than maintained. ADFA-5119's card goes to DONE with its leftover written on it — the kiwix index leg still sends exit and error onward, so a failed companion run still reports as finished. Cards added for ADFA-5137 and ADFA-5143. Two things were quietly broken and are fixed in passing: the roadmap's background rect was still 2679 high under a 2825 canvas, leaving an unpainted band, and one of finding 5's citation lines had been pushed inside finding 6 by an earlier coordinate shift. --- controller/docs/operation-model-roadmap.svg | 45 ++++++---- controller/docs/state-spine.svg | 91 +++++++++++---------- 2 files changed, 81 insertions(+), 55 deletions(-) diff --git a/controller/docs/operation-model-roadmap.svg b/controller/docs/operation-model-roadmap.svg index c092d4c4a..1cc3bc8c8 100644 --- a/controller/docs/operation-model-roadmap.svg +++ b/controller/docs/operation-model-roadmap.svg @@ -24,7 +24,7 @@ - + Operation model — ticket map A card's column is its status: delivered, in flight, next. Arrows say what directs what — the teal cards are the two hubs, and they are hubs because arrows leave them. Solid teal = it drives that work. Grey = it enabled it. Dashed = coordination only, no dependency. Generated, so the geometry is checked rather than eyeballed. @@ -35,7 +35,7 @@ The app's state is scattered and undeclared. A dozen facts live in preferences, in files, in in-memory singletons and inside the rootfs, and nothing says who writes each one, who clears it, or what happens if the process dies between the two. That is not a tidiness complaint: four of the seven dead ends in state-spine.svg are the same sentence — a fact with no lifecycle. The rule, from here on. A change that touches a fact leaves it with a named owner and a stated lifetime, and updates the row below. Not a cleanup ticket for later — refactor-by-feature, the way the layering already spreads. Two rows -are already right and are the shape to copy: both put the fact where its own destruction takes it with them, so nothing has to remember to invalidate anything. +are already right and are the shape to copy: both put the fact where its own destruction takes it with them, so nothing has to remember to invalidate anything. ADFA-5137 shows the third move — a fact that can be derived is deleted, not maintained. FACT WHERE IT LIVES WHO WRITES IT @@ -43,12 +43,12 @@ DIES WITH STATUS -setup_complete -SharedPreferences -4 sites -nobody -never -no lifecycle +setup_complete — DELETED 13 Aug +nowhere: derived on demand +nobody +nothing to clear + +ADFA-5137: removed .install_in_progress file in filesDir 5 sites @@ -223,12 +223,12 @@ Maps runrole swallows failures onProcessExit and onError both continue, so a failed install reports as finished. - - -ADFA-5119 -Base-install legs end honestly -Kiwix index and extract. The module queue -already does it right in the same file. + + +ADFA-5119 · DONE +Pause, Retry, Cancel — and a failure +that leads somewhere. PR #400, 13 Aug. +Left over: the kiwix index leg still lies. FOLLOW UP @@ -304,6 +304,23 @@ teardown() clears the install marker, so the app stops standing back while Ansible is still configuring. ADFA-5119 replaced the notification's Cancel with View: door closed, hazard intact. + + +ADFA-5143 · NOW +Clone sharing has no name, and its +Retry boots the server mid-transfer + +canStartServer learns about the share + +Amber header state, with Stop +Live on main today. The guard first: it is the destructive half. + + +ADFA-5137 · DONE +setup_complete stops existing +Findings 3 and 5 closed, and the first +ledger row deleted rather than fixed. +A derivable fact is removed, not maintained. SPANS THE BOARD — real work with no ticket, and the small things diff --git a/controller/docs/state-spine.svg b/controller/docs/state-spine.svg index 2e9b50064..4f585489c 100644 --- a/controller/docs/state-spine.svg +++ b/controller/docs/state-spine.svg @@ -1,6 +1,6 @@ - + System lifecycle — the states the whole app shares, and where a user gets stuck -States are combinations of facts, not screens: setup_complete, a rootfs on disk, health, whether the server answers, and who holds the environment lock. Edges are labelled with the guard that allows them, cited to file:line. Red states are dead ends. Read this before drawing any per-area diagram, and reuse these state names verbatim. +States are combinations of facts, not screens: a rootfs on disk, health, whether the server answers, and who holds the environment lock. Edges are labelled with the guard that allows them, cited to file:line. Red states are dead ends. Read this before drawing any per-area diagram, and reuse these state names verbatim.