diff --git a/samples/CameraAccessAndroid/app/build.gradle.kts b/samples/CameraAccessAndroid/app/build.gradle.kts index fde26851..9c1b9cb4 100644 --- a/samples/CameraAccessAndroid/app/build.gradle.kts +++ b/samples/CameraAccessAndroid/app/build.gradle.kts @@ -83,6 +83,7 @@ dependencies { implementation(libs.datastore.preferences) implementation(libs.gson) implementation(libs.lifecycle.process) + implementation(libs.security.crypto) androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.test.uiautomator) androidTestImplementation(libs.androidx.test.rules) diff --git a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt index cfd3cdae..29d0d468 100644 --- a/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt +++ b/samples/CameraAccessAndroid/app/src/main/java/com/meta/wearable/dat/externalsampleapps/cameraaccess/settings/SettingsManager.kt @@ -2,6 +2,8 @@ package com.meta.wearable.dat.externalsampleapps.cameraaccess.settings import android.content.Context import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey import com.meta.wearable.dat.externalsampleapps.cameraaccess.Secrets import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -40,9 +42,11 @@ enum class CaptureSource(val value: String, val label: String) { object SettingsManager { private const val PREFS_NAME = "visionclaw_settings" + private const val SECURE_PREFS_NAME = "visionclaw_secure" private const val DEFAULT_SIGNALING_URL = "wss://YOUR_SIGNALING_SERVER" private lateinit var prefs: SharedPreferences + private lateinit var securePrefs: SharedPreferences private val _captureSourceFlow = MutableStateFlow(CaptureSource.PHONE) val captureSourceFlow: StateFlow = _captureSourceFlow.asStateFlow() @@ -55,10 +59,28 @@ object SettingsManager { fun init(context: Context) { prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + val masterKey = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + securePrefs = EncryptedSharedPreferences.create( + context, + SECURE_PREFS_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) + migrateGatewayToken() _captureSourceFlow.value = CaptureSource.fromValue(prefs.getString("captureSource", null)) refreshUnlocked() } + private fun migrateGatewayToken() { + val legacyToken = prefs.getString("gatewayToken", null) ?: return + if (securePrefs.edit().putString("gatewayToken", legacyToken).commit()) { + prefs.edit().remove("gatewayToken").apply() + } + } + fun refreshUnlocked() { _unlockedFlow.value = isUnlocked } @@ -76,9 +98,9 @@ object SettingsManager { set(value) = prefs.edit().putString("gatewayBaseUrl", value).apply() var gatewayToken: String - get() = prefs.getString("gatewayToken", null) ?: Secrets.gatewayToken + get() = securePrefs.getString("gatewayToken", null) ?: Secrets.gatewayToken set(value) { - prefs.edit().putString("gatewayToken", value).apply() + securePrefs.edit().putString("gatewayToken", value).apply() refreshUnlocked() } @@ -104,8 +126,8 @@ object SettingsManager { get() = isGatewayConfigured && accountStatus != "pending" fun signOut() { + securePrefs.edit().remove("gatewayToken").apply() prefs.edit() - .remove("gatewayToken") .remove("accountEmail") .remove("accountUserId") .remove("accountStatus") @@ -134,6 +156,7 @@ object SettingsManager { set(value) = prefs.edit().putString("webrtcSignalingURL", value).apply() fun resetAll() { + securePrefs.edit().clear().apply() prefs.edit().clear().apply() _captureSourceFlow.value = CaptureSource.PHONE refreshUnlocked() diff --git a/samples/CameraAccessAndroid/gradle/libs.versions.toml b/samples/CameraAccessAndroid/gradle/libs.versions.toml index 0b459f74..d5501a64 100644 --- a/samples/CameraAccessAndroid/gradle/libs.versions.toml +++ b/samples/CameraAccessAndroid/gradle/libs.versions.toml @@ -18,6 +18,7 @@ camerax = "1.4.1" datastore = "1.1.1" gson = "2.11.0" lifecycleProcess = "2.8.7" +securityCrypto = "1.1.0-alpha06" [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } @@ -46,6 +47,7 @@ camerax-view = { group = "androidx.camera", name = "camera-view", version.ref = datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" } +security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }