Skip to content

Commit 0b01542

Browse files
authored
Merge branch 'main' into renovate/kotlinx.serialization
2 parents eae5496 + 38c2a87 commit 0b01542

41 files changed

Lines changed: 1392 additions & 601 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
java-version: '17'
2121

2222
- name: Setup Gradle
23-
uses: gradle/actions/setup-gradle@v3
23+
uses: gradle/actions/setup-gradle@v4
2424

2525
- name: Grant execute permission for gradlew
2626
run: chmod +x gradlew

.github/workflows/publish-release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
java-version: '17'
2424

2525
- name: Setup Gradle
26-
uses: gradle/actions/setup-gradle@v3
26+
uses: gradle/actions/setup-gradle@v4
2727

2828
- name: Grant Permission to Execute
2929
run: chmod +x gradlew
@@ -53,7 +53,7 @@ jobs:
5353
5454
- name: Create GitHub Release
5555
id: create_release
56-
uses: softprops/action-gh-release@v1
56+
uses: softprops/action-gh-release@v2
5757
with:
5858
name: Release ${{ env.VERSION_NAME }}
5959
draft: false
@@ -83,7 +83,7 @@ jobs:
8383
bundler-cache: true
8484

8585
- name: Setup Gradle
86-
uses: gradle/actions/setup-gradle@v3
86+
uses: gradle/actions/setup-gradle@v4
8787

8888
- name: Grant Permission to Execute
8989
run: chmod +x gradlew

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.kotlin.compose)
55
alias(libs.plugins.kotlin.serialization)
6+
id("kotlin-parcelize")
67
}
78

89
android {
@@ -104,6 +105,7 @@ dependencies {
104105
implementation(libs.androidx.runtime.livedata)
105106
implementation(libs.face.detection)
106107
implementation(libs.bcrypt)
108+
implementation(libs.androidx.work.runtime.ktx)
107109

108110
testImplementation(libs.junit)
109111
testImplementation(libs.koin.test.junit4)

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
1111
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
1212
<uses-permission android:name="android.permission.VIBRATE"/>
13+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
14+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" android:minSdkVersion="34"/>
1315

1416
<application
1517
android:name=".SnapSafeApplication"
@@ -63,6 +65,10 @@
6365
android:authorities="${applicationId}.decryptingprovider"
6466
android:exported="false"
6567
android:grantUriPermissions="true"/>
68+
69+
<service
70+
android:name="androidx.work.impl.foreground.SystemForegroundService"
71+
android:foregroundServiceType="dataSync"/>
6672
</application>
6773

6874
</manifest>

app/src/main/kotlin/com/darkrockstudios/app/securecamera/App.kt

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
package com.darkrockstudios.app.securecamera
22

3-
import android.net.Uri
43
import androidx.compose.foundation.layout.imePadding
54
import androidx.compose.material3.Scaffold
65
import androidx.compose.material3.SnackbarHost
76
import androidx.compose.material3.SnackbarHostState
8-
import androidx.compose.runtime.Composable
9-
import androidx.compose.runtime.MutableState
10-
import androidx.compose.runtime.collectAsState
11-
import androidx.compose.runtime.getValue
12-
import androidx.compose.runtime.mutableStateOf
13-
import androidx.compose.runtime.remember
14-
import androidx.compose.runtime.saveable.rememberSaveable
7+
import androidx.compose.runtime.*
158
import androidx.compose.ui.Modifier
169
import androidx.lifecycle.compose.LifecycleResumeEffect
1710
import androidx.navigation.NavHostController
18-
import androidx.navigation.compose.rememberNavController
1911
import com.darkrockstudios.app.securecamera.auth.AuthorizationRepository
20-
import com.darkrockstudios.app.securecamera.navigation.AppDestinations
2112
import com.darkrockstudios.app.securecamera.navigation.AppNavHost
2213
import com.darkrockstudios.app.securecamera.navigation.enforceAuth
2314
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
@@ -28,33 +19,16 @@ import org.koin.compose.koinInject
2819
@Composable
2920
fun App(
3021
capturePhoto: MutableState<Boolean?>,
31-
photosToImport: List<Uri>
22+
startDestination: String,
23+
navController: NavHostController
3224
) {
3325
KoinContext {
3426
SecureCameraTheme {
3527
val snackbarHostState = remember { SnackbarHostState() }
36-
val navController = rememberNavController()
3728
val preferencesManager = koinInject<AppPreferencesDataSource>()
3829
val authorizationRepository = koinInject<AuthorizationRepository>()
3930

4031
val hasCompletedIntro by preferencesManager.hasCompletedIntro.collectAsState(initial = null)
41-
val startDestination = rememberSaveable(hasCompletedIntro) {
42-
if (hasCompletedIntro == true) {
43-
val targetDestination = if (photosToImport.isNotEmpty()) {
44-
AppDestinations.IMPORT_PHOTOS_ROUTE
45-
} else {
46-
AppDestinations.CAMERA_ROUTE
47-
}
48-
49-
if (authorizationRepository.checkSessionValidity()) {
50-
targetDestination
51-
} else {
52-
AppDestinations.createPinVerificationRoute(targetDestination)
53-
}
54-
} else {
55-
AppDestinations.INTRODUCTION_ROUTE
56-
}
57-
}
5832

5933
VerifySessionOnResume(navController, hasCompletedIntro, authorizationRepository)
6034

@@ -63,17 +37,13 @@ fun App(
6337
snackbarHost = { SnackbarHost(snackbarHostState) },
6438
modifier = Modifier.imePadding()
6539
) { paddingValues ->
66-
// Create a mutable state to hold the photos to import
67-
val photosToImportState = remember { mutableStateOf(photosToImport) }
68-
6940
AppNavHost(
7041
navController = navController,
7142
capturePhoto = capturePhoto,
7243
modifier = Modifier,
7344
snackbarHostState = snackbarHostState,
7445
startDestination = startDestination,
7546
paddingValues = paddingValues,
76-
photosToImport = photosToImportState
7747
)
7848
}
7949
}
@@ -82,7 +52,7 @@ fun App(
8252
}
8353

8454
@Composable
85-
fun VerifySessionOnResume(
55+
private fun VerifySessionOnResume(
8656
navController: NavHostController,
8757
hasCompletedIntro: Boolean?,
8858
authorizationRepository: AuthorizationRepository

app/src/main/kotlin/com/darkrockstudios/app/securecamera/AppModule.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.darkrockstudios.app.securecamera
22

3+
import androidx.work.WorkManager
34
import com.darkrockstudios.app.securecamera.auth.AuthorizationRepository
45
import com.darkrockstudios.app.securecamera.auth.PinVerificationViewModel
56
import com.darkrockstudios.app.securecamera.camera.SecureImageRepository
@@ -10,11 +11,11 @@ import com.darkrockstudios.app.securecamera.introduction.IntroductionViewModel
1011
import com.darkrockstudios.app.securecamera.obfuscation.ObfuscatePhotoViewModel
1112
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
1213
import com.darkrockstudios.app.securecamera.security.DeviceInfo
13-
import com.darkrockstudios.app.securecamera.security.EncryptionScheme
14-
import com.darkrockstudios.app.securecamera.security.HardwareBackedEncryptionScheme
1514
import com.darkrockstudios.app.securecamera.security.SecurityLevel
1615
import com.darkrockstudios.app.securecamera.security.SecurityLevelDetector
17-
import com.darkrockstudios.app.securecamera.security.SoftwareEncryptionScheme
16+
import com.darkrockstudios.app.securecamera.security.schemes.EncryptionScheme
17+
import com.darkrockstudios.app.securecamera.security.schemes.HardwareBackedEncryptionScheme
18+
import com.darkrockstudios.app.securecamera.security.schemes.SoftwareEncryptionScheme
1819
import com.darkrockstudios.app.securecamera.settings.SettingsViewModel
1920
import com.darkrockstudios.app.securecamera.usecases.CreatePinUseCase
2021
import com.darkrockstudios.app.securecamera.usecases.PinStrengthCheckUseCase
@@ -37,13 +38,15 @@ val appModule = module {
3738
when (detector.detectSecurityLevel()) {
3839
SecurityLevel.SOFTWARE ->
3940
SoftwareEncryptionScheme(get())
40-
41-
SecurityLevel.TEE, SecurityLevel.STRONGBOX ->
41+
SecurityLevel.TEE, SecurityLevel.STRONGBOX -> {
4242
HardwareBackedEncryptionScheme(get(), get(), get())
43+
}
4344
}
4445
} bind EncryptionScheme::class
4546
singleOf(::SecurityLevelDetector)
4647

48+
single { WorkManager.getInstance(get()) }
49+
4750
factoryOf(::DeviceInfo)
4851

4952
factoryOf(::ThumbnailCache)

app/src/main/kotlin/com/darkrockstudios/app/securecamera/MainActivity.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@ import androidx.activity.ComponentActivity
99
import androidx.activity.compose.setContent
1010
import androidx.activity.enableEdgeToEdge
1111
import androidx.compose.runtime.mutableStateOf
12+
import androidx.navigation.NavHostController
13+
import androidx.navigation.compose.rememberNavController
14+
import com.darkrockstudios.app.securecamera.auth.AuthorizationRepository
15+
import com.darkrockstudios.app.securecamera.navigation.AppDestinations
16+
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
17+
import kotlinx.coroutines.flow.firstOrNull
18+
import kotlinx.coroutines.runBlocking
1219
import org.koin.android.ext.android.inject
1320

1421
class MainActivity : ComponentActivity() {
1522
private var capturePhoto = mutableStateOf<Boolean?>(null)
1623
private val locationRepository: LocationRepository by inject()
24+
private val preferences: AppPreferencesDataSource by inject()
25+
private val authorizationRepository: AuthorizationRepository by inject()
26+
lateinit var navController: NavHostController
1727

1828
override fun onCreate(savedInstanceState: Bundle?) {
1929
super.onCreate(savedInstanceState)
@@ -27,10 +37,31 @@ class MainActivity : ComponentActivity() {
2737

2838
enableEdgeToEdge()
2939

30-
val photosToImport = receiveFiles()
40+
val startDestination = determineStartRoute()
3141
setContent {
32-
App(capturePhoto, photosToImport)
42+
navController = rememberNavController()
43+
App(capturePhoto, startDestination, navController)
44+
}
45+
}
46+
47+
private fun determineStartRoute(): String {
48+
val photosToImport = receiveFiles()
49+
val hasCompletedIntro = runBlocking { preferences.hasCompletedIntro.firstOrNull() ?: false }
50+
val startDestination = if (hasCompletedIntro) {
51+
val targetDestination = if (photosToImport.isNotEmpty()) {
52+
AppDestinations.createImportPhotosRoute(photosToImport)
53+
} else {
54+
AppDestinations.CAMERA_ROUTE
55+
}
56+
if (authorizationRepository.checkSessionValidity()) {
57+
targetDestination
58+
} else {
59+
AppDestinations.createPinVerificationRoute(targetDestination)
60+
}
61+
} else {
62+
AppDestinations.INTRODUCTION_ROUTE
3363
}
64+
return startDestination
3465
}
3566

3667
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {

app/src/main/kotlin/com/darkrockstudios/app/securecamera/SnapSafeApplication.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.darkrockstudios.app.securecamera
22

33
import android.app.Application
4-
import com.darkrockstudios.app.securecamera.auth.AuthorizationRepository
54
import com.darkrockstudios.app.securecamera.camera.SecureImageRepository
65
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
6+
import com.darkrockstudios.app.securecamera.usecases.SecurityResetUseCase
77
import kotlinx.coroutines.flow.first
88
import kotlinx.coroutines.runBlocking
99
import org.koin.android.ext.koin.androidContext
@@ -15,8 +15,8 @@ import timber.log.Timber
1515

1616
class SnapSafeApplication : Application(), KoinComponent {
1717
private val imageManager by inject<SecureImageRepository>()
18-
private val authRepository by inject<AuthorizationRepository>()
1918
private val preferences by inject<AppPreferencesDataSource>()
19+
private val securityReset by inject<SecurityResetUseCase>()
2020

2121
override fun onCreate() {
2222
super.onCreate()
@@ -53,8 +53,7 @@ class SnapSafeApplication : Application(), KoinComponent {
5353
val isProdReady = (preferences.isProdReady.first() == true)
5454
val wasInBeta = intoComplete && !isProdReady
5555
if (wasInBeta) {
56-
imageManager.securityFailureReset()
57-
authRepository.securityFailureReset()
56+
securityReset.reset()
5857
preferences.markProdReady()
5958
} else if (!isProdReady) {
6059
preferences.markProdReady()

app/src/main/kotlin/com/darkrockstudios/app/securecamera/auth/AuthorizationRepository.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.darkrockstudios.app.securecamera.auth
22

33
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
44
import com.darkrockstudios.app.securecamera.preferences.HashedPin
5-
import com.darkrockstudios.app.securecamera.security.EncryptionScheme
5+
import com.darkrockstudios.app.securecamera.security.schemes.EncryptionScheme
66
import kotlinx.coroutines.flow.MutableStateFlow
77
import kotlinx.coroutines.flow.StateFlow
88
import kotlinx.coroutines.flow.asStateFlow
@@ -105,8 +105,8 @@ class AuthorizationRepository(
105105
/**
106106
* Initial key creation
107107
*/
108-
suspend fun createKey(): Boolean {
109-
encryptionScheme.createKey()
108+
suspend fun createKey(pin: String, hashedPin: HashedPin): Boolean {
109+
encryptionScheme.createKey(pin, hashedPin)
110110
return true
111111
}
112112

app/src/main/kotlin/com/darkrockstudios/app/securecamera/camera/SecureImageRepository.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import com.ashampoo.kim.model.GpsCoordinates
1111
import com.ashampoo.kim.model.MetadataUpdate
1212
import com.ashampoo.kim.model.TiffOrientation
1313
import com.darkrockstudios.app.securecamera.preferences.AppPreferencesDataSource
14-
import com.darkrockstudios.app.securecamera.security.EncryptionScheme
14+
import com.darkrockstudios.app.securecamera.security.schemes.EncryptionScheme
1515
import java.io.ByteArrayOutputStream
1616
import java.io.File
1717
import java.text.SimpleDateFormat
@@ -29,9 +29,7 @@ class SecureImageRepository(
2929
fun getGalleryDirectory(): File = File(appContext.filesDir, PHOTOS_DIR)
3030

3131
fun getDecoyDirectory(): File {
32-
val dir = File(appContext.filesDir, DECOYS_DIR)
33-
dir.mkdirs()
34-
return dir
32+
return File(appContext.filesDir, DECOYS_DIR)
3533
}
3634

3735
fun evictKey() {
@@ -421,7 +419,11 @@ class SecureImageRepository(
421419
}
422420

423421
fun isDecoyPhoto(photoDef: PhotoDef): Boolean = getDecoyFile(photoDef).exists()
424-
internal fun getDecoyFile(photoDef: PhotoDef) = File(getDecoyDirectory(), photoDef.photoName)
422+
423+
internal fun getDecoyFile(photoDef: PhotoDef): File {
424+
return File(getDecoyDirectory(), photoDef.photoName)
425+
}
426+
425427
private fun getDecoyFiles(): List<File> {
426428
val dir = getDecoyDirectory()
427429
if (!dir.exists()) {
@@ -436,6 +438,7 @@ class SecureImageRepository(
436438
suspend fun addDecoyPhoto(photoDef: PhotoDef): Boolean {
437439
return if (numDecoys() < MAX_DECOY_PHOTOS) {
438440
val jpgBytes = decryptJpg(photoDef)
441+
getDecoyDirectory().mkdirs()
439442
val decoyFile = getDecoyFile(photoDef)
440443

441444
val ppp = preferencesManager.getHashedPoisonPillPin() ?: return false

0 commit comments

Comments
 (0)