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
4 changes: 4 additions & 0 deletions .github/workflows/build-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ on:
installer_base_name:
required: true
type: string
android_identity_seed:
required: false
type: string

jobs:
build-android:
Expand Down Expand Up @@ -154,6 +157,7 @@ jobs:
BUILD_TYPE: ${{ inputs.build_type }}
VERSION: ${{ inputs.version }}
INSTALLER_NAME: ${{ inputs.installer_base_name }}
ANDROID_IDENTITY_SEED: ${{ inputs.android_identity_seed }}
GOMOBILECACHE: ${{ env.GOMOBILECACHE }}

- name: Upload Android APK
Expand Down
47 changes: 40 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ SDKMANAGER := $(ANDROID_SDK_ROOT)/cmdline-tools/latest/bin/sdk
ANDROID_PAGE_SIZE ?= 16384
# Android 15+ Play requirement: arm64 native libs must be linked for 16 KB page-size compatibility.
ANDROID_GOMOBILE_LDFLAGS ?= -checklinkname=0 -extldflags=-Wl,-z,max-page-size=$(ANDROID_PAGE_SIZE),-z,common-page-size=$(ANDROID_PAGE_SIZE)
ANDROID_STEALTH_IDENTITY ?= $(if $(strip $(filter stealth stealth-%,$(BUILD_TYPE))$(STEALTH_MODE)$(STEALTH_PROFILE)),1,0)
ANDROID_GENERATE_IDENTITY_PROFILE ?= $(ANDROID_STEALTH_IDENTITY)
ANDROID_GENERATED_IDENTITY_PROFILE := $(BUILD_DIR)/stealth/android-identity.properties
ANDROID_IDENTITY_PROFILE ?= $(if $(filter 1 true yes,$(ANDROID_GENERATE_IDENTITY_PROFILE)),$(ANDROID_GENERATED_IDENTITY_PROFILE),)
ANDROID_IDENTITY_SOURCE_PROFILE ?= $(STEALTH_PROFILE_OUT)
ANDROID_IDENTITY_PROFILE_PREREQ = $(MAYBE_STEALTH_PROFILE)
ANDROID_IDENTITY_PROFILE_ARGS = $(if $(strip $(ANDROID_IDENTITY_SOURCE_PROFILE)),--profile "$(ANDROID_IDENTITY_SOURCE_PROFILE)",)
ANDROID_IDENTITY_ENV = $(if $(strip $(ANDROID_IDENTITY_PROFILE)),ANDROID_IDENTITY_PROFILE="$(abspath $(ANDROID_IDENTITY_PROFILE))",)
ANDROID_AUTH_SCHEME = $(strip $(if $(ANDROID_IDENTITY_PROFILE),$(shell sed -n 's/^appAuthScheme=//p' "$(ANDROID_IDENTITY_PROFILE)" 2>/dev/null | tail -n 1),))
ANDROID_IDENTITY_DART_DEFINES = $(if $(ANDROID_AUTH_SCHEME),--dart-define=APP_AUTH_SCHEME=$(ANDROID_AUTH_SCHEME))

IOS_INSTALLER := $(INSTALLER_NAME)$(if $(filter-out production,$(BUILD_TYPE)),-$(BUILD_TYPE)).ipa
IOS_DIR := ios/
Expand Down Expand Up @@ -473,6 +483,28 @@ install-android-sdk: check-android-sdk
.PHONY: install-android-deps
install-android-deps: install-gomobile

.PHONY: android-identity-profile
android-identity-profile: $(ANDROID_IDENTITY_PROFILE_PREREQ)
@if [ "$(ANDROID_GENERATE_IDENTITY_PROFILE)" = "1" ] || [ "$(ANDROID_GENERATE_IDENTITY_PROFILE)" = "true" ] || [ "$(ANDROID_GENERATE_IDENTITY_PROFILE)" = "yes" ]; then \
if [ -z "$(ANDROID_IDENTITY_PROFILE)" ]; then \
echo "ANDROID_IDENTITY_PROFILE is empty"; \
exit 1; \
fi; \
if [ ! -f "$(ANDROID_IDENTITY_PROFILE)" ] || [ -n "$(ANDROID_IDENTITY_SEED)" ] || [ "$(ANDROID_FORCE_IDENTITY_PROFILE)" = "1" ] || [ "$(ANDROID_FORCE_IDENTITY_PROFILE)" = "true" ] || [ "$(ANDROID_FORCE_IDENTITY_PROFILE)" = "yes" ]; then \
mkdir -p "$$(dirname "$(ANDROID_IDENTITY_PROFILE)")"; \
python3 scripts/stealth/generate_android_identity.py \
--output "$(ANDROID_IDENTITY_PROFILE)" \
$(ANDROID_IDENTITY_PROFILE_ARGS) \
$(if $(ANDROID_IDENTITY_SEED),--seed "$(ANDROID_IDENTITY_SEED)",); \
elif [ -n "$(ANDROID_IDENTITY_SOURCE_PROFILE)" ]; then \
python3 scripts/stealth/generate_android_identity.py \
--output "$(ANDROID_IDENTITY_PROFILE)" \
--profile "$(ANDROID_IDENTITY_SOURCE_PROFILE)"; \
else \
echo "Using Android identity profile: $(ANDROID_IDENTITY_PROFILE)"; \
fi; \
fi

.PHONY: android
android: check-android-sdk check-gomobile $(ANDROID_LIB_BUILD)

Expand Down Expand Up @@ -501,16 +533,17 @@ build-android: check-android-sdk check-gomobile
android-debug: $(ANDROID_DEBUG_BUILD)

$(ANDROID_DEBUG_BUILD): $(ANDROID_LIB_BUILD)
flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --debug
$(MAKE) android-identity-profile
$(ANDROID_IDENTITY_ENV) flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --debug $(ANDROID_IDENTITY_DART_DEFINES)

.PHONY: android-apk-release
android-apk-release:
flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES)
android-apk-release: android-identity-profile
$(ANDROID_IDENTITY_ENV) flutter build apk --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)
cp $(ANDROID_APK_RELEASE_BUILD) $(ANDROID_RELEASE_APK)

.PHONY: android-aab-release
android-aab-release:
flutter build appbundle --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES)
android-aab-release: android-identity-profile
$(ANDROID_IDENTITY_ENV) flutter build appbundle --target-platform $(ANDROID_TARGET_PLATFORMS) --verbose --release $(DART_DEFINES) $(ANDROID_IDENTITY_DART_DEFINES)
cp $(ANDROID_AAB_RELEASE_BUILD) $(ANDROID_RELEASE_AAB)
# Copy Play console artifacts
@if [ -f "$(ANDROID_MAPPING_SRC)" ]; then \
Expand All @@ -525,10 +558,10 @@ android-aab-release:


.PHONY: android-release
android-release: clean android pubget gen android-apk-release
android-release: clean android pubget gen android-identity-profile android-apk-release

.PHONY: android-release-ci
android-release-ci: android pubget gen android-apk-release android-aab-release
android-release-ci: android pubget gen android-identity-profile android-apk-release android-aab-release

# iOS Build
.PHONY: install-ios-deps
Expand Down
92 changes: 88 additions & 4 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,64 @@ def start = new Date(2015, 1, 1).getTime()
def now = System.currentTimeMillis()
def code = (int)((now - start) / 1000)

def androidIdentityDefaults = [
applicationId: "org.getlantern.lantern",
appLabel: "Lantern",
launcherLabel: "Lantern",
identityLabel: "Lantern",
identityProfileId: "standard",
identityMetadata: "{}",
vpnSessionName: "LanternVpn",
notificationChannelVpn: "VPN",
notificationChannelDataUsage: "Data Usage",
notificationTitle: "Lantern",
notificationConnectedText: "Lantern VPN is running",
notificationStartingText: "Starting Lantern VPN...",
notificationDisconnectAction: "Disconnect",
quickTileActiveLabel: "VPN Connected",
quickTileInactiveLabel: "VPN Disconnected",
appIcon: "@mipmap/ic_launcher",
appRoundIcon: "@mipmap/ic_launcher_round",
notificationSmallIcon: "@drawable/lantern_notification_icon",
quickTileIcon: "@drawable/lantern_notification_icon",
appAuthScheme: "lantern",
]

def androidIdentityProfilePath =
(project.findProperty("androidIdentityProfile") ?: System.getenv("ANDROID_IDENTITY_PROFILE"))?.toString()?.trim()
def androidIdentity = new LinkedHashMap(androidIdentityDefaults)
if (androidIdentityProfilePath) {
def profileFile = new File(androidIdentityProfilePath)
if (!profileFile.isAbsolute()) {
profileFile = new File(rootProject.projectDir.parentFile, androidIdentityProfilePath)
}
if (!profileFile.exists()) {
throw new GradleException("Android identity profile not found: ${profileFile}")
}
def props = new java.util.Properties()
profileFile.withReader("UTF-8") { props.load(it) }
props.each { key, value ->
def name = key.toString()
if (!androidIdentity.containsKey(name)) {
throw new GradleException("Unknown Android identity profile key '${name}' in ${profileFile}")
}
androidIdentity[name] = value.toString()
}
}

if (!(androidIdentity.applicationId ==~ /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/)) {
throw new GradleException("Invalid Android applicationId in identity profile: ${androidIdentity.applicationId}")
}

def buildConfigString = { value ->
def escaped = value.toString()
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
return "\"${escaped}\""
}

android {
namespace = "org.getlantern.lantern"
compileSdk = 36
Expand Down Expand Up @@ -44,6 +102,10 @@ android {
jvmTarget = "17"
}

buildFeatures {
buildConfig true
}

// Use legacy packaging to helps reduce apk size
packagingOptions {
exclude "DebugProbesKt.bin"
Expand All @@ -53,11 +115,23 @@ android {
}

defaultConfig {
applicationId = "org.getlantern.lantern"
applicationId = androidIdentity.applicationId
minSdkVersion = 24
targetSdk = 36
versionCode = code
versionName = flutter.versionName
manifestPlaceholders = [
appLabel: androidIdentity.appLabel,
launcherLabel: androidIdentity.launcherLabel,
appIcon: androidIdentity.appIcon,
appRoundIcon: androidIdentity.appRoundIcon,
identityLabel: androidIdentity.identityLabel,
identityProfileId: androidIdentity.identityProfileId,
identityMetadata: androidIdentity.identityMetadata,
quickTileIcon: androidIdentity.quickTileIcon,
appAuthScheme: androidIdentity.appAuthScheme,
Comment thread
reflog marked this conversation as resolved.
]
resValue "string", "app_name", androidIdentity.appLabel

ndk {
// Must match the ABIs Flutter actually builds (ANDROID_TARGET_PLATFORMS
Expand All @@ -75,9 +149,19 @@ android {
arguments "-DANDROID_ARM_NEON=TRUE", '-DANDROID_STL=c++_shared'
}
}
buildFeatures {
buildConfig true
}
buildConfigField "String", "ANDROID_IDENTITY_LABEL", buildConfigString(androidIdentity.identityLabel)
buildConfigField "String", "ANDROID_IDENTITY_PROFILE_ID", buildConfigString(androidIdentity.identityProfileId)
buildConfigField "String", "ANDROID_IDENTITY_METADATA", buildConfigString(androidIdentity.identityMetadata)
buildConfigField "String", "VPN_SESSION_NAME", buildConfigString(androidIdentity.vpnSessionName)
buildConfigField "String", "NOTIFICATION_CHANNEL_VPN", buildConfigString(androidIdentity.notificationChannelVpn)
buildConfigField "String", "NOTIFICATION_CHANNEL_DATA_USAGE", buildConfigString(androidIdentity.notificationChannelDataUsage)
buildConfigField "String", "NOTIFICATION_TITLE", buildConfigString(androidIdentity.notificationTitle)
buildConfigField "String", "NOTIFICATION_CONNECTED_TEXT", buildConfigString(androidIdentity.notificationConnectedText)
buildConfigField "String", "NOTIFICATION_STARTING_TEXT", buildConfigString(androidIdentity.notificationStartingText)
buildConfigField "String", "NOTIFICATION_DISCONNECT_ACTION", buildConfigString(androidIdentity.notificationDisconnectAction)
buildConfigField "String", "QUICK_TILE_ACTIVE_LABEL", buildConfigString(androidIdentity.quickTileActiveLabel)
buildConfigField "String", "QUICK_TILE_INACTIVE_LABEL", buildConfigString(androidIdentity.quickTileInactiveLabel)
buildConfigField "String", "NOTIFICATION_SMALL_ICON", buildConfigString(androidIdentity.notificationSmallIcon)
}

signingConfigs {
Expand Down
25 changes: 18 additions & 7 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@

<application
android:name=".LanternApp"
android:icon="@mipmap/ic_launcher"
android:label="Lantern"
android:icon="${appIcon}"
android:label="${appLabel}"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round">
android:roundIcon="${appRoundIcon}">

<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:label="${launcherLabel}"
android:launchMode="singleTask"
android:taskAffinity=""
android:screenOrientation="portrait"
Expand Down Expand Up @@ -65,19 +66,19 @@
<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" />
<data android:scheme="${appAuthScheme}" android:host="auth" />
</intent-filter>
Comment thread
reflog marked this conversation as resolved.
<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" />
<data android:scheme="${appAuthScheme}" 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" />
<data android:scheme="${appAuthScheme}" android:host="private-server" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
Expand Down Expand Up @@ -122,6 +123,16 @@
android:name="flutterEmbedding"
android:value="2" />

<meta-data
android:name="app.identity.label"
android:value="${identityLabel}" />
<meta-data
android:name="app.identity.profile_id"
android:value="${identityProfileId}" />
<meta-data
android:name="app.identity.metadata"
android:value='${identityMetadata}' />


<service
android:name=".service.LanternVpnService"
Expand All @@ -137,7 +148,7 @@
android:name=".service.QuickTileService"
android:directBootAware="true"
android:exported="true"
android:icon="@drawable/lantern_notification_icon"
android:icon="${quickTileIcon}"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
tools:targetApi="n">
<intent-filter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.net.Uri
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.getSystemService
import org.getlantern.lantern.BuildConfig
import org.getlantern.lantern.LanternApp
import org.getlantern.lantern.MainActivity
import org.getlantern.lantern.R
Expand All @@ -28,8 +29,6 @@ class NotificationHelper {
private const val CHANNEL_DATA_USAGE = "data_usage"
const val OPEN_URL = "SERVICE_OPEN_URL"

private const val VPN_DESC = "VPN"
private const val DATA_USAGE_DESC = "Data Usage"
var notificationManager = LanternApp.application.getSystemService<NotificationManager>()!!
val flags =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
Expand All @@ -45,6 +44,7 @@ class NotificationHelper {

private lateinit var dataUsageNotificationChannel: NotificationChannel
private lateinit var vpnNotificationChannel: NotificationChannel
private val notificationSmallIconId: Int by lazy { resolveNotificationSmallIcon() }


init {
Expand All @@ -59,14 +59,14 @@ class NotificationHelper {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vpnNotificationChannel = NotificationChannel(
CHANNEL_VPN,
VPN_DESC,
BuildConfig.NOTIFICATION_CHANNEL_VPN,
NotificationManager.IMPORTANCE_HIGH,
)
notificationManager.createNotificationChannel(vpnNotificationChannel)

dataUsageNotificationChannel = NotificationChannel(
CHANNEL_DATA_USAGE,
DATA_USAGE_DESC,
BuildConfig.NOTIFICATION_CHANNEL_DATA_USAGE,
NotificationManager.IMPORTANCE_HIGH,
)
notificationManager.createNotificationChannel(dataUsageNotificationChannel)
Expand Down Expand Up @@ -96,14 +96,14 @@ class NotificationHelper {
return NotificationCompat.Builder(LanternApp.application, CHANNEL_VPN)
.setShowWhen(false)
.setOngoing(true)
.setContentTitle("Lantern")
.setContentText("Lantern VPN is running")
.setContentTitle(BuildConfig.NOTIFICATION_TITLE)
.setContentText(BuildConfig.NOTIFICATION_CONNECTED_TEXT)
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.lantern_notification_icon)
.setSmallIcon(notificationSmallIconId)
.addAction(
NotificationCompat.Action.Builder(
android.R.drawable.ic_menu_close_clear_cancel,
"Disconnect",
BuildConfig.NOTIFICATION_DISCONNECT_ACTION,
disconnectVPN()
).build()
)
Expand All @@ -123,10 +123,10 @@ class NotificationHelper {
return NotificationCompat.Builder(LanternApp.application, CHANNEL_VPN)
.setShowWhen(false)
.setOngoing(true)
.setContentTitle("Lantern")
.setContentText("Starting Lantern VPN...")
.setContentTitle(BuildConfig.NOTIFICATION_TITLE)
.setContentText(BuildConfig.NOTIFICATION_STARTING_TEXT)
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.lantern_notification_icon)
.setSmallIcon(notificationSmallIconId)
.setContentIntent(contentIntent)
.setSilent(true)
.build()
Expand Down Expand Up @@ -209,7 +209,7 @@ class NotificationHelper {
.setContentTitle(notification.title)
.setContentText(notification.body)
.setOnlyAlertOnce(true)
.setSmallIcon(R.drawable.lantern_notification_icon)
.setSmallIcon(notificationSmallIconId)
.setCategory(NotificationCompat.CATEGORY_EVENT)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
Expand All @@ -234,4 +234,19 @@ class NotificationHelper {
notificationManager.notify(notification.typeID, builder.build())
}

private fun resolveNotificationSmallIcon(): Int {
val configured = BuildConfig.NOTIFICATION_SMALL_ICON.removePrefix("@")
val parts = configured.split("/", limit = 2)
if (parts.size == 2) {
val resolved = LanternApp.application.resources.getIdentifier(
parts[1],
parts[0],
LanternApp.application.packageName,
)
if (resolved != 0) {
return resolved
}
}
return R.drawable.lantern_notification_icon
}
}
Loading
Loading