1"use strict";(globalThis.webpackChunkopensource_contentauth_org=globalThis.webpackChunkopensource_contentauth_org||[]).push([[6231],{4969:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"sdk-repos/c2pa-android-example/README","title":"C2PA Android example app","description":"The c2pa-android-example repository contains a prototype Android application demonstrating how to capture photos and videos, embed Content Credentials, and then sign them.","source":"@site/docs/sdk-repos/c2pa-android-example/README.md","sourceDirName":"sdk-repos/c2pa-android-example","slug":"/sdk-repos/c2pa-android-example/","permalink":"/docs/sdk-repos/c2pa-android-example/","draft":false,"unlisted":false,"editUrl":"https://github.com/contentauth/c2pa-android-example/edit/main/README.md","tags":[],"version":"current","frontMatter":{},"sidebar":"docs","previous":{"title":"C2PA Android library","permalink":"/docs/sdk-repos/c2pa-android/"},"next":{"title":"CAWG on mobile","permalink":"/docs/cawg-mobile"}}');var r=i(4848),a=i(8453);const s={},o="C2PA Android example app",c={},d=[{value:"Key Features",id:"key-features",level:2},{value:"Tech stack and core libraries",id:"tech-stack-and-core-libraries",level:2},{value:"Project Architecture",id:"project-architecture",level:2},{value:"UI layer (/ui)",id:"ui-layer-ui",level:3},{value:"ViewModel layer (/ui)",id:"viewmodel-layer-ui",level:3},{value:"C2PA signing logic (/c2pa_signing)",id:"c2pa-signing-logic-c2pa_signing",level:3},{value:"General utilities (/utils)",id:"general-utilities-utils",level:3}];function l(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"c2pa-android-example-app",children:"C2PA Android example app"})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-android-example",children:"c2pa-android-example repository"})," contains a prototype Android application demonstrating how to capture photos and videos, embed Content Credentials, and then sign them."]}),"\n",(0,r.jsx)(n.p,{children:"The app is built using modern Android development practices with Jetpack Compose and CameraX."}),"\n",(0,r.jsx)(n.h2,{id:"key-features",children:"Key Features"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Photo and video capture"}),": Uses CameraX for a robust camera implementation."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"C2PA signing"}),": Signs captured media with RSA keys to create a C2PA manifest, ensuring content authenticity."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"In-place signing"}),": Correctly handles modern Android Scoped Storage by signing files from ",(0,r.jsx)(n.code,{children:"content://"})," URIs in-place using a temporary file strategy."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Location tagging"}),": Fetches the device's current location and embeds it into the C2PA manifest."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Video recording control"}),": Supports starting, pausing, resuming, and stopping video recordings, with the UI reacting to the current state."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Runtime permissions"}),": Gracefully handles camera, audio, and location permissions."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"tech-stack-and-core-libraries",children:"Tech stack and core libraries"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"UI: 100% Jetpack Compose for a declarative and modern UI."}),"\n",(0,r.jsx)(n.li,{children:"Architecture: MVVM (Model-View-ViewModel)."}),"\n",(0,r.jsx)(n.li,{children:"Dependency Injection: Hilt for managing dependencies."}),"\n",(0,r.jsx)(n.li,{children:"Camera: CameraX for camera operations, including preview, image capture, and video capture."}),"\n",(0,r.jsx)(n.li,{children:"Content Authenticity: C2PA Android Library for creating and embedding authenticity manifests."}),"\n",(0,r.jsx)(n.li,{children:"Asynchronicity: Kotlin Coroutines and Flow for managing background tasks and reactive data streams."}),"\n",(0,r.jsx)(n.li,{children:"Permissions: Accompanist Permissions for a clean, composable-based permissions handling flow."}),"\n",(0,r.jsx)(n.li,{children:"Location: Google Play Services Fused Location Provider for accurate and efficie
1nt location fetching."}),"\n",(0,r.jsx)(n.li,{children:"Image Loading: Coil for displaying the thumbnail preview."}),"\n",(0,r.jsx)(n.li,{children:"MapLibre Compose: For showing location of captured media."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"project-architecture",children:"Project Architecture"}),"\n",(0,r.jsx)(n.p,{children:"The application's logic is centered around a few key files that demonstrate a clean separation of concerns."}),"\n",(0,r.jsx)(n.h3,{id:"ui-layer-ui",children:"UI layer (/ui)"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-android-example/blob/main/app/src/main/java/com/proofmode/c2pa/ui/CameraScreen.kt",children:"CameraScreen.kt"})}),":"]}),"\n",(0,r.jsx)(n.p,{children:"The main entry point for the camera UI that handles all runtime permission requests (Camera, Audio, Location):"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Displays either a permission request screen or the main ",(0,r.jsx)(n.code,{children:"CameraCaptureScreen"})," that:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Observes state from ",(0,r.jsx)(n.code,{children:"CameraViewModel"})," (",(0,r.jsx)(n.code,{children:"recordingState"}),", ",(0,r.jsx)(n.code,{children:"thumbPreviewUri"}),")."]}),"\n",(0,r.jsxs)(n.li,{children:["Displays the ",(0,r.jsx)(n.code,{children:"CameraXViewfinder"})," for the live camera preview. Provides ",(0,r.jsx)(n.code,{children:"IconButton"})," controls for taking photos, starting/pausing/resuming/stopping video, and navigating to a media preview."]}),"\n",(0,r.jsxs)(n.li,{children:["The thumbnail preview dynamically updates by observing ",(0,r.jsx)(n.code,{children:"StateFlow"})," of the ",(0,r.jsx)(n.code,{children:"thumbPreviewUri"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"viewmodel-layer-ui",children:"ViewModel layer (/ui)"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-android-example/blob/main/app/src/main/java/com/proofmode/c2pa/ui/CameraViewModel.kt",children:"CameraViewModel.kt"}),":"]})}),"\n",(0,r.jsx)(n.p,{children:"The core of the application's logic; acts as the bridge between the UI and the data/domain layers and:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Manages the camera's lifecycle via ",(0,r.jsx)(n.code,{children:"bindToCamera"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Handles user actions like ",(0,r.jsx)(n.code,{children:"takePhoto()"})," and ",(0,r.jsx)(n.code,{children:"captureVideo()"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Manages the recording state through the ",(0,r.jsx)(n.code,{children:"RecordingState"})," sealed class, exposing it as a ",(0,r.jsx)(n.code,{children:"StateFlow"})," for the UI to observe."]}),"\n",(0,r.jsxs)(n.li,{children:["Fetches the device's location using ",(0,r.jsx)(n.code,{children:"getCurrentLocation()"})," before a capture."]}),"\n",(0,r.jsxs)(n.li,{children:["After a capture is saved, it triggers the ",(0,r.jsx)(n.code,{children:"signMediaFile()"})," function."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"c2pa-signing-logic-c2pa_signing",children:"C2PA signing logic (/c2pa_signing)"}),"\n",(0,r.jsx)(n.p,{children:(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-android-example/blob/main/app/src/main/java/com/proofmode/c2pa/c2pa_signing/C2PAManager.kt",children:"Utils.kt"}),":"]})}),"\n",(0,r.jsxs)(n.p,{children:["Contains the ",(0,r.jsx)(n.code,{children:"signMediaFile()"})," function, which is the heart of the content signing process:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Scoped Storage Solution: To sign a file from a ",(0,r.jsx)(n.code,{children:"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."]}),"\n",(0,r.jsxs)(n.li,{children:["Constructs the C2PA manifest, adding claims for the action (",(0,r.jsx)(n.code,{children:"c2pa.created"}),"), software agent, and location data."]}),"\n",(0,r.jsxs)(n.li,{children:["Configures the ",(0,r.jsx)(n.code,{children:"SignerInfo"})," object using RSA keys stored locally as PEM files."]}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"general-utilities-utils",children:"General utilities (/utils)"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.a,{href:"https://github.com/contentauth/c2pa-android-example/blob/main/app/src/main/java/com/proofmode/c2pa/utils/Utils.kt",children:"Utils.
1kt"})}),":"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"generateKeyPair()"}),": Generates an RSA key-pair from the Android Keystore (default signing mode is keystore)."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getCurrentLocation()"}),": A suspend function that uses the Fused Location Provider to fetch the device's location one time."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"getMediaFlow()"}),": A function that queries the Android MediaStore to retrieve all photos and videos created by the app, exposing them as a Kotlin Flow."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(l,{...e})}):l(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>o});var t=i(6540);const r={},a=t.createContext(r);function s(e){const n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.