Skip to content

Show permissions Android won't ask for again, and reset them without clearing data - #160

Draft
WahdanZ wants to merge 4 commits into
masterfrom
feat/permission-wont-ask-again-146
Draft

WahdanZ wants to merge 4 commits into
masterfrom
feat/permission-wont-ask-again-146

Conversation

@WahdanZ

@WahdanZ WahdanZ commented Oct 1, 2026

Copy link
Copy Markdown
Owner

What and why

See when a permission won't be asked again, and reset it without clearing data. After two denials on Android 11+, or "Don't ask again" on older versions, Android sets USER_FIXED. From then on requestPermissions() returns denied immediately and shows no dialog. Until now, Home › Permissions › Manage… showed that the same way as any other unticked permission, and the only way out was Clear data, which wipes login, prefs and databases.

This PR:

  • Manage… shows three states for each runtime permission: granted, denied — will ask, or denied — won't ask again (right-click to reset). Labels follow the checkbox while the dialog is open.
  • Right-click → "Ask again (keeps app data)" runs pm clear-permission-flags --user 0 <pkg> <perm> user-set user-fixed, then reads dumpsys package back. Success is reported only when the flags are actually gone. pm sets no exit status, so its output is never treated as proof. The row updates in place after a successful reset.
  • The Home summary line counts them, e.g. 3 granted / 2 denied (1 won't ask again).
  • android_diagnose_current_screen adds an optional wontAskAgain array, plus moreWontAskAgain past 12, to its permissions section. The key is absent when nothing is flagged, so existing consumers see identical JSON.
  • The parser (GetApplicationPermission) now keeps USER_SET / USER_FIXED, matching whole tokens so USER_SENSITIVE_WHEN_DENIED does not count. It reads only the first user's runtime block.
  • Fixed: the grant/revoke balloon named the permission as ListItem(name=…, isSelected=…). It now prints the permission name.
  • Sample app: the Permissions screen gains a Request CAMERA only button. It shows rationale= and how fast the answer came back, so you can watch the whole deny-twice → reset → prompt-returns cycle.

No new SpockAction, tool-window control or MCP tool. Tool counts are unchanged.

Refs #146. This is a slice: the remaining piece is an MCP reset tool, which needs a decision on SAFE_ACTION vs DESTRUCTIVE.

How it was verified

Run from the branch head ea184bc:

  • ./gradlew detekt: pass, with no new suppressions
  • ./gradlew test: pass. 1453 tests, 0 failures, 9 skipped (the existing device/launcher-dependent McpSmokeTest / McpBridgeServerTest cases). ToolSafetyTest and McpSmokeTest are unchanged.
  • ./gradlew buildPlugin: pass
  • cd sample && ./gradlew assembleDebug: pass (at d48659b; the later commit touches no sample files)

New unit tests cover:

  • flag parsing (exact tokens, no flags=, granted+fixed, a second user's block ignored)
  • the reset verdict: no success without the read-back, already-granted, only USER_SET left, unknown pm command, not a runtime permission
  • the exact pm command plus the read-back against a mocked IDevice
  • PermissionSummary.describe()
  • the diagnose wontAskAgain / moreWontAskAgain keys

Unproven: needs runIde and/or a physical device or emulator.

  • None of the UI has been seen rendered: the Manage… labels, the right-click menu, the in-place row update, and the summary text.
  • Deny-twice on API 30+ with the sample's Request CAMERA only → shows as won't ask again → Ask again → the system dialog returns and app data is intact.
  • What pm clear-permission-flags does on API 29 and below, including the exact "Unknown command" wording. The plugin should report a refusal there; this is unit-tested only.
  • Right-click (popup trigger) on macOS, including Ctrl-click, vs Windows/Linux.
  • The diagnose output on a real device before and after a reset.

Review notes

Two independent reviews (plugin invariants, and Kotlin) found no blocking issues. The should-fix items were fixed in ea184bc:

  • Labels and the menu went stale after a grant.
  • Ask again on a granted permission reported success.
  • The failure wording when only USER_SET remained.
  • A later user's runtime block could leak permissions into the list.
  • wontAskAgain was truncated with no count of how many were cut.

Still open (nits / known limits):

  • CheckBoxDialog is shared with the action-settings dialog and SpockActionsPopup. In all three, a right-click no longer toggles a row, and a click in the empty space below the list no longer toggles the last row. These changes are intended; smoke-test both other dialogs in runIde.
  • Ask again is also offered on a denied row with only USER_SET ("denied — will ask"). There it clears the denied-once state before a second denial. Restrict it to won't-ask-again rows if that is confusing.
  • The open dialog does not re-read flags after a grant or revoke. A row revoked inside the dialog reads "denied, will ask" unless it already had USER_FIXED.
  • --user 0 is hard-coded in both the parser and the reset (single-user assumption, consistent with the rest of the plugin).
  • Untouched nits: the existing star imports and the handelSelection typo in CheckBoxDialog, and a double blank line.
  • Trivial merge conflicts with PRs Give android_get_ui_tree tap points and a labels-kept filter #150 and Add android_set_animations, and read animation scales back #152, in CHANGELOG.md [Unreleased] and one line of docs/MCP.md.

Produced unattended by the SpockAdb daily agent team (planner → builder → two reviewers). It needs a human read and a device run before merge.

🤖 Generated with Claude Code

WahdanZ and others added 4 commits October 1, 2026 22:56
After two denials (or one "Don't ask again" before Android 11) the
platform sets USER_FIXED and requestPermissions() answers denied with no
dialog. The Manage... dialog showed that exactly like a permission never
requested, and the only way back was Clear data, which also wipes the
login and the database.

The permission parser now keeps USER_SET and USER_FIXED. Manage... labels
each denied row as "will ask" or "won't ask again", and right-click offers
Ask again, which runs pm clear-permission-flags and trusts only a dumpsys
read-back, since pm sets no exit status. The Home summary counts them and
the diagnose permissions section lists them.

Also stops the grant/revoke balloon printing ListItem's toString, and
ignores dialog clicks below the last row, which used to toggle it.

Refs #146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The existing button asks for five permissions at once, which makes it
hard to deny one twice and see the system stop prompting. The new button
asks for CAMERA alone and prints how fast the answer came back, so an
instant denial with no dialog is visible on the device.

Refs #146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Release notes come from CHANGELOG.md, and agents reading docs/MCP.md
need to know the diagnose permissions section can carry wontAskAgain.

Refs #146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review found the dialog's labels and right-click menu were a snapshot
while the checkbox was live: a won't-ask-again row ticked to grant still
said "won't ask again" and still offered Ask again, which then reported
success for a granted permission. Labels now follow the box, the menu
only appears on unticked rows, and a successful Ask again updates its
row in place through a success flag on resetPermissionPrompt.

The reset verdict also refuses a granted permission, and names USER_SET
when that is the flag left behind. The parser now stops after the first
runtime-permissions block, so a permission only a work profile holds is
not listed, and the diagnose section counts won't-ask-again names past
its cap as moreDenied does.

Refs #146

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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