Modernize Android Permissions Structure
- Dominant language
- Java
- Stars
- 2.7k
- Forks
- 1.1k
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 163
Description
**Is your feature request related to a problem? Please describe.**
Currently Android is requesting some heavy permissions due to how we store images and other needed files on the system. The process has changes to a more modern solution that wouldn't require these.
**LLM Suggestions**
What to change
Move from “shared storage + broad file access” to:
App-specific storage (getFilesDir, getExternalFilesDir) for Forge-managed data
Storage Access Framework (SAF) for user-picked import/export files
MediaStore/Photo Picker only if you truly need user media libraries
Permission impact
If done well, you can likely remove:
MANAGE_EXTERNAL_STORAGE ✅ (big win)
WRITE_EXTERNAL_STORAGE ✅
READ_EXTERNAL_STORAGE ✅ (for modern flows; keep only for very old API support if needed)
Potentially READ_MEDIA_* ✅ if you stop scanning shared media and rely on SAF picker URIs
You’d still keep unrelated permissions like INTERNET, ACCESS_NETWORK_STATE, maybe VIBRATE.
Practical migration plan for Forge
Store decks/saves/cache/assets under app dirs:
internal: private app data
app-specific external: larger files user can see via file managers, no broad permission needed
Replace direct file path imports with SAF:
ACTION_OPEN_DOCUMENT (import)
ACTION_CREATE_DOCUMENT (export)
persist URI permissions when needed
For “share/open with Forge”, accept content:// URIs and stream via ContentResolver (not raw file paths)
Update updater flow:
avoid broad storage; download APK to app-private location
share via FileProvider
REQUEST_INSTALL_PACKAGES only if you keep in-app sideload update path
Caveats
If Forge currently expects arbitrary folder traversal across shared storage, that behavior must be redesigned (SAF tree access or explicit user selection).
You may keep compatibility shims for older Android versions, but gate them by API level and phase out over time.
**What success looks like**
Goal state (end of migration)
Forge data lives in app-scoped dirs (internal/app-specific external).
Import/export uses SAF (content:// URIs), not raw shared-storage paths.
No broad storage permissions on modern Android.
Legacy path-based behavior remains only behind API/version gates during transition.
Phase 0 — Inventory + abstraction (1 release)
Objective: Stop direct File usage from spreading.
Introduce a StorageGateway interface (single entry point for all file operations).
Add implementations:
ScopedStorageGateway (new path)
LegacyStorageGateway (old path/shim)
Route all current callsites through gateway first, without behavior change.
Add telemetry/logging tags (local debug or Sentry breadcrumbs) to track:
operation type (import/export/read/write/list)
URI vs raw path usage
API level
Permissions: no removals yet.
Phase 1 — Write-path migration (1–2 releases)
Objective: New writes go to modern locations first.
New/updated Forge-generated files (decks, logs, caches, assets) write to:
context.getFilesDir() or
context.getExternalFilesDir(null) for larger/user-visible app files
Keep read fallback to legacy locations.
Add one-time “copy forward” for high-value user data (decks/preferences) from old dirs to new app dirs.
Permissions impact:
On API 29+, stop relying on WRITE_EXTERNAL_STORAGE.
Keep manifest entry temporarily for older API shim if still needed.
Phase 2 — Import/export via SAF (2 releases)
Objective: Replace arbitrary filesystem browsing.
Import:
ACTION_OPEN_DOCUMENT + ContentResolver.openInputStream(uri)
Export:
ACTION_CREATE_DOCUMENT + openOutputStream(uri)
Optional folder workflows:
ACTION_OPEN_DOCUMENT_TREE + persisted URI grants
Update share intents to consume content:// safely (already aligned with provider usage patterns).
Permissions impact:
Modern APIs: no READ_EXTERNAL_STORAGE needed for SAF-selected files.
READ_MEDIA_* only if you still directly query media libraries; otherwise remove later.
Phase 3 — Runtime permission gating + feature flags (1 release)
Objective: Turn off legacy by default on modern Android, keep shim selectable.
Add feature flag: legacy_storage_mode (default false on API >= 30, true on older supported).
Gate legacy code by API:
API >= 30: scoped+SAF default, legacy off unless emergency toggle
API 26–29: scoped preferred, legacy shim available
Add migration settings UI:
“Import from old location”
“Use legacy file access (compatibility mode)” (only shown where applicable)
Permissions impact:
Start removing runtime requests on modern APIs even if manifest still contains entries for compatibility.
Phase 4 — Permission cleanup (after telemetry confidence, 1 release)
Objective: Remove broad permissions where no longer required.
Target removals:
MANAGE_EXTERNAL_STORAGE (first priority)
WRITE_EXTERNAL_STORAGE
READ_EXTERNAL_STORAGE
READ_MEDIA_IMAGES/VIDEO/AUDIO (if not needed)
Keep:
INTERNET, ACCESS_NETWORK_STATE, VIBRATE, FOREGROUND_SERVICE (if still used), REQUEST_INSTALL_PACKAGES (only if updater flow requires it)
Phase 5 — Deprecate shim (future)
Objective: Minimize maintenance burden.
Keep shim only for supported API range where truly needed.
Add sunset date/version for legacy_storage_mode.
Remove dead path-based code once usage drops below threshold.
API-level behavior matrix (recommended)
API 33–35: SAF + app-scoped only; no storage runtime permissions.
API 30–32: same as above; avoid all-files model.
API 26–29 (your minSdk starts at 26): keep compatibility shim; prefer app-scoped + SAF.
Only request permissions at runtime on versions where truly required by shim path.
Backward-compat shims to keep during transition
Legacy read fallback chain:
scoped location
migrated location index
legacy shared path
Path-to-URI adapter:
Wrap old modules expecting File by copying to temp app file and passing Uri
Graceful failure messages:
If direct path access fails on modern Android, prompt user to pick file via SAF.
Testing gates before each phase cutover
Import/export decks across API 26, 29, 30, 33, 35
Upgrade install with existing legacy data
“Open with Forge” intent from file manager/cloud providers
Large asset download/update in background
Recovery when URI permission revoked
What users will notice
Fewer scary permission prompts.
More explicit “pick a file/folder” dialogs.
Old installs keep working due to read fallback and optional legacy mode during transition.
Contributor guide
Research direction
No files or tests are named. Begin by locating direct File usage, manifest permissions, and runtime permission requests, then map them to the proposed StorageGateway, SAF, and app-scoped storage phases. Done means modern Android uses app-scoped storage and content:// imports/exports without broad storage permissions, while legacy behavior remains gated and tested across the listed API levels.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, java
- Domain
- mobile
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 28/100