Skip to content

v0.6.0 — Rebuild Android NSD around explicit ownership and readable flow - #24

Merged
CodePandaaAI merged 1 commit into
masterfrom
quality
Sep 30, 2026
Merged

CodePandaaAI merged 1 commit into
masterfrom
quality

Conversation

@CodePandaaAI

Copy link
Copy Markdown
Owner

Why this PR exists

This is a deliberately human-oriented refactor.

Sync360's nearby discovery code is important. It decides whether nearby devices appear, whether this device is visible to others, and whether the app can safely clean up and start again. It is also native Android code with asynchronous callbacks, legacy API paths, listener ownership, and lifecycle questions. That means it is exactly the kind of code that becomes risky when it works only because it was generated, copied, or made defensive in ways the current maintainer cannot comfortably explain.

The older Android NSD implementation was not treated as worthless. It was a useful reference and it contained defensive handling for difficult situations. But it was not the implementation I wanted to carry forward as the owner of this project. I wanted the code to read like the actual mental model of Sync360:

Start discovery.
Receive a service.
Resolve it when needed.
Publish a nearby device.
Remove it when it is lost.
Stop and release the callback/listener we own.

That simple story matters more here than reducing line count. Native networking code does not become easier merely because it is shorter. It becomes easier when each resource has an obvious owner, each callback has an obvious job, and future changes do not require trusting code that nobody fully understands.

What changed

The common NetworkServices contract is now split into four direct actions:

  • startDiscovery()
  • stopDiscovery()
  • startAdvertising(httpServerPort, fileTransferPort)
  • stopAdvertising()

This removes the old combined start/stop operation and makes the contract reflect what the app actually owns: browsing nearby services and advertising this device are related, but they are still separate native operations with separate callbacks and failures.

Android now uses the rebuilt AndroidNetworkServices implementation. Its intended discovery paths are explicit:

  • Tiramisu SDK extension 22 or newer uses DiscoveryRequest and one ServiceInfoCallback for modern combined discovery, resolution, updates, and loss.
  • Older supported Android uses DiscoveryListener plus the deprecated one-shot resolveService() API.
  • Legacy resolves are serialized through a queue so only one resolution runs at a time.
  • Advertising uses one RegistrationListener, shared by both discovery paths.
  • Self devices are filtered by Sync360's stable device UUID before being published.
  • Discovery and registration expose real platform states such as Starting, FailedToStart, Running, Stopping, and CleanupFailed.

The controller was simplified at the same time. It now starts the HTTP server and file-transfer receiver once, then directly asks the platform service to start or stop nearby sharing. It exposes the nearby-device list and the real platform discovery/registration statuses. The Send UI no longer keeps a separate isDiscoveryEnabled boolean or a generic discovery-error string that can contradict Android's actual state.

Instead, the UI has a direct relationship with discovery status:

Idle            → Start
Starting        → Stop
Running         → Stop
Stopping        → Stop
FailedToStart   → Try again
CleanupFailed   → Try again

Desktop and iOS were updated to implement the same explicit common contract. This is contract alignment, not a claim that every platform backend has received the same level of manual validation as Android.

Why this is intentionally not "just fewer lines"

The old implementation may have covered some edge cases more aggressively. This PR does not claim that every removed layer was bad or that the new implementation has magically solved every native networking problem.

The point is different: this code can now be read from top to bottom by the person responsible for it.

A future fix should begin with understandable questions:

  • Which listener or callback do we currently own?
  • Did Android accept the start request or only receive it?
  • Which callback confirms completion?
  • Which status should the UI show?
  • Can this legacy result still belong to a service that is present?
  • What must be retained after cleanup fails?

That is a better foundation for reliability than an implementation which might be more defensive on paper but is too opaque to change with confidence.

Current confidence and honest limitations

Normal Android foreground discovery has been manually observed during this work: devices were added and removed in normal conditions.

This is still a preview release and not a claim of complete NSD maturity. Known follow-up areas include:

  • Android 17 local-network permission handling is still missing.
  • Lifecycle overlap, startup/stop overlap, and broader cleanup-failure validation need more device testing.
  • Legacy discovery still needs more presence/session tracking so a late legacy resolve cannot publish a device that was already lost or stopped.
  • Modern multi-network ownership needs stronger service-name-plus-network tracking.
  • Registration status is exposed separately, but the Send UI currently concentrates on discovery status.
  • Desktop, iOS, adapter, VPN, hotspot, firewall, and physical-device validation remain broader work.

These are not hidden from this PR. They are simply not being solved by pretending this release is finished when its real achievement is clarity, ownership, and a much better base for deliberate future work.

Versioning

  • Android version name: 0.6.0
  • Android version code: 10
  • Desktop package version: 0.6.0
  • iOS marketing version: 0.6.0
  • iOS build number: 10

This is not a normal “fix one bug” or “add one feature” update.

For around ten days, Sync360’s nearby-network code has been rebuilt with a different
goal: not to make the code look smaller, not to add an abstraction layer, and not to
pretend that the old implementation had no useful defensive ideas. The goal was to
make the Android NSD implementation understandable enough that it can genuinely be
owned, maintained, questioned, and improved by a human who reads it later.

The previous Android implementation was defensive and tried to cover many difficult
situations. Some of that work was valuable. But it had become hard to follow as one
story. It did not read like “start discovery, receive a device, resolve it if needed,
publish it, remove it, and stop it.” It read more like a collection of mechanisms
whose purpose had to be rediscovered before changing anything safely.

This release replaces that relationship with a more direct one.

The shared NetworkServices contract now says exactly what the app does:

- start discovery
- stop discovery
- start advertising
- stop advertising

Android then owns the Android-specific details behind those four actions.

On Tiramisu SDK extension 22 and newer, discovery uses DiscoveryRequest and one
ServiceInfoCallback. That callback owns the modern discovery path: it receives device
updates, device loss, and callback registration/unregistration outcomes.

Below extension 22, discovery uses DiscoveryListener and the older one-shot
resolveService API. Legacy resolution is deliberately serialized with a queue because
only one legacy resolve is allowed to be active at a time. A resolver slot is released
after success, failure, malformed data, a null result, self-device filtering, or a
synchronous resolveService exception, so the next queued service can continue.

Advertising is separate from discovery but shared across Android versions through one
RegistrationListener. Discovery and advertising now expose their real platform status
instead of hiding state behind a generic controller error message or a second
“enabled” boolean that can disagree with Android.

The shared controller was simplified heavily. It starts the HTTP and TCP listeners
once, asks NetworkServices to start or stop nearby sharing, and exposes the platform
status flows and nearby-device list. The Send UI now chooses Start, Stop, or Try again
from the actual discovery status. It does not maintain a duplicate story about whether
discovery should theoretically be enabled.

Desktop and iOS implementations were also split into the same four explicit
operations. This is not an attempt to make every platform implementation identical.
It is an attempt to make the shared contract honest while allowing each platform to
keep its own native behavior.

This change may contain more code in some native places than a compact abstraction
would. That is intentional. Explicit listener ownership, callbacks, queues, status
changes, and cleanup are easier to reason about than clever code which is shorter but
requires faith to modify.

This is a developer-ownership release. The normal Android foreground path has been
manually observed: nearby devices appear, update, and disappear. It is not a claim
that every lifecycle, failure, multi-network, permission, or legacy NSD edge case is
finished forever. Those are now visible, named problems in code that can be understood
and improved deliberately instead of being hidden behind code we do not really own.

Prepare Android, Desktop, and iOS package metadata as version 0.6.0. Android and iOS
build numbers move to 10.
@CodePandaaAI
CodePandaaAI merged commit 25fdc35 into master Sep 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant