Storage capabilities and Android Keystore test state¶
Read automobile:devices/{deviceId}/storage/capabilities?appId={packageName} to
negotiate storage features. The platform-neutral secure_state domain reports
Android’s single storage.keystore capability through the typed CtrlProxy
discover_keystore request. No app scope or disabled embedded-SDK configuration
means no discovery is attempted. iOS secure-state policy remains owned by #5161.
App-container, user-files, and media-library operations derive from the registered
production provider instances, combined with device/prerequisite signals. Missing
write/list/read providers yield unavailable (or preserve a platform unsupported
state); reset and indexing require the write provider’s feature declaration.
Bounded Android user-files list/read coverage comes separately from
SharedStorageReadService. Android user_files reports namespace_reset (only
the declared Downloads namespace) and optional media_indexing; media_library
reports write and indexing according to its provider. iOS Simulator imports have
no host MediaScanner indexing equivalent. Neither platform exposes media-library
list/read. Capability support describes the registered contract, not proof that
a device workflow has been verified.
Physical-Android app_containers resolves the debuggable app build prerequisite
per appId from the package’s pkgFlags: list/read/write are supported when
DEBUGGABLE is present and unavailable when it is absent. Missing app scope or
an unverified package probe leaves the prerequisite partial. Provider coverage
still applies. Emulators and iOS do not use this probe.
iOS user_files writes and namespace_reset are Simulator-only and require the
managed Files fixture app (dev.jasonpearson.automobile.FilesFixture), not yet
shipped in this repo. iosFilesFixtureInstalled: true means its container was
verified and supports those operations; false means unavailable; an omitted
signal yields partial, pending verification. A missing registered provider
still yields unavailable. The capability resource currently leaves installation
unverified. portable remains false even though platformScope is cross-platform.
Physical iOS stays unsupported regardless of iosFileIntegration. iOS user-files
list/read are unavailable, and media indexing is unsupported. Picker visibility
is a separate write effect, unavailable unless an exact-destination verifier
observes it; host staging alone does not prove visibility. Only the historical
iOS 17.5 / iPhone 15 Pro Simulator experiment is device-tested (see the
accepted design); this provider’s tests
use fakes and do not establish physical-device support.
Canonical file resources use exact storage-domain names:
automobile:devices/{deviceId}/storage-domains/app_containers/{appId}/{container}[/{path}]{?userId}
and automobile:devices/{deviceId}/storage-domains/user_files/{namespace}[/{path}]
(the user-files resource is bounded Android Downloads only).
The app .../apps/{appId}/files/{container}[/{path}]{?userId} and bounded Downloads
.../downloads/{namespace}[/{path}] aliases remain supported until device
verification permits retirement; emitted write/list links remain aliases.
See putAppFile’s canonical examples and session policy.
KeystoreTestState is a code-only opt-in, disabled by default. The app declares an exact scope name and explicit alias set; no prefixes, globs, or cross-app access. The provider exists only in the SDK debug variant and executes in the target app’s security context under the existing shell/root/self/CtrlProxy caller check. This contract is implemented for Android only in this change. Any iOS (Keychain) counterpart is a future implementation that must follow the same written contract: explicit app opt-in, exact allowlists, caller authorization, no secret export, and explicit support boundaries. It is tracked by #5161 and does not exist today. No shared gate is extracted here.
The version-1 JSON envelope contains capability, outcome, optional stable
reason, bridgeAvailable, metadata: supported,
mutation: declared_unsupported, deviceLocked: locked|unlocked|unknown, and
sorted declared scopes. Disabled responses hide scope names and do not invoke the device-lock probe. Host discovery
reports BRIDGE_NOT_INSTALLED from provider resolution, independently of
DISABLED, BRIDGE_UNAVAILABLE, or DECLARED_UNSUPPORTED; clients never parse
error text. Metadata support describes the bridge contract, while outcome
reports its current availability.
The SDK provider’s metadata call accepts a required exact scope and optional
exact alias. It reports only alias, present, and
category: KEY|CERTIFICATE|UNKNOWN. present: false means not present or not
observable, with category UNKNOWN. AndroidKeyStore swallows metadata lookup
errors, making a Keystore daemon failure indistinguishable from an absent alias.
Category detection uses only isKeyEntry / isCertificateEntry, directly
implemented by the SPI. Key entries report KEY: distinguishing secret from
private keys would require loading material (including certificates through
entryInstanceOf). SECRET_KEY and PRIVATE_KEY remain reserved contract values. Undeclared aliases return SCOPE_NOT_DECLARED
without touching Keystore, whether the alias exists or not. No keys, private
keys, certificates, certificate chains, values, or credential bytes are returned.
The injected device-lock probe reports deviceLocked: locked from device lock
state but never blocks metadata reads. Per-alias locked and
authentication_required are reserved outcomes that the current read-only
metadata path cannot observe on real devices. Fake-backend contract tests retain
the typed exception mappings for future slices; they do not prove real-device
behavior. Error messages are logged locally and not serialized.
The debug-only provider contributes the flat storage.keystore descriptor through
AutoMobileSDK.registerCapability on creation and after an authorized call (to
restore it after SDK shutdown). Release builds contain no provider contribution.
An old CtrlProxy APK without the optional discover_keystore advertisement
consistently reports unsupported / DECLARED_UNSUPPORTED on every read.
This slice exposes host capability discovery only. Existing key-value tools
return values and database tools return tables, neither a natural alias-metadata
surface, so secure_state.read stays unavailable. The SDK read-only endpoint is
available to app tests. All mutation calls are unsupported; package-data reset
is separate and explicit. Scoped mutation is tracked by #5190.
Device-backed categories, locked-device behavior, authenticated/StrongBox keys, shell/CtrlProxy reachability, and release authority absence still require a separate device fixture validation.