Repository navigation
android: let users stop the background connection service - #22
Conversation
eaa94ef to
9805bb6
Compare
|
Hi, I have also noticed that the Android app literally cannot be stopped. Or at least not easily and that's a current issue for me. Little suggestion: Maybe you could have started an issue and therefore discussion with this first? I'd like to see coming to the Android part. Cheers |
thtbee
left a comment
There was a problem hiding this comment.
Thanks for this, it's a real gap, and Jochen ran into it too. The code itself is clean: the single check in ConnectionService.start() covers every caller, it follows the existing patterns (the static helpers next to away mode, nectarSwitchColors, the switch's content description), and the Simplified Chinese strings are there.
There's one problem that needs a different approach, though, plus a few smaller points.
1. Turning it off doesn't stop the connection. The phone's connection doesn't live in ConnectionService: the core starts with the app process (NectarlinkApplication.onCreate calls core.start()), and the service only adds the ongoing notification, the multicast lock that local discovery needs, and protection from Android killing the process. With notification access granted, which most people have, Android keeps the process alive through NotificationListener, so after Stop the phone stays connected to the PC. It just loses the multicast lock, so discovery and reconnects get less reliable. And if Android does kill the process, the listener being rebound (or a companion wake-up) starts the core again. So people would get a half-off state, while the setting says NectarLink only connects while the app is open.
For the setting to mean what it says, the core has to go offline when it's off and no NectarLink screen is open, and come back when the app opens. That needs a small addition to the core (a way to pause and resume the node) and a hook in Core.kt for the app going to and from the background. I'm happy to add the core part, or to point you at the exact places if you'd like to do it.
2. One tap turns it all off, with no sign of it. Stop on the ongoing notification turns the background connection off for good, the notification disappears, and nothing in the app says it's off; the only way back is the switch in Settings. A mistaken tap would quietly stop notifications reaching the PC. Please:
- label the action Turn off, which says more than Stop next to "Connected to PC";
- show a note on Home while it's off, for example "Background connection is off" with a Turn on button.
3. Wording. The hint's last sentence reads as how it works today. Something like: "Keeps this phone reachable from your PC. When off, NectarLink connects only while it's open, and the notification goes away." Also, both strings.xml files gained an empty line after the hint.
4. The test. It only checks that two constants equal their own values. A test of the behavior (for example that ConnectionService.start does nothing while the setting is off) would be worth having; otherwise it's fine to drop it.
Thanks again. With point 1 sorted, this will be a good addition.
|
hey bee bro thanks for the detailed review, super helpful and you're right, i missed that the core starts with the app process itself so my switch only half works ill fix the smaller points now
for point 1 id really like your help with the core pause/resume if you dont mind, you know that part way better than me. once its in ill do the kotlin side, pause when the app goes to background with the setting off and resume when it opens. or if you want me to try the core part too just point me to the exact places and ill give it a go no rush at all, thanks again! |
The phone app can stop its node while in the background and start a new one when opened (GitHub #22). This checks that stopping lets go of the USB port and everything else a new node binds, three times over, while the old handle is still alive.
|
Good news: the core doesn't need a pause. Stopping the node and starting a new one in the same process already works, and 417e13d adds a test for it: three rounds of stop and start while the old node is still around, keeping the identity, pairing and USB port. So it's all in
Happy to review it when it's up. |
|
Hi @thtbee So, this means that if I'm away from my connected desktop. I mean out of the vicinity, like driving away, then the app powers down completely? My main concern is background battery usage because of attempts to reconnect to the desktop which is not possible for x amount of time. This would be a serious problem, if the app does not shutdown completely. Or am I reading this wrongly? Cheers |
|
Good question, Jochen. Two cases:
That 30 s limit is also why I flagged the supervisor wake in |
|
Hi @thtbee Ok, let's see how this plays out. Removed wake_supervisor from Node::connected: |
9805bb6 to
81a1b32
Compare
|
thanks @thtbee , all done and tested on my phone (oppo, android 11) core now stops and starts with the app going to background and foreground like you suggested, with a 5s delay and a mutex so start and stop dont overlap. ops get dropped while its stopped and device states go offline. also did the Turn off wording, the home card, the hint text and the blank lines. replaced the weak test with a CoreState one since the project only has junit, no robolectric tested on my phone: turning it off and leaving the app disconnects the pc, force stop stays off, opening the app reconnects and shows the card, turn on brings everything back, and a quick reopen right after turning off doesnt freeze let me know if anything looks off! |
thtbee
left a comment
There was a problem hiding this comment.
Thanks for the update. I ran it on an Android 16 emulator to check the start and stop.
- With Run in the background on (the default), a background start ends with no node.
start()bumpslifecycleGenerationon every call, including the ones that return early because a start is already running, and the start job then shuts its node down because the generation moved on.onCreatecallsstart()twice (the frame callback and the 120 ms delay), so when Android starts the process without an activity (boot, the companion wake, the service), the first node starts, gets shut down within a second, and nothing starts another. On the emulator:node startedat 00.337, swarm discovery's mailbox closed at 01.059, nothing after that. With the activity it only recovers becauseonStartcallsstart()once more. Onlystop()should cancel a pending start, for example a counter that onlystop()bumps. - With it off, everything stops 5 s after the app leaves the foreground, including things the user is in the middle of: mirroring the phone to the PC (which means leaving the app), a file transfer, the webcam, the mic. The stop should wait while one of those is active and run once it ends.
- Small: after
activeNode.shutdown(), callactiveNode.close()so the old node's runtime and threads go right away instead of at the next GC.
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
…tion is off Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
728282f to
52ed2be
Compare
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
Signed-off-by: abinanthan-cg <dabinanthan@gmail.com>
|
Hi @thtbee, thanks for testing it on the emulator and for the detailed notes, good catch on that race pushed 2 more commits for your 3 points
tested on my oppo android 11
didnt test webcam or mic on my phone. gradle tests, lint and header check pass |
thtbee
left a comment
There was a problem hiding this comment.
Checked it again on the emulator: a background start with the setting on keeps its node, with it off the node stops about 5 s after leaving the app and comes back on return, and coming back right as it stops ends with one node every time. Thanks for working through all of it.
|
Merged, thanks. One small thing I noticed while testing: phone as speaker (the PC's audio playing on the phone) isn't in |
|
thanks for merging! took the phone as speaker one, opened a pr for it, will link it here in a bit |
Problem
On Android, NectarLink runs an always-on foreground service (
ConnectionService) with an ongoing notification ("Connected to PC" / "Waiting for your PC"). There was previously no way for the user to stop it: swiping the ongoing notification does nothing, and force-stopping the app gets undone quickly because external events (such as Android rebindingNotificationListeneror BLE wakeups) triggerConnectionService.start(...).Solution: Central-Guard Approach
backgroundServiceEnabled(defaulttrue, preserving existing behavior) stored in SharedPreferences ("appearance"), accessible synchronously from non-UI components (services/receivers) viaPreferences.isBackgroundServiceEnabled(context)andPreferences.setBackgroundServiceEnabled(context, boolean).ConnectionService.start(context), return early ifbackgroundServiceEnabledisfalse. Because all callers restart the service viaConnectionService.start(...), this single guard prevents any automatic foreground service recreation when the user has turned the background service off.action_stop_service) toongoingNotification(...)whose PendingIntent sendsACTION_STOP_SERVICEtoonStartCommand(). This setsbackgroundServiceEnabled = false, dismisses the notification, and stops the foreground service viastopSelf().ConnectionService, and toggling it off stopsConnectionService.Note
If Android's Notification Access (
NotificationListener) is enabled, the system service can still keep the application process alive in the background as an active listener, but NectarLink will no longer run an ongoing foreground service, post an ongoing notification, or hold a multicast lock when stopped.Testing
ConnectionService.ACTION_STOP_SERVICEandPreferences.BACKGROUND_SERVICE_ENABLEDinCompanionAndMigrationTest../gradlew testDebugUnitTest,./gradlew lintDebug, and./gradlew assembleDebug(all passing). Also verified license headers viascripts/check-headers.ps1and formatting viacargo fmt --all --check.Manual Test Verification Steps