Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
38 changes: 34 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,45 @@
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/Theme.Snowflake">
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<activity-alias
android:name=".ui.DefaultLauncher"
android:enabled="true"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:targetActivity=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
<activity-alias
android:name=".ui.HiddenLauncher"
android:enabled="false"
android:exported="true"
android:icon="@mipmap/ic_hidden_launcher"
android:label="@string/hidden_app_name"
android:targetActivity=".ui.MainActivity">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>

<activity
android:name=".ui.PanicResponseActivity"
android:exported="true"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/io/bloco/snowflake/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/io/bloco/snowflake/data/AppDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/io/bloco/snowflake/data/database/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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")
}
}
}
37 changes: 37 additions & 0 deletions app/src/main/java/io/bloco/snowflake/ui/Launchers.kt
Original file line number Diff line number Diff line change
@@ -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,
)
}
1 change: 1 addition & 0 deletions app/src/main/java/io/bloco/snowflake/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() {
installSplashScreen()
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setLauncher(DefaultLauncher)

setContent {
val viewModel = viewModel { dependencies.mainViewModel }
Expand Down
34 changes: 34 additions & 0 deletions app/src/main/java/io/bloco/snowflake/ui/PanicResponseActivity.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
14 changes: 14 additions & 0 deletions app/src/main/res/drawable/ic_hidden_launcher_foreground.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="240"
android:translateY="240">
<path
android:pathData="M342,800h276l40,-160L302,640l40,160ZM342,880q-28,0 -49,-17t-28,-44l-45,-179h520l-45,179q-7,27 -28,44t-49,17L342,880ZM200,560h560v-80L200,480v80ZM480,320q0,-100 70,-170t170,-70q0,90 -57,156t-143,80v84h320v160q0,33 -23.5,56.5T760,640L200,640q-33,0 -56.5,-23.5T120,560v-160h320v-84q-86,-14 -143,-80t-57,-156q100,0 170,70t70,170Z"
android:fillColor="#2B5C62"/>
</group>
</vector>
6 changes: 6 additions & 0 deletions app/src/main/res/mipmap-anydpi/ic_hidden_launcher.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_hidden_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_hidden_launcher_foreground"/>
</adaptive-icon>
6 changes: 3 additions & 3 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<resources>
<string name="app_name" translatable="false">Snowflake</string>

<string name="hidden_app_name">Plants</string>
<string name="close">Close</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>

<string name="snowflake_disabled">Enable Snowflake. Help others bypass censorship.</string>
<string name="snowflake_enabled">Snowflake is Active. You’re helping bypass censorship.</string>
Expand Down Expand Up @@ -68,7 +71,4 @@

<string name="quick_settings_looking">Looking…</string>
<string name="quick_settings_helping">Helping %d</string>

<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>
</resources>
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -89,6 +90,7 @@ app = [
"ktor-json",
"room-runtime",
"room-ktx",
"panic",
]
ksp = [
"room-compiler",
Expand Down
Loading