refactor(app): debloat 3.3.1 and fix startup restoration

Maintainer changes on top of PR #2 (Naveax). Reduces the release to two
deliberate connection modes and resolves the issues found in review:

- Lock-screen privacy: move VISIBILITY_PRIVATE and a generic publicVersion
  into MainActivity.updateVpnNotification. The PR's fix lived in an
  unreferenced class and never reached the live notification.
- Haptics: restore the default to off, and reduce HapticSettings to a
  single success() on connect/disconnect via the native channel.
- SOCKS5: make it a real proxyOnly local-proxy mode rather than a TUN
  connection under a different label. TUN stays the default.
- Health probe: restore the idle-only guard so /health is no longer polled
  every 30s while connected.
- Recovery: add a _connectionEpoch counter so connect() aborts an
  in-flight Extreme recovery.
- Startup restoration: rebuild the recovery snapshot from the still-active
  session after an app-process restart (TUN), and surface an honest
  orphaned state with a Reconnect action in proxy mode.

Drops per-app routing entirely. It only ever functioned in TUN --
addDisallowedApplication lives inside the VPN-interface setup that
proxyOnly skips -- and its allowlist half had no Dart caller.

Also removes flutter_local_notifications, the global tap-haptic surface,
and unused runtime telemetry.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
esefxdzandClaude Opus 5 committed 2026-08-31 23:31:51 +03:00
1 parent a71161e7e5
commit 6fcde9dd7b
18 files changed
+271 -1027

No files matched your search

-5
View File
@@ -18,11 +18,6 @@
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="https"/>
</intent>
<!-- App routing only enumerates launcher apps; QUERY_ALL_PACKAGES is not required. -->
<intent>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent>
</queries>
<application
@@ -7,9 +7,6 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
@@ -26,14 +23,11 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
import java.io.ByteArrayOutputStream
import kotlin.math.roundToInt
class MainActivity : FlutterActivity() {
private val installerChannel = "com.revoltvpn.app/installer"
private val notificationChannel = "com.revoltvpn.app/notification"
private val hapticsChannel = "com.revoltvpn.app/haptics"
private val appsChannel = "com.revoltvpn.app/apps"
private val networkChannel = "com.revoltvpn.app/network"
private var notificationChannelReady = false
@@ -75,15 +69,6 @@ class MainActivity : FlutterActivity() {
}
}
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, appsChannel)
.setMethodCallHandler { call, result ->
if (call.method == "getLaunchableApps") {
result.success(launchableApps())
} else {
result.notImplemented()
}
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, networkChannel)
.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
@@ -129,6 +114,14 @@ class MainActivity : FlutterActivity() {
stopIntent.putExtra("COMMAND", AppConfigs.V2RAY_SERVICE_COMMANDS.STOP_SERVICE)
val stopPendingIntent = PendingIntent.getService(this, 0, stopIntent, flags)
// Public lock-screen preview: keep the VPN status visible but hide the
// session countdown/speed so usage metadata never reaches the lock screen.
val publicVersion = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.notification_status_icon)
.setContentTitle("Revolt VPN")
.setContentText("VPN is active")
.build()
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.notification_status_icon)
.setContentTitle(title)
@@ -142,6 +135,8 @@ class MainActivity : FlutterActivity() {
.setSilent(true)
.setShowWhen(false)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(publicVersion)
if (launchIntent != null) {
builder.setContentIntent(PendingIntent.getActivity(this, 0, launchIntent, flags))
@@ -311,54 +306,6 @@ class MainActivity : FlutterActivity() {
null
}
private fun launchableApps(): List<Map<String, Any>> {
val intent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}
val resolved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.queryIntentActivities(
intent,
PackageManager.ResolveInfoFlags.of(0L),
)
} else {
@Suppress("DEPRECATION")
packageManager.queryIntentActivities(intent, 0)
}
return resolved
.mapNotNull { info ->
val appPackage = info.activityInfo?.packageName ?: return@mapNotNull null
if (appPackage == packageName) return@mapNotNull null
val item = mutableMapOf<String, Any>(
"packageName" to appPackage,
"label" to info.loadLabel(packageManager).toString(),
)
appIconPng(info)?.let { item["icon"] = it }
item
}
.distinctBy { it["packageName"] as String }
.sortedBy { (it["label"] as String).lowercase() }
}
private fun appIconPng(info: ResolveInfo): ByteArray? = try {
val drawable = info.loadIcon(packageManager)
val density = resources.displayMetrics.density
val size = (48f * density).roundToInt().coerceIn(48, 144)
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, size, size)
drawable.draw(canvas)
ByteArrayOutputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
output.toByteArray()
}
} catch (_: Exception) {
null
}
private fun performHaptic(kind: String): Boolean {
return try {
val vibrator: Vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {