Skip to content

Show disable-remote-verify snackbar on UI thread#332

Merged
thestinger merged 1 commit intoGrapheneOS:mainfrom
thomasbuilds:fix-disable-remote-verify-snackbar-thread
Apr 29, 2026
Merged

Show disable-remote-verify snackbar on UI thread#332
thestinger merged 1 commit intoGrapheneOS:mainfrom
thomasbuilds:fix-disable-remote-verify-snackbar-thread

Conversation

@thomasbuilds
Copy link
Copy Markdown
Contributor

Summary

In AttestationActivity#onOptionsItemSelected, the R.id.action_disable_remote_verify handler updates the Snackbar from RemoteVerifyJob.executor (a single-threaded background executor):

RemoteVerifyJob.executor.submit(() -> {
    ...
    preferences.edit()
            .remove(RemoteVerifyJob.KEY_USER_ID)
            .remove(RemoteVerifyJob.KEY_SUBSCRIBE_KEY)
            .apply();

    snackbar.setText(R.string.disable_remote_verify_success).show(); // background thread
});

This is inconsistent with the surrounding action_clear_auditee and action_clear_auditor handlers in the same method, both of which correctly wrap the snackbar update in runOnUiThread(...).

Why it matters

Snackbar.setText(int) ultimately calls TextView.setText(...)checkForRelayout()View.requestLayout(). When the snackbar's view is attached to a window, requestLayout() invokes ViewRootImpl.checkThread(), which throws CalledFromWrongThreadException from any non-UI thread.

In the common case the previous snackbar has already been dismissed by the time the disable flow finishes, so the call happens to not crash — but it is incorrect by Android's contract regardless, and it will crash when another snackbar is still visible (e.g. the user re-opens the menu after a recent action). Touching View state off the UI thread is also racy with respect to fields read by the UI thread.

Fix

Wrap the call in runOnUiThread(...) to match the other two handlers in the same method. One-line change.

Regression introduced in

ee842c4 "improve user feedback for background tasks"(2022-09-12). The same commit correctly wrapped the equivalentclearAuditeeandclearAuditorsnackbar updates inrunOnUiThread; the disable_remote_verify` branch was edited differently and just moved the existing snackbar call into the executor lambda without the wrapper.

The Snackbar update in the disable_remote_verify dialog handler runs
on RemoteVerifyJob.executor (a single-threaded background executor),
unlike the snackbar updates in the surrounding clear_auditee /
clear_auditor handlers which are correctly wrapped in runOnUiThread.

Touching a View off the UI thread is incorrect by Android's contract
and can throw CalledFromWrongThreadException via View.requestLayout()
when the Snackbar's view is currently attached (e.g. another snackbar
is still visible from a prior action).

Wrap the call in runOnUiThread to match the other handlers.
@thestinger thestinger merged commit f2693a3 into GrapheneOS:main Apr 29, 2026
2 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.

2 participants