Repository navigation
USB: stop the permission dialog that comes back on every connect, let a plug-in leave the phone alone, and recover a lost link - #1207
Conversation
67bc82d to
3dcbf76
Compare
3dcbf76 to
e2e560e
Compare
|
@o-jcardenass Thanks for working on this. I was investigating the duplicate permission requests in #1194: the USB button and a settings-close recheck issued requests about 46 ms apart. The shared record in this PR covers that overlap, so I won't open a duplicate implementation. Reviewing 1. Service recreation can forget an unanswered dialog
One possible direction would be to keep request ownership independent of the service receiver's lifetime, including a way to retire the request if its result arrives while that receiver is absent. Explicit cancellation and device detach could remain separate invalidation paths rather than treating every service teardown as completion. 2. An old callback can clear a newer request at the same pathBoth permission callbacks clear by
The recovery callback has the same issue because it clears the ordinary shared record before validating its recovery token. A request-specific token, with matching-only completion, would let an old result leave a newer request untouched. For ordinary requests, that would also need a distinct PendingIntent identity; putting a token only in extras on the shared I exercised the PR's production policy and synchronized record methods in a small JVM replay: the 46 ms automatic/Save overlaps were rejected, while the two lifecycle sequences above allowed another claim. This was not an Android-device or dialog-UI reproduction; the lifecycle ordering came from inspecting the callers. The three-second manual retry is explicitly intentional and covered by your tests, so I'm not suggesting silently removing that behavior. |
3679d69 to
5b56290
Compare
15a13bf to
fd11055
Compare
|
@andrecuellar @andreknieriem @emotionbug Please test this. I changed when the USB permission dialogs appear. Before this PR, a first connect could show up to 4 dialogs: 2 from Android and 2 from OHU on top of them. Android shows one dialog before the phone switches to Android Auto and one after, because the phone then looks like a new device. If a dialog was left unticked, all of them came back on the next plug. Now OHU leaves a plug to Android's dialog when Android shows one, and asks by itself only when Android shows none. So a first connect shows Android's 2 dialogs, and none once both are ticked. The USB button still asks every time. @andrecuellar OHU's own dialog came from your fix for #173 (#185), and the wake check from #292. I kept both, so they should still work, but please check them on your CMZX65-U1:
Two changes to know about:
One case I could not test: if another app holds "Use by default" for the phone, Android opens that app with no dialog, and OHU waits for a dialog that never comes. If you have a unit with ZLink or Autokit as the default, please try it. https://github.com/andreknieriem/open-headunit/actions/runs/40937055895/artifacts/12925487501 |
|
@o-jcardenass I reviewed the updated I found two further lifecycle defects, plus the default-app limitation you called out. Here are concrete implementation directions, including where the public Android APIs limit what can be guaranteed. 1. Connect-on-plug off is bypassed when the plug starts a fresh serviceLocation: With Connect when plugged in off, either single-USB auto-connect or last-session auto-connect on, and the service not running: I exercised the current production Suggested correction:
2. Another USB default app cannot be distinguished reliably from an unanswered/cancelled Android chooserLocation: This matches the untested case in your comment. For example, with an accessory identity whose default handler is another app, Android grants that app permission and launches it directly; no chooser needs to appear. OHU still sees a matching filter, an observed attach and no permission, so the automatic policy returns AOSP's default-handler branch grants the selected UID and starts its activity directly. USB defaults are resolved using the USB service's own preference map, not simply Important implementation constraint: the public Consequently, “never ask again after cancellation” and “automatically recover another app's default without user action” cannot both be guaranteed from those observations. Feasible conservative direction:
This is a product/API limitation to state explicitly, not a fully automatic recovery that can be implemented just by querying a default-app API. The conservative path preserves cancellation and provides a clear manual escape; it does not claim automatic recovery in every competing-default configuration. 3. The permission record outlives detach observationLocation: A separate service-restart sequence remains:
For an API 24+ non-automotive MTP case where the policy would otherwise request permission itself, automatic admission now returns Suggested correction:
This addresses the service-lifetime observation gap. It is not a claim to infer every re-enumeration on firmware that omits the underlying system events as well. Acceptance scenarios
|
The service leaves a plug to Android's dialog while it stays plugged in and asks once per bus path. Each ask has its own token and PendingIntent, and only a grant ends an entry.
…ion ask Both ask as the user through the service's record. A blacklisted accessory device shows as blacklisted, and below API 21 the key reads the device names from sysfs.
…e phone A new key, default on, so every install keeps plug-and-connect. Off, a plug-in opens the app and the USB button connects; accessory identities, reconnects and a re-attach after a glitch stay ungated.
Connect when plugged in is the new plug-in key, shown disabled when detection is off. The old auto-start key becomes Open the app when plugged in; onboarding shows the same rows.
A frame header with a flag bit above 0x0F means bytes were lost from the stream. Disconnect at once instead of decoding body bytes as headers until the socket dies.
Each session end records who ended it. A formed USB session lost to a link failure with the phone attached re-checks the bus even with auto-connect off, and only such a loss shows a notice, once per episode.
The next bulk read is discarded as failed, as a bad transfer does. Armed only through the automation receiver's settings import, with no visible setting.
fd11055 to
bb09069
Compare
Seven commits on
main(a4f80ff1), 4435 unit tests. Tested on four devices over eight hardware rounds, the first onmain: an HP Slate 7 Plus (Android 4.2) and a POCO X3 NFC (Android 15) as USB hosts, and a Motorola edge 30 neo (Android 14) and a Samsung Galaxy S24+ (Android 16) as phones, with an RP2040 board presenting Carlinkit and AOA USB identities.d61ae460): the service stacked its own permission dialog on Android's confirm dialog and asked twice for the accessory identity, so a first connect showed 3 or 4 dialogs and an unticked one brought them back on the next plug. The service now leaves a plug to Android's dialog wherever Android shows one, for as long as the device stays plugged in, so a cancel stays cancelled and the USB button still asks. Where Android shows none it asks once per bus path through one process-wide record, and logs each decision (UsbLauncher: permission for ... action=). With detection off it no longer asks on attach, which was 2 dialogs on every plug with no way to keep a choice. "Reopen on reconnection" no longer raises the home screen over Android's dialog, which hid it and left no way to connect, and a second USB press within 3 s opens no second dialog. Each ask carries its own token and PendingIntent, and a manifest receiver hands the answer to the record, so a service restart in the same process or a late answer at a reused bus path opens no second dialog. Only a grant ends an entry, because below Android 12 the system dialog also answers "denied" when a rotation or night mode only rebuilds it. A first connect now shows Android's 2 dialogs, and none once they are ticked.64648b8a): the USB button and the USB list ask through the same record as the service, and their request counts as the user's, so it still asks with detection off. A blacklisted device in accessory mode now shows as blacklisted and can be removed, where the row used to read "Allowed" with its button disabled. Below Android 5 the API gives no device name, so the key now reads the same strings from sysfs and holds a phone in every USB mode, as on Android 5 and up. A device with no names falls back to its USB ID, and the list says so when the user adds one. Android's own dialog follows the manifest filter, which the blacklist cannot change, so blacklisting a device the filter matches tells the user to choose that device's app and tick the box once; a device the filter misses, such as a stock Carlinkit, gets no dialog and no note.7b19dc79,5b562903): a new setting (usb-connect-on-plug-in, default on) decides whether a plug-in switches the phone to Android Auto. With it off, a plug-in opens the app and leaves the phone in its normal mode, and the USB button connects (UsbLauncher: not switching ... Connect when plugged in is off). Every USB check now carries its trigger, so the refusal covers the attach screen, the listener's fallback, a wake and a settings-screen replay, while a user's press is never refused. A replug within 30 s of a session that ended without an exit still switches, so a loose cable reconnects. The boot start needs both this setting and "Open the app when plugged in". In settings, the Listen for USB Devices description told users to turn it off to stop the prompt, which is what made the prompt appear on every connect. The switch is now "Detect plugged-in phones/AA dongles", sits in General after "Use Native USB Driver", and says what it does. In Auto-start settings, "Connect when plugged in" is shown disabled with a note while detection is off, "Auto-start on USB" becomes "Open the app when plugged in", and both it and "Reopen on reconnection" hide when a plug-in cannot connect. Onboarding shows the same rows. New keys are translated in every locale.0115da20,dcf17b71,fd110551): one failed USB read loses its data in the kernel, and the reader carried on and decoded body bytes as frame headers until TLS failed about a second later. It now ends the session at the first header with a flag bit above 0x0F, which Android Auto never sets (AapRead: frame header on channel ... carries flags). A session that reached SSL and was lost to a link failure with the phone still plugged in now reconnects without a replug with both auto-connect settings off, which is their default (AapService: USB link lost with the phone still attached); with either on it behaves as before. Every session end now records who ended it, so the projection screen shows one notice per lost session instead of the WiFi tip on every end: the WiFi tip on wireless, a new USB line on USB, and nothing for a cable pull, an exit, the app's own teardowns or a failed retry, which used to repeat it every few seconds. Each notice is logged (AapProjectionActivity: showing the USB_LOST notice ...). A hidden one-shot pref armed through the automation receiver (debug-usb-read-fault) makes the next USB read fail, for rig rounds.