Skip to content
Open
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
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ ANDROID_AAB_RELEASE_BUILD := $(BUILD_DIR)/app/outputs/bundle/release/app-release
ANDROID_TARGET_PLATFORMS := android-arm,android-arm64
ANDROID_RELEASE_APK := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).apk
ANDROID_RELEASE_AAB := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).aab
ANDROID_STEALTH_NOVPN_APK := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE))-stealth-novpn.apk
ANDROID_STEALTH_NOVPN_AAB := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE))-stealth-novpn.aab
ANDROID_MAPPING_SRC := build/app/outputs/mapping/release/mapping.txt
ANDROID_SYMBOLS_SRC := build/app/outputs/native-debug-symbols/release/native-debug-symbols.zip
ANDROID_NDK_VERSION ?= 28.2.13676358
Expand Down Expand Up @@ -160,6 +162,7 @@ get-command = $(shell which="$$(which $(1) 2> /dev/null)" && if [[ ! -z "$$which
APPDMG := $(call get-command,appdmg)

DART_DEFINES := --dart-define=BUILD_TYPE=$(BUILD_TYPE) $(if $(VERSION),--dart-define=VERSION=$(VERSION),)
STEALTH_NOVPN_DART_DEFINES := $(DART_DEFINES) --dart-define=STEALTH_NO_VPN=true

INSTALLER_RESOURCES := installer-resources

Expand Down Expand Up @@ -512,6 +515,10 @@ android-apk-release:
android-aab-release:
flutter build appbundle --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES)
cp $(ANDROID_AAB_RELEASE_BUILD) $(ANDROID_RELEASE_AAB)
$(MAKE) android-copy-play-artifacts

.PHONY: android-copy-play-artifacts
android-copy-play-artifacts:
# Copy Play console artifacts
@if [ -f "$(ANDROID_MAPPING_SRC)" ]; then \
cp "$(ANDROID_MAPPING_SRC)" mapping.txt; \
Expand All @@ -523,6 +530,20 @@ android-aab-release:
(cd build/app/intermediates/merged_native_libs/release/out && zip -r ../../../../../../debug-symbols.zip lib >/dev/null); \
fi

.PHONY: android-stealth-novpn-apk-release
android-stealth-novpn-apk-release:
ORG_GRADLE_PROJECT_stealthNoVpn=true flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(STEALTH_NOVPN_DART_DEFINES)
cp $(ANDROID_APK_RELEASE_BUILD) $(ANDROID_STEALTH_NOVPN_APK)

.PHONY: android-stealth-novpn-aab-release
android-stealth-novpn-aab-release:
ORG_GRADLE_PROJECT_stealthNoVpn=true flutter build appbundle --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(STEALTH_NOVPN_DART_DEFINES)
cp $(ANDROID_AAB_RELEASE_BUILD) $(ANDROID_STEALTH_NOVPN_AAB)
$(MAKE) android-copy-play-artifacts

Comment thread
reflog marked this conversation as resolved.
.PHONY: android-stealth-novpn-release
android-stealth-novpn-release: android pubget gen android-stealth-novpn-apk-release android-stealth-novpn-aab-release


.PHONY: android-release
android-release: clean android pubget gen android-apk-release
Expand Down
8 changes: 8 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def hasReleaseKeystore =
def start = new Date(2015, 1, 1).getTime()
def now = System.currentTimeMillis()
def code = (int)((now - start) / 1000)
def stealthNoVpn = project.findProperty("stealthNoVpn")?.toString()?.toBoolean() ?: false

android {
namespace = "org.getlantern.lantern"
Expand All @@ -23,6 +24,7 @@ android {

sourceSets {
main {
manifest.srcFile stealthNoVpn ? 'src/main/AndroidManifest.novpn.xml' : 'src/main/AndroidManifest.xml'
jniLibs.srcDirs = ['libs']
jniLibs.srcDirs += ['src/main/jniLibs']
}
Expand Down Expand Up @@ -78,6 +80,9 @@ android {
buildFeatures {
buildConfig true
}
buildConfigField "boolean", "STEALTH_NO_VPN", stealthNoVpn.toString()
buildConfigField "String", "STEALTH_NO_VPN_PROXY_HOST", '"127.0.0.1"'
buildConfigField "int", "STEALTH_NO_VPN_PROXY_PORT", "14986"
}

signingConfigs {
Expand Down Expand Up @@ -110,6 +115,9 @@ android {
}
buildConfigField "boolean", "DEVELOPMENT_MODE", "false"
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
if (stealthNoVpn) {
proguardFiles 'proguard-stealth-novpn.pro'
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions android/app/proguard-stealth-novpn.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-checkdiscard class org.getlantern.lantern.service.LanternVpnService
-checkdiscard class org.getlantern.lantern.service.QuickTileService
159 changes: 159 additions & 0 deletions android/app/src/main/AndroidManifest.novpn.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- <uses-permission android:name="android.permission.USE_EXACT_ALARM" /> -->

<application
android:name=".LanternApp"
android:icon="@mipmap/ic_launcher"
android:label="Lantern"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round">

<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="lantern.io" android:pathPrefix="/report-issue" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="lantern.io" android:pathPrefix="/auth" />
</intent-filter>
<!-- Custom scheme fallback for browsers that don't support Android App Links (e.g. Firefox) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lantern" android:host="auth" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lantern" android:host="report-issue" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lantern" android:host="private-server" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="lantern.io" android:pathPrefix="/private-server" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.lantern.io" android:pathPrefix="/report-issue" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.lantern.io" android:pathPrefix="/auth" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.lantern.io" android:pathPrefix="/private-server" />
</intent-filter>

</activity>

<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>

<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />


<service
android:name="foundation.bridge.SyncService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="User-controlled local proxy connection" />
</service>

<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
android:value="true"/>

</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="alipays" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="alipay" />
</intent>
<package android:name="com.eg.android.AlipayGphone" />
</queries>


</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package foundation.bridge

import org.getlantern.lantern.service.NoVpnLanternService

class SyncService : NoVpnLanternService()
69 changes: 67 additions & 2 deletions android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.getlantern.lantern

import android.Manifest
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.net.VpnService
Expand All @@ -10,6 +11,7 @@ import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import foundation.bridge.SyncService
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import kotlinx.coroutines.CoroutineScope
Expand All @@ -20,6 +22,7 @@ import org.getlantern.lantern.constant.VPNStatus
import org.getlantern.lantern.handler.EventHandler
import org.getlantern.lantern.handler.MethodHandler
import org.getlantern.lantern.service.LanternVpnService
import org.getlantern.lantern.service.NoVpnLanternService
import org.getlantern.lantern.service.QuickTileService
import org.getlantern.lantern.utils.AppLogger
import org.getlantern.lantern.utils.VpnStatusManager
Expand Down Expand Up @@ -51,6 +54,8 @@ class MainActivity : FlutterFragmentActivity() {

private val serviceStartHandler = Handler(Looper.getMainLooper())

private val noVpnServiceClass: Class<out Service>
get() = if (BuildConfig.STEALTH_NO_VPN) SyncService::class.java else NoVpnLanternService::class.java

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Expand Down Expand Up @@ -94,12 +99,19 @@ class MainActivity : FlutterFragmentActivity() {
if (pendingServiceStart && retryCountResume < maxRetriesResume) {
retryCountResume++
AppLogger.d(TAG, "Retrying pending service start")
startLanternService()
retryServiceStart()
}
}

private fun startLanternService() {
AppLogger.d(TAG, "Starting LanternService")
if (BuildConfig.STEALTH_NO_VPN) {
AppLogger.d(TAG, "Stealth no-VPN build skips proxy autostart")
pendingServiceStart = false
retryCount = 0
retryCountResume = 0
return
Comment thread
reflog marked this conversation as resolved.
}
if (isServiceRunning(this, LanternVpnService::class.java)) {
AppLogger.d(TAG, "LanternService is already running")
return
Expand All @@ -125,6 +137,27 @@ class MainActivity : FlutterFragmentActivity() {
}
}

private fun startNoVpnProxyService() {
if (isServiceRunning(this, noVpnServiceClass)) {
AppLogger.d(TAG, "NoVpnLanternService is already running; sending start action")
}
try {
ContextCompat.startForegroundService(this, Intent(this, noVpnServiceClass).apply {
action = NoVpnLanternService.ACTION_START_PROXY
})
AppLogger.d(TAG, "NoVpnLanternService started")
pendingServiceStart = false
retryCount = 0
retryCountResume = 0
} catch (e: IllegalStateException) {
AppLogger.e(TAG, "Cannot start no-VPN proxy service in background: ${e.message}")
pendingServiceStart = true
Comment thread
reflog marked this conversation as resolved.
} catch (e: Exception) {
AppLogger.e(TAG, "Error starting no-VPN proxy service", e)
handleImmediateRetry()
}
}

private fun handleImmediateRetry() {
AppLogger.d(TAG, "Handling immediate retry for LanternService start")
if (retryCount < maxRetries) {
Expand All @@ -133,7 +166,7 @@ class MainActivity : FlutterFragmentActivity() {

AppLogger.d(TAG, "Scheduling immediate retry #$retryCount in ${delay}ms")
serviceStartHandler.postDelayed({
startLanternService()
retryServiceStart()
}, delay)
} else {
/*
Expand All @@ -148,8 +181,20 @@ class MainActivity : FlutterFragmentActivity() {
}
}

private fun retryServiceStart() {
if (BuildConfig.STEALTH_NO_VPN) {
startNoVpnProxyService()
} else {
startLanternService()
}
}


fun startVPN() {
if (BuildConfig.STEALTH_NO_VPN) {
startNoVpnProxyService()
return
}
if (!isVPNServiceReady()) {
AppLogger.d(TAG, "VPN service not ready")
return
Expand Down Expand Up @@ -180,6 +225,13 @@ class MainActivity : FlutterFragmentActivity() {
}

fun connectToServer(tag: String) {
if (BuildConfig.STEALTH_NO_VPN) {
ContextCompat.startForegroundService(this, Intent(this, noVpnServiceClass).apply {
action = NoVpnLanternService.ACTION_CONNECT_TO_SERVER
putExtra("tag", tag)
})
return
}
if (!isVPNServiceReady()) {
AppLogger.d(TAG, "VPN service not ready")
return
Expand Down Expand Up @@ -211,6 +263,19 @@ class MainActivity : FlutterFragmentActivity() {


fun stopVPN() {
if (BuildConfig.STEALTH_NO_VPN) {
if (isServiceRunning(this, noVpnServiceClass)) {
startService(Intent(this, noVpnServiceClass).apply {
action = NoVpnLanternService.ACTION_STOP_PROXY
})
} else {
CoroutineScope(Dispatchers.Main).launch {
runCatching { Mobile.stopVPN() }
VpnStatusManager.postVPNStatus(VPNStatus.Disconnected)
}
}
return
}
if (isServiceRunning(this, LanternVpnService::class.java)) {
LanternApp.application.sendBroadcast(
Intent(LanternVpnService.ACTION_STOP_VPN)
Expand Down
Loading
Loading