The c2pa-android-example repository contains a prototype Android application demonstrating how to capture photos and videos, embed Content Credentials, and then sign them.
The app is built using modern Android development practices with Jetpack Compose and CameraX.
- Photo and video capture: Uses CameraX for a robust camera implementation.
- C2PA signing: Signs captured media with RSA keys to create a C2PA manifest, ensuring content authenticity.
- In-place signing: Correctly handles modern Android Scoped Storage by signing files from
content://URIs in-place using a temporary file strategy. - Location tagging: Fetches the device's current location and embeds it into the C2PA manifest.
- Video recording control: Supports starting, pausing, resuming, and stopping video recordings, with the UI reacting to the current state.
- Runtime permissions: Gracefully handles camera, audio, and location permissions.
- UI: 100% Jetpack Compose for a declarative and modern UI.
- Architecture: MVVM (Model-View-ViewModel).
- Dependency Injection: Hilt for managing dependencies.
- Camera: CameraX for camera operations, including preview, image capture, and video capture.
- Content Authenticity: C2PA Android Library for creating and embedding authenticity manifests.
- Asynchronicity: Kotlin Coroutines and Flow for managing background tasks and reactive data streams.
- Permissions: Accompanist Permissions for a clean, composable-based permissions handling flow.
- Location: Google Play Services Fused Location Provider for accurate and efficient location fetching.
- Image Loading: Coil for displaying the thumbnail preview.
- MapLibre Compose: For showing location of captured media.
The application's logic is centered around a few key files that demonstrate a clean separation of concerns.
The main entry point for the camera UI that handles all runtime permission requests (Camera, Audio, Location):
- Displays either a permission request screen or the main
CameraCaptureScreenthat:- Observes state from
CameraViewModel(recordingState,thumbPreviewUri). - Displays the
CameraXViewfinderfor the live camera preview. ProvidesIconButtoncontrols for taking photos, starting/pausing/resuming/stopping video, and navigating to a media preview. - The thumbnail preview dynamically updates by observing
StateFlowof thethumbPreviewUri.
- Observes state from
The core of the application's logic; acts as the bridge between the UI and the data/domain layers and:
- Manages the camera's lifecycle via
bindToCamera. - Handles user actions like
takePhoto()andcaptureVideo(). - Manages the recording state through the
RecordingStatesealed class, exposing it as aStateFlowfor the UI to observe. - Fetches the device's location using
getCurrentLocation()before a capture. - After a capture is saved, it triggers the
signMediaFile()function.
Contains the signMediaFile() function, which is the heart of the content signing process:
- Scoped Storage Solution: To sign a file from a
content://URI, it first creates a temporary file in the app's cache, signs that file in-place, and then writes the modified (signed) file back to the original URI. - Constructs the C2PA manifest, adding claims for the action (
c2pa.created), software agent, and location data. - Configures the
SignerInfoobject using RSA keys stored locally as PEM files.
getOrGenerateKeyPair(),saveKeyToPem(), etc.: A set of functions for generating an RSA KeyPair and saving it to disk in the standard PEM format.readPemString(): A robust function to read PEM files, correctly stripping headers/footers.getCurrentLocation(): A suspend function that uses the Fused Location Provider to fetch the device's location one time.getMediaFlow(): A function that queries the Android MediaStore to retrieve all photos and videos created by the app, exposing them as a Kotlin Flow.