// ProofMode Android Integration Example
// This file shows how to integrate the ProofMode library into your Android app
package org.proofmode.android.example
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.location.Location
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import android.telephony.TelephonyManager
import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.guardianproject.proofmode.*
import java.io.ByteArrayOutputStream
import java.io.File
// MARK: - ProofMode Integration ViewModel
class ProofModeViewModel(private val context: Context) : ViewModel() {
private val proofMode = ProofMode()
private val _isInitialized = MutableStateFlow(false)
val isInitialized: StateFlow<Boolean> = _isInitialized
private val _proofs = MutableStateFlow<List<String>>(emptyList())
val proofs: StateFlow<List<String>> = _proofs
private val _isGeneratingProof = MutableStateFlow(false)
val isGeneratingProof: StateFlow<Boolean> = _isGeneratingProof
private val _lastError = MutableStateFlow<String?>(null)
val lastError: StateFlow<String?> = _lastError
// Initialize ProofMode with app-specific configuration
fun initialize() {
viewModelScope.launch {
try {
val storageDir = File(context.filesDir, "proofs")
if (!storageDir.exists()) {
storageDir.mkdirs()
}
proofMode.initialize(
storagePath = storageDir.absolutePath,
email = "user@example.com", // Should be user-configurable
passphrase = "secure_passphrase" // Should be from secure storage
)
_isInitialized.value = true
} catch (e: Exception) {
_lastError.value = "Failed to initialize ProofMode: ${e.message}"
}
}
}
// Generate proof for image bitmap
fun generateProof(
bitmap: Bitmap,
description: String? = null,
includeLocation: Boolean = true,
onProgress: ((String) -> Unit)? = null
) {
viewModelScope.launch {
_isGeneratingProof.value = true
_lastError.value = null
try {
// Convert bitmap to byte array
val imageData = withContext(Dispatchers.IO) {
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
stream.toByteArray()
}
onProgress?.invoke("Collecting metadata...")
// Collect metadata
val metadata = mutableMapOf<String, String>()
if (!description.isNullOrBlank()) {
metadata["description"] = description
}
// Add device information
metadata.putAll(collectDeviceInfo())
// Add location if requested and permitted
if (includeLocation) {
collectLocationInfo()?.let { location ->
metadata["latitude"] = location.latitude.toString()
metadata["longitude"] = location.longitude.toString()
metadata["altitude"] = location.altitude.toString()
metadata["accuracy"] = location.accuracy.toString()
}
}
// Add network information
metadata.putAll(collectNetworkInfo())
onProgress?.invoke("Generating cryptographic proof...")
// Generate the proof
val hash = proofMode.generateProof(imageData, metadata)
// Update the proofs list
val currentProofs = _proofs.value.toMutableList()
currentProofs.add(hash)
_proofs.value = currentProofs
onProgress?.invoke("Proof generated successfully!")
} catch (e: Exception) {
_lastError.value = "Failed to generate proof: ${e.message}"
} finally {
_isGeneratingProof.value = false
}
}
}
// Verify an existing proof
fun verifyProof(hash: String, onResult: (Boolean) -> Unit) {
viewModelScope.launch {
try {
val isValid = proofMode.verifyProof(hash)
onResult(isValid)
} catch (e: Exception) {
_lastError.value = "Failed to verify proof: ${e.message}"
onResult(false)
}
}
}
// Get PGP public key for sharing
fun getPublicKey(): String {
return try {
proofMode.getPublicKey()
} catch (e: Exception) {
_lastError.value = "Failed to get public key: ${e.message}"
""
}
}
// MARK: - Metadata Collection
private fun collectDeviceInfo(): Map<String, String> {
return mapOf(
"device_manufacturer" to Build.MANUFACTURER,
"device_model" to Build.MODEL,
"device_brand" to Build.BRAND,
"os_version" to Build.VERSION.RELEASE,
"api_level" to Build.VERSION.SDK_INT.toString(),
"device_id" to getDeviceId()
)
}
private fun getDeviceId(): String {
return try {
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
} catch (e: Exception) {
"unknown"
}
}
private fun collectLocationInfo(): Location? {
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return null
}
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return try {
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
?: locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
} catch (e: Exception) {
null
}
}
private fun collectNetworkInfo(): Map<String, String> {
val info = mutableMapOf<String, String>()
try {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = connectivityManager.activeNetworkInfo
if (activeNetwork != null) {
info["network_type"] = activeNetwork.typeName
info["network_subtype"] = activeNetwork.subtypeName
info["is_connected"] = activeNetwork.isConnected.toString()
}
// WiFi information
val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
if (wifiManager.isWifiEnabled) {
val wifiInfo = wifiManager.connectionInfo
if (wifiInfo.ssid != "<unknown ssid>") {
info["wifi_ssid"] = wifiInfo.ssid.replace("\"", "")
}
info["wifi_rssi"] = wifiInfo.rssi.toString()
}
// Cellular information
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_PHONE_STATE
) == PackageManager.PERMISSION_GRANTED
) {
info["carrier_name"] = telephonyManager.networkOperatorName ?: "unknown"
info["network_operator"] = telephonyManager.networkOperator ?: "unknown"
}
} catch (e: Exception) {
info["network_error"] = e.message ?: "unknown"
}
return info
}
}
// MARK: - Camera Integration Example
import android.app.Activity
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class CameraProofFragment : Fragment() {
private lateinit var proofModeViewModel: ProofModeViewModel
// Camera launcher
private val cameraLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val imageBitmap = result.data?.extras?.get("data") as? Bitmap
imageBitmap?.let { bitmap ->
generateProofForImage(bitmap)
}
}
}
// Gallery launcher
private val galleryLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { uri ->
val bitmap = getBitmapFromUri(uri)
bitmap?.let { generateProofForImage(it) }
}
}
}
// Permission launcher
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val locationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
// Handle permission results
if (cameraGranted) {
// Camera permission granted, can take photos
}
if (locationGranted) {
// Location permission granted, can include GPS data
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
proofModeViewModel = ProofModeViewModel(requireContext())
proofModeViewModel.initialize()
// Request necessary permissions
requestPermissions()
}
private fun requestPermissions() {
val permissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE
)
permissionLauncher.launch(permissions)
}
fun takePhotoForProof() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
cameraLauncher.launch(intent)
}
fun selectPhotoForProof() {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
galleryLauncher.launch(intent)
}
private fun generateProofForImage(bitmap: Bitmap) {
proofModeViewModel.generateProof(
bitmap = bitmap,
description = "Photo taken with ProofMode",
includeLocation = true
) { progress ->
// Update UI with progress
// e.g., show progress dialog or update progress bar
}
}
private fun getBitmapFromUri(uri: Uri): Bitmap? {
return try {
val inputStream = requireContext().contentResolver.openInputStream(uri)
BitmapFactory.decodeStream(inputStream)
} catch (e: Exception) {
null
}
}
}
// MARK: - Complete Activity Example
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class ProofModeActivity : AppCompatActivity() {
private lateinit var proofModeViewModel: ProofModeViewModel
private lateinit var cameraFragment: CameraProofFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_proofmode) // Your layout
// Initialize ProofMode
proofModeViewModel = ProofModeViewModel(this)
proofModeViewModel.initialize()
// Initialize camera fragment
cameraFragment = CameraProofFragment()
// Observe ProofMode state
observeProofModeState()
// Setup UI click listeners
setupUI()
}
private fun observeProofModeState() {
lifecycleScope.launch {
proofModeViewModel.isInitialized.collect { initialized ->
if (initialized) {
// ProofMode is ready to use
enableProofGeneration()
}
}
}
lifecycleScope.launch {
proofModeViewModel.lastError.collect { error ->
error?.let {
Toast.makeText(this@ProofModeActivity, it, Toast.LENGTH_LONG).show()
}
}
}
lifecycleScope.launch {
proofModeViewModel.proofs.collect { proofs ->
// Update UI with new proofs
updateProofsList(proofs)
}
}
}
private fun setupUI() {
// Setup button click listeners
// findViewById<Button>(R.id.btnTakePhoto).setOnClickListener {
// cameraFragment.takePhotoForProof()
// }
// findViewById<Button>(R.id.btnSelectPhoto).setOnClickListener {
// cameraFragment.selectPhotoForProof()
// }
// findViewById<Button>(R.id.btnViewProofs).setOnClickListener {
// showProofsList()
// }
}
private fun enableProofGeneration() {
// Enable UI elements once ProofMode is initialized
Toast.makeText(this, "ProofMode initialized successfully", Toast.LENGTH_SHORT).show()
}
private fun updateProofsList(proofs: List<String>) {
// Update RecyclerView or ListView with proofs
// This would typically update an adapter
}
private fun showProofsList() {
// Show a list of generated proofs
// Could open a new activity or fragment
}
}
/*
* Integration Notes:
*
* 1. Add ProofMode AAR to your project:
* - Copy proofmode.aar to app/libs/
* - Add implementation files('libs/proofmode.aar') to build.gradle
*
* 2. Add required permissions to AndroidManifest.xml:
* - <uses-permission android:name="android.permission.CAMERA" />
* - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
* - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
* - <uses-permission android:name="android.permission.INTERNET" />
*
* 3. Handle runtime permissions properly:
* - Request permissions before using camera or location
* - Provide fallbacks when permissions are denied
* - Follow Android permission best practices
*
* 4. Initialize ProofMode early in your app lifecycle:
* - Call initialize() in Application.onCreate() or Activity.onCreate()
* - Handle initialization errors gracefully
* - Store configuration securely
*
* 5. Handle background processing:
* - Proof generation can take time for large images
* - Use coroutines or background threads
* - Provide user feedback during processing
*
* 6. Security considerations:
* - Store PGP keys securely (Android Keystore)
* - Don't log sensitive information
* - Validate all inputs
* - Handle native library loading errors
*
* 7. Testing:
* - Test on different Android versions (API 21+)
* - Test on different device architectures
* - Test with various image sizes and formats
* - Test permission scenarios (granted/denied)
*/