diff --git a/README.md b/README.md index 17c29a9..a9e9998 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,10 @@ your connection through volunteer proxies located in uncensored countries. Similar to VPNs, which help users bypass Internet censorship, Snowflake disguises your Internet activity as though you’re making a video or voice call, making you less detectable to Internet censors. + +## Panic Kit Support + +This app responds to "Panic Button" apps such as [Ripple](https://github.com/guardianproject/ripple). +If the panic button is triggered, the app will delete all data, reset user preferences, and hide +itself has an app called "Plants" 🪴. If you open that Plants app, the app's name and icon will be +restored. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3ebe536..fc008c4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,15 +20,45 @@ + android:launchMode="singleInstance"> + + + + + - + + - + + + + + + + + + + diff --git a/app/src/main/java/io/bloco/snowflake/Dependencies.kt b/app/src/main/java/io/bloco/snowflake/Dependencies.kt index 155dc60..eb01566 100644 --- a/app/src/main/java/io/bloco/snowflake/Dependencies.kt +++ b/app/src/main/java/io/bloco/snowflake/Dependencies.kt @@ -53,7 +53,7 @@ class Dependencies( } } } - private val appDatabase by lazy { + val appDatabase by lazy { Room.databaseBuilder(app, AppDatabase::class.java, "snowflake").build() } private val statsDao by lazy { appDatabase.statsDao() } diff --git a/app/src/main/java/io/bloco/snowflake/data/AppDataStore.kt b/app/src/main/java/io/bloco/snowflake/data/AppDataStore.kt index 04f1782..5f55ce7 100644 --- a/app/src/main/java/io/bloco/snowflake/data/AppDataStore.kt +++ b/app/src/main/java/io/bloco/snowflake/data/AppDataStore.kt @@ -74,6 +74,12 @@ class AppDataStore( suspend fun setStunServersDate(date: Instant) = dataStore().edit { it[STUN_SERVERS_DATE] = date.epochSeconds } + // Clear + + suspend fun clear() { + dataStoreFlow().first().edit { it.clear() } + } + // Internal @Suppress("ktlint:standard:backing-property-naming") diff --git a/app/src/main/java/io/bloco/snowflake/data/database/AppDatabase.kt b/app/src/main/java/io/bloco/snowflake/data/database/AppDatabase.kt index 1bf113f..534a844 100644 --- a/app/src/main/java/io/bloco/snowflake/data/database/AppDatabase.kt +++ b/app/src/main/java/io/bloco/snowflake/data/database/AppDatabase.kt @@ -4,6 +4,8 @@ import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters import io.bloco.snowflake.models.DayStats +import timber.log.Timber +import java.io.File @Database( entities = [DayStats::class], @@ -12,4 +14,13 @@ import io.bloco.snowflake.models.DayStats @TypeConverters(LocalDateConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun statsDao(): StatsDao + + fun clear() { + try { + if (isOpen) close() + openHelper.readableDatabase.path?.let { File(it).delete() } + } catch (e: Exception) { + Timber.e(e, "Failed to delete database") + } + } } diff --git a/app/src/main/java/io/bloco/snowflake/ui/Launchers.kt b/app/src/main/java/io/bloco/snowflake/ui/Launchers.kt new file mode 100644 index 0000000..5dc02a6 --- /dev/null +++ b/app/src/main/java/io/bloco/snowflake/ui/Launchers.kt @@ -0,0 +1,37 @@ +package io.bloco.snowflake.ui + +import android.app.Activity +import android.content.ComponentName +import android.content.pm.PackageManager + +sealed interface Launcher + +object DefaultLauncher : Launcher + +object HiddenLauncher : Launcher + +val LAUNCHERS = listOf(DefaultLauncher, HiddenLauncher) + +fun Activity.setLauncher(launcher: Launcher) { + LAUNCHERS.forEach { + setLauncherEnabled(launcher = it, isEnabled = launcher == it) + } +} + +private fun Activity.setLauncherEnabled( + launcher: Launcher, + isEnabled: Boolean, +) { + application.packageManager.setComponentEnabledSetting( + ComponentName( + application, + launcher::class.java.name, + ), + if (isEnabled) { + PackageManager.COMPONENT_ENABLED_STATE_ENABLED + } else { + PackageManager.COMPONENT_ENABLED_STATE_DISABLED + }, + PackageManager.DONT_KILL_APP, + ) +} diff --git a/app/src/main/java/io/bloco/snowflake/ui/MainActivity.kt b/app/src/main/java/io/bloco/snowflake/ui/MainActivity.kt index 8e3d79c..eb8fdc5 100644 --- a/app/src/main/java/io/bloco/snowflake/ui/MainActivity.kt +++ b/app/src/main/java/io/bloco/snowflake/ui/MainActivity.kt @@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() { installSplashScreen() super.onCreate(savedInstanceState) enableEdgeToEdge() + setLauncher(DefaultLauncher) setContent { val viewModel = viewModel { dependencies.mainViewModel } diff --git a/app/src/main/java/io/bloco/snowflake/ui/PanicResponseActivity.kt b/app/src/main/java/io/bloco/snowflake/ui/PanicResponseActivity.kt new file mode 100644 index 0000000..71af79a --- /dev/null +++ b/app/src/main/java/io/bloco/snowflake/ui/PanicResponseActivity.kt @@ -0,0 +1,34 @@ +package io.bloco.snowflake.ui + +import android.Manifest +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import info.guardianproject.panic.PanicResponder +import io.bloco.snowflake.App +import kotlinx.coroutines.runBlocking +import kotlin.system.exitProcess + +/* + * If a Panic button is trigger, reset the app state as much as possible + */ +class PanicResponseActivity : ComponentActivity() { + private val dependencies by lazy { (applicationContext as App).dependencies } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (PanicResponder.shouldUseDefaultResponseToTrigger(this)) { + runBlocking { + dependencies.appDataStore.clear() + dependencies.appDatabase.clear() + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + revokeSelfPermissionOnKill(Manifest.permission.POST_NOTIFICATIONS) + } + setLauncher(HiddenLauncher) + exitProcess(0) + } else { + finish() + } + } +} diff --git a/app/src/main/res/drawable/ic_hidden_launcher_foreground.xml b/app/src/main/res/drawable/ic_hidden_launcher_foreground.xml new file mode 100644 index 0000000..4f91ef1 --- /dev/null +++ b/app/src/main/res/drawable/ic_hidden_launcher_foreground.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi/ic_hidden_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_hidden_launcher.xml new file mode 100644 index 0000000..cf03aef --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_hidden_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3353fb8..74db9a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,10 @@ Snowflake + Plants Close + Enabled + Disabled Enable Snowflake. Help others bypass censorship. Snowflake is Active. You’re helping bypass censorship. @@ -68,7 +71,4 @@ Looking… Helping %d - - Enabled - Disabled diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bd3bb0..cc4fb3c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,7 @@ ktor-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +panic = { module = "info.guardianproject.panic:panic", version = "1.0" } [bundles] kotlin = [ @@ -89,6 +90,7 @@ app = [ "ktor-json", "room-runtime", "room-ktx", + "panic", ] ksp = [ "room-compiler",