diff --git a/.github/workflows/android-app.yml b/.github/workflows/android-app.yml index 406d36245574..4dea9151d803 100644 --- a/.github/workflows/android-app.yml +++ b/.github/workflows/android-app.yml @@ -193,7 +193,7 @@ jobs: uses: burrunan/gradle-cache-action@v1 with: job-id: jdk17 - arguments: assembleDebug + arguments: assembleOssProdDebug gradle-version: wrapper build-root-directory: android @@ -222,7 +222,7 @@ jobs: uses: burrunan/gradle-cache-action@v1 with: job-id: jdk17 - arguments: assembleAndroidTest + arguments: assembleOssProdAndroidTest gradle-version: wrapper build-root-directory: android execution-only-caches: true diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index b1b1b5616a39..6c61206afe20 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,7 +1,8 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.android.build.gradle.internal.tasks.factory.dependsOn import java.io.FileInputStream -import java.util.Properties +import java.util.* +import org.gradle.configurationcache.extensions.capitalized plugins { id(Dependencies.Plugin.androidApplicationId) @@ -47,23 +48,18 @@ android { if (keystorePropertiesFile.exists()) { signingConfigs { - create("release") { + create(SigningConfigs.RELEASE) { storeFile = file("$credentialsPath/app-keys.jks") storePassword = keystoreProperties.getProperty("storePassword") keyAlias = keystoreProperties.getProperty("keyAlias") keyPassword = keystoreProperties.getProperty("keyPassword") } } - - buildTypes { - getByName("release") { - signingConfig = signingConfigs.getByName("release") - } - } } buildTypes { - getByName("release") { + getByName(BuildTypes.RELEASE) { + signingConfig = signingConfigs.findByName(SigningConfigs.RELEASE) isMinifyEnabled = true isShrinkResources = true proguardFiles( @@ -71,40 +67,51 @@ android { "proguard-rules.pro" ) } - - create("fdroid") { - initWith(buildTypes.getByName("release")) - isMinifyEnabled = true - isShrinkResources = true + create(BuildTypes.FDROID) { + initWith(buildTypes.getByName(BuildTypes.RELEASE)) signingConfig = null - matchingFallbacks += "release" + matchingFallbacks += BuildTypes.RELEASE + } + create(BuildTypes.LEAK_CANARY) { + initWith(buildTypes.getByName(BuildTypes.DEBUG)) + applicationIdSuffix = ".leakcanary" + matchingFallbacks += BuildTypes.DEBUG } + } - create("leakCanary") { - initWith(buildTypes.getByName("debug")) - matchingFallbacks += "debug" + flavorDimensions += FlavorDimensions.BILLING + flavorDimensions += FlavorDimensions.INFRASTRUCTURE + + productFlavors { + create(Flavors.OSS) { + dimension = FlavorDimensions.BILLING + isDefault = true + } + create(Flavors.PLAY) { dimension = FlavorDimensions.BILLING } + create(Flavors.PROD) { + dimension = FlavorDimensions.INFRASTRUCTURE + isDefault = true + } + create(Flavors.DEVMOLE) { + dimension = FlavorDimensions.INFRASTRUCTURE + applicationId = "net.mullvad.mullvadvpn.devmole" } } sourceSets { getByName("main") { - val changelogDir = gradleLocalProperties(rootProject.projectDir).getOrDefault( - "OVERRIDE_CHANGELOG_DIR", - defaultChangeLogAssetsDirectory - ) + val changelogDir = + gradleLocalProperties(rootProject.projectDir) + .getOrDefault("OVERRIDE_CHANGELOG_DIR", defaultChangeLogAssetsDirectory) assets.srcDirs(extraAssetsDirectory, changelogDir) jniLibs.srcDirs(extraJniDirectory) } } - buildFeatures { - compose = true - } + buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = Versions.kotlinCompilerExtensionVersion - } + composeOptions { kotlinCompilerExtensionVersion = Versions.kotlinCompilerExtensionVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 @@ -114,12 +121,13 @@ android { kotlinOptions { allWarningsAsErrors = false jvmTarget = Versions.jvmTarget - freeCompilerArgs = listOf( - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi", - // Opt-in option for Koin annotation of KoinComponent. - "-opt-in=kotlin.RequiresOptIn" - ) + freeCompilerArgs = + listOf( + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi", + // Opt-in option for Koin annotation of KoinComponent. + "-opt-in=kotlin.RequiresOptIn" + ) } tasks.withType { @@ -143,29 +151,29 @@ android { packaging { jniLibs.useLegacyPackaging = true resources { - pickFirsts += setOf( - // Fixes packaging error caused by: androidx.compose.ui:ui-test-junit4 - "META-INF/AL2.0", - "META-INF/LGPL2.1", - // Fixes packaging error caused by: jetified-junit-* - "META-INF/LICENSE.md", - "META-INF/LICENSE-notice.md" - ) + pickFirsts += + setOf( + // Fixes packaging error caused by: androidx.compose.ui:ui-test-junit4 + "META-INF/AL2.0", + "META-INF/LGPL2.1", + // Fixes packaging error caused by: jetified-junit-* + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md" + ) } } applicationVariants.configureEach { - val alwaysShowChangelog = gradleLocalProperties(rootProject.projectDir) - .getProperty("ALWAYS_SHOW_CHANGELOG") ?: "false" + val alwaysShowChangelog = + gradleLocalProperties(rootProject.projectDir).getProperty("ALWAYS_SHOW_CHANGELOG") + ?: "false" - buildConfigField( - "boolean", - "ALWAYS_SHOW_CHANGELOG", - alwaysShowChangelog - ) + buildConfigField("boolean", "ALWAYS_SHOW_CHANGELOG", alwaysShowChangelog) - val enableInAppVersionNotifications = gradleLocalProperties(rootProject.projectDir) - .getProperty("ENABLE_IN_APP_VERSION_NOTIFICATIONS") ?: "true" + val enableInAppVersionNotifications = + gradleLocalProperties(rootProject.projectDir) + .getProperty("ENABLE_IN_APP_VERSION_NOTIFICATIONS") + ?: "true" buildConfigField( "boolean", @@ -174,9 +182,63 @@ android { ) } + applicationVariants.all { + val artifactSuffix = buildString { + productFlavors.getOrNull(0)?.name?.let { billingFlavorName -> + if (billingFlavorName != Flavors.OSS) { + append(".$billingFlavorName") + } + } + + productFlavors.getOrNull(1)?.name?.let { infrastructureFlavorName -> + if (infrastructureFlavorName != Flavors.PROD) { + append(".$infrastructureFlavorName") + } + } + + if (buildType.name != BuildTypes.RELEASE) { + append(".${buildType.name}") + } + } + + val variantName = name + val capitalizedVariantName = variantName.capitalized() + val artifactName = "MullvadVPN-${versionName}${artifactSuffix}" + + tasks.register("create${capitalizedVariantName}DistApk") { + from(packageApplicationProvider) + into("${rootDir.parent}/dist") + include { it.name.endsWith(".apk") } + rename { "$artifactName.apk" } + } + + val createDistBundle = + tasks.register("create${capitalizedVariantName}DistBundle") { + from("$buildDir/outputs/bundle/$variantName") + into("${rootDir.parent}/dist") + include { it.name.endsWith(".aab") } + rename { "$artifactName.aab" } + } + + createDistBundle.dependsOn("bundle$capitalizedVariantName") + } + project.tasks.preBuild.dependsOn("ensureJniDirectoryExist") } +androidComponents { + beforeVariants { variantBuilder -> + variantBuilder.enable = + variantBuilder.let { currentVariant -> + val enabledVariants = + enabledVariantTriples.map { (billing, infra, buildType) -> + billing + infra.capitalized() + buildType.capitalized() + } + enabledVariants.contains(currentVariant.name) + } + } +} + configure { // Skip the lintClassPath configuration, which relies on many dependencies that has been flagged // to have CVEs, as it's related to the lint tooling rather than the project's compilation class @@ -212,9 +274,7 @@ afterEvaluate { } } -play { - serviceAccountCredentials.set(file("play-api-key.json")) -} +play { serviceAccountCredentials.set(file("play-api-key.json")) } configurations.all { resolutionStrategy { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NotificationBanner.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NotificationBanner.kt index 04fff334c3c1..4e638d56b2e7 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NotificationBanner.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/NotificationBanner.kt @@ -24,13 +24,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension -import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.extensions.getExpiryQuantityString import net.mullvad.mullvadvpn.compose.state.ConnectNotificationState import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER import net.mullvad.mullvadvpn.compose.util.rememberPrevious -import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes +import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD import net.mullvad.mullvadvpn.lib.common.util.getErrorNotificationResources import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.lib.theme.Dimens @@ -122,10 +121,10 @@ private fun ShowNotification( versionInfoNotification( versionInfo = connectNotificationState.versionInfo, onClickUpdateVersion = - if (BuildConfig.BUILD_TYPE != BuildTypes.RELEASE) { - onClickUpdateVersion - } else { + if (IS_PLAY_BUILD) { null + } else { + onClickUpdateVersion } ) } @@ -133,10 +132,10 @@ private fun ShowNotification( accountExpiryNotification( expiry = connectNotificationState.expiry, onClickShowAccount = - if (BuildConfig.BUILD_TYPE != BuildTypes.RELEASE) { - onClickShowAccount - } else { + if (IS_PLAY_BUILD) { null + } else { + onClickShowAccount } ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt index cf8af6fc0736..c84804d95587 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import me.onebone.toolbar.ScrollStrategy import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState -import net.mullvad.mullvadvpn.BuildConfig import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.button.ActionButton import net.mullvad.mullvadvpn.compose.component.CollapsingToolbarScaffold @@ -36,7 +35,7 @@ import net.mullvad.mullvadvpn.compose.component.InformationView import net.mullvad.mullvadvpn.compose.component.MissingPolicy import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar import net.mullvad.mullvadvpn.compose.state.AccountUiState -import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes +import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD import net.mullvad.mullvadvpn.lib.common.util.capitalizeFirstCharOfEachWord import net.mullvad.mullvadvpn.lib.common.util.openAccountPageInBrowser import net.mullvad.mullvadvpn.lib.theme.Dimens @@ -158,7 +157,7 @@ fun AccountScreen( Spacer(modifier = Modifier.weight(1f)) - if (BuildConfig.BUILD_TYPE != BuildTypes.RELEASE) { + if (IS_PLAY_BUILD.not()) { ActionButton( text = stringResource(id = R.string.manage_account), onClick = onManageAccountClick, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt index a9c3aa5ea946..ef5250424a9e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreen.kt @@ -39,9 +39,9 @@ import net.mullvad.mullvadvpn.compose.extensions.itemWithDivider import net.mullvad.mullvadvpn.compose.state.SettingsUiState import net.mullvad.mullvadvpn.compose.test.LAZY_LIST_TEST_TAG import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes -import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild import net.mullvad.mullvadvpn.lib.common.util.openLink import net.mullvad.mullvadvpn.lib.theme.Dimens +import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild @OptIn(ExperimentalMaterial3Api::class) @Preview @@ -132,7 +132,7 @@ fun SettingsScreen( Uri.parse( context.resources .getString(R.string.download_url) - .appendHideNavOnReleaseBuild() + .appendHideNavOnPlayBuild() ) ) }, @@ -208,7 +208,7 @@ fun SettingsScreen( Uri.parse( context.resources .getString(R.string.privacy_policy_url) - .appendHideNavOnReleaseBuild() + .appendHideNavOnPlayBuild() ) ) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildConstant.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildConstant.kt new file mode 100644 index 000000000000..db76afc769fe --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/constant/BuildConstant.kt @@ -0,0 +1,5 @@ +package net.mullvad.mullvadvpn.constant + +import net.mullvad.mullvadvpn.BuildConfig + +const val IS_PLAY_BUILD = BuildConfig.FLAVOR_billing == "play" diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt index 7592be144b08..b32f162761c2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/ConnectFragment.kt @@ -10,10 +10,10 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.platform.ComposeView import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.screen.ConnectScreen -import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.ui.MainActivity import net.mullvad.mullvadvpn.ui.NavigationBarPainter +import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel import org.koin.androidx.viewmodel.ext.android.viewModel @@ -58,9 +58,7 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter { Intent( Intent.ACTION_VIEW, Uri.parse( - requireContext() - .getString(R.string.download_url) - .appendHideNavOnReleaseBuild() + requireContext().getString(R.string.download_url).appendHideNavOnPlayBuild() ) ) .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt index 464395d2d806..4b500d3f586e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/PrivacyDisclaimerFragment.kt @@ -10,12 +10,12 @@ import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.compose.screen.PrivacyDisclaimerScreen -import net.mullvad.mullvadvpn.lib.common.util.appendHideNavOnReleaseBuild import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras import net.mullvad.mullvadvpn.lib.theme.AppTheme import net.mullvad.mullvadvpn.ui.MainActivity import net.mullvad.mullvadvpn.ui.NavigationBarPainter import net.mullvad.mullvadvpn.ui.StatusBarPainter +import net.mullvad.mullvadvpn.util.appendHideNavOnPlayBuild import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel import org.koin.android.ext.android.inject @@ -51,7 +51,7 @@ class PrivacyDisclaimerFragment : Fragment(), StatusBarPainter, NavigationBarPai val privacyPolicyUrlIntent = Intent( Intent.ACTION_VIEW, - Uri.parse(getString(R.string.privacy_policy_url).appendHideNavOnReleaseBuild()) + Uri.parse(getString(R.string.privacy_policy_url).appendHideNavOnPlayBuild()) ) context?.startActivity(privacyPolicyUrlIntent) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt new file mode 100644 index 000000000000..4214aac20921 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/StringExtensions.kt @@ -0,0 +1,10 @@ +package net.mullvad.mullvadvpn.util + +import net.mullvad.mullvadvpn.constant.IS_PLAY_BUILD + +fun String.appendHideNavOnPlayBuild(): String = + if (IS_PLAY_BUILD) { + "$this?hide_nav" + } else { + this + } diff --git a/android/build.gradle.kts b/android/build.gradle.kts index b9ff70a359a3..8f801b85a794 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -50,11 +50,7 @@ allprojects { tasks.withType { gradleReleaseChannel = "current" - rejectVersionIf { - candidate.version.isNonStableVersion() - } + rejectVersionIf { candidate.version.isNonStableVersion() } } -tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) -} +tasks.register("clean", Delete::class) { delete(rootProject.buildDir) } diff --git a/android/buildSrc/build.gradle.kts b/android/buildSrc/build.gradle.kts index d58dd3f651b2..3d5c9c964312 100644 --- a/android/buildSrc/build.gradle.kts +++ b/android/buildSrc/build.gradle.kts @@ -1,11 +1,5 @@ -plugins { - `kotlin-dsl` -} +plugins { `kotlin-dsl` } -repositories { - maven("https://plugins.gradle.org/m2/") -} +repositories { maven("https://plugins.gradle.org/m2/") } -kotlin { - jvmToolchain(17) -} +kotlin { jvmToolchain(17) } diff --git a/android/buildSrc/src/main/kotlin/BuildVariants.kt b/android/buildSrc/src/main/kotlin/BuildVariants.kt new file mode 100644 index 000000000000..dcb9e548e2ab --- /dev/null +++ b/android/buildSrc/src/main/kotlin/BuildVariants.kt @@ -0,0 +1,44 @@ +import BuildTypes.DEBUG +import BuildTypes.FDROID +import BuildTypes.LEAK_CANARY +import BuildTypes.RELEASE +import Flavors.DEVMOLE +import Flavors.OSS +import Flavors.PLAY +import Flavors.PROD + +object BuildTypes { + const val DEBUG = "debug" + const val RELEASE = "release" + const val FDROID = "fdroid" + const val LEAK_CANARY = "leakCanary" +} + +object SigningConfigs { + const val RELEASE = "release" +} + +object FlavorDimensions { + const val BILLING = "billing" + const val INFRASTRUCTURE = "infrastructure" +} + +object Flavors { + const val OSS = "oss" + const val PLAY = "play" + + const val PROD = "prod" + const val DEVMOLE = "devmole" +} + +val enabledVariantTriples = + listOf( + Triple(OSS, PROD, DEBUG), + Triple(OSS, PROD, RELEASE), + Triple(OSS, PROD, FDROID), + Triple(OSS, PROD, LEAK_CANARY), + Triple(PLAY, PROD, DEBUG), + Triple(PLAY, PROD, RELEASE), + Triple(PLAY, DEVMOLE, DEBUG), + Triple(PLAY, DEVMOLE, RELEASE) + ) diff --git a/android/lib/build.gradle.kts b/android/lib/build.gradle.kts index e69de29bb2d1..8b137891791f 100644 --- a/android/lib/build.gradle.kts +++ b/android/lib/build.gradle.kts @@ -0,0 +1 @@ + diff --git a/android/lib/common-test/build.gradle.kts b/android/lib/common-test/build.gradle.kts index ca820dbadd27..3d0ef6c0288d 100644 --- a/android/lib/common-test/build.gradle.kts +++ b/android/lib/common-test/build.gradle.kts @@ -7,9 +7,7 @@ android { namespace = "net.mullvad.mullvadvpn.lib.common.test" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/android/lib/common/build.gradle.kts b/android/lib/common/build.gradle.kts index d8a84581402f..ba33ab7db7a6 100644 --- a/android/lib/common/build.gradle.kts +++ b/android/lib/common/build.gradle.kts @@ -8,9 +8,7 @@ android { namespace = "net.mullvad.mullvadvpn.lib.common" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/StringExtensions.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt similarity index 80% rename from android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/StringExtensions.kt rename to android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt index 3fa4ec303408..f46664e92930 100644 --- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/StringExtensions.kt +++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/CommonStringExtensions.kt @@ -1,7 +1,5 @@ package net.mullvad.mullvadvpn.lib.common.util -import net.mullvad.mullvadvpn.lib.common.BuildConfig -import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat @@ -23,13 +21,6 @@ fun String.parseAsDateTime(): DateTime? { } } -fun String.appendHideNavOnReleaseBuild(): String = - if (BuildTypes.RELEASE == BuildConfig.BUILD_TYPE) { - "$this?hide_nav" - } else { - this - } - fun String.groupWithSpaces(groupCharSize: Int = 4): String { return fold(StringBuilder()) { formattedText, nextDigit -> if ((formattedText.length % (groupCharSize + 1)) == groupCharSize) { diff --git a/android/lib/endpoint/build.gradle.kts b/android/lib/endpoint/build.gradle.kts index 282d9df76a06..f46c70c7d785 100644 --- a/android/lib/endpoint/build.gradle.kts +++ b/android/lib/endpoint/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.mullvadvpn.lib.endpoint" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") @@ -28,6 +24,4 @@ android { } } -dependencies { - implementation(Dependencies.Kotlin.stdlib) -} +dependencies { implementation(Dependencies.Kotlin.stdlib) } diff --git a/android/lib/ipc/build.gradle.kts b/android/lib/ipc/build.gradle.kts index 6fbc935b705b..0c6213e889cb 100644 --- a/android/lib/ipc/build.gradle.kts +++ b/android/lib/ipc/build.gradle.kts @@ -18,9 +18,7 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/android/lib/model/build.gradle.kts b/android/lib/model/build.gradle.kts index bd949928bfe4..040c2fca0c0c 100644 --- a/android/lib/model/build.gradle.kts +++ b/android/lib/model/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.mullvadvpn.model" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/android/lib/resource/build.gradle.kts b/android/lib/resource/build.gradle.kts index d42d6b93559d..e5a33a661575 100644 --- a/android/lib/resource/build.gradle.kts +++ b/android/lib/resource/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.mullvadvpn.lib.resource" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("lint.xml") @@ -29,6 +25,4 @@ android { } } -dependencies { - implementation(Dependencies.AndroidX.appcompat) -} +dependencies { implementation(Dependencies.AndroidX.appcompat) } diff --git a/android/lib/talpid/build.gradle.kts b/android/lib/talpid/build.gradle.kts index e18c1c962bc6..ac760a860ed0 100644 --- a/android/lib/talpid/build.gradle.kts +++ b/android/lib/talpid/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.talpid" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/android/lib/theme/build.gradle.kts b/android/lib/theme/build.gradle.kts index d5efe5c96fe9..687184c5c0ac 100644 --- a/android/lib/theme/build.gradle.kts +++ b/android/lib/theme/build.gradle.kts @@ -7,26 +7,18 @@ android { namespace = "net.mullvad.mullvadvpn.lib.theme" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } - buildFeatures { - compose = true - } + buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = Versions.kotlinCompilerExtensionVersion - } + composeOptions { kotlinCompilerExtensionVersion = Versions.kotlinCompilerExtensionVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/android/scripts/run-instrumented-tests.sh b/android/scripts/run-instrumented-tests.sh index 5e855ca242e1..5c6ace121b97 100755 --- a/android/scripts/run-instrumented-tests.sh +++ b/android/scripts/run-instrumented-tests.sh @@ -20,7 +20,7 @@ while [[ "$#" -gt 0 ]]; do TEST_TYPE="app" USE_ORCHESTRATOR="false" TEST_PACKAGE="net.mullvad.mullvadvpn.test" - TEST_APK="$APK_BASE_DIR/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk" + TEST_APK="$APK_BASE_DIR/app/build/outputs/apk/androidTest/ossProd/debug/app-oss-prod-debug-androidTest.apk" ;; e2e) TEST_TYPE="e2e" @@ -105,7 +105,7 @@ echo "Starting instrumented tests of type: $TEST_TYPE" echo "" echo "### Install packages ###" -adb install -t "$APK_BASE_DIR/app/build/outputs/apk/debug/app-debug.apk" +adb install -t "$APK_BASE_DIR/app/build/outputs/apk/ossProd/debug/app-oss-prod-debug.apk" adb install "$TEST_APK" if [[ "$USE_ORCHESTRATOR" == "true" ]]; then echo "Using ORCHESTRATOR_APK_PATH: $ORCHESTRATOR_APK_PATH" diff --git a/android/service/build.gradle.kts b/android/service/build.gradle.kts index cf10c2859f72..bbdb862cf30e 100644 --- a/android/service/build.gradle.kts +++ b/android/service/build.gradle.kts @@ -8,24 +8,27 @@ android { namespace = "net.mullvad.mullvadvpn.service" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") abortOnError = true warningsAsErrors = true } + + flavorDimensions += FlavorDimensions.BILLING + + productFlavors { + create(Flavors.OSS) { dimension = FlavorDimensions.BILLING } + create(Flavors.PLAY) { dimension = FlavorDimensions.BILLING } + } } dependencies { diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/constant/BuildConstant.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/constant/BuildConstant.kt new file mode 100644 index 000000000000..5c7c387c91e3 --- /dev/null +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/constant/BuildConstant.kt @@ -0,0 +1,5 @@ +package net.mullvad.mullvadvpn.service.constant + +import net.mullvad.mullvadvpn.service.BuildConfig + +const val IS_PLAY_BUILD = BuildConfig.FLAVOR == "play" diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt index e07f1725d72c..83e2c970ece0 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt @@ -10,7 +10,6 @@ import android.net.Uri import androidx.core.app.NotificationCompat import kotlin.properties.Delegates.observable import kotlinx.coroutines.delay -import net.mullvad.mullvadvpn.lib.common.constant.BuildTypes import net.mullvad.mullvadvpn.lib.common.constant.MAIN_ACTIVITY_CLASS import net.mullvad.mullvadvpn.lib.common.constant.MULLVAD_PACKAGE_NAME import net.mullvad.mullvadvpn.lib.common.util.Intermittent @@ -18,9 +17,9 @@ import net.mullvad.mullvadvpn.lib.common.util.JobTracker import net.mullvad.mullvadvpn.lib.common.util.SdkUtils import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.isNotificationPermissionGranted import net.mullvad.mullvadvpn.model.AccountExpiry -import net.mullvad.mullvadvpn.service.BuildConfig import net.mullvad.mullvadvpn.service.MullvadDaemon import net.mullvad.mullvadvpn.service.R +import net.mullvad.mullvadvpn.service.constant.IS_PLAY_BUILD import net.mullvad.mullvadvpn.service.endpoint.AccountCache import org.joda.time.DateTime import org.joda.time.Duration @@ -105,7 +104,7 @@ class AccountExpiryNotification( Uri.parse("$buyMoreTimeUrl?token=${daemon.await().getWwwAuthToken()}") } val intent = - if (BuildTypes.RELEASE == BuildConfig.BUILD_TYPE) { + if (IS_PLAY_BUILD) { Intent().apply { setClassName(MULLVAD_PACKAGE_NAME, MAIN_ACTIVITY_CLASS) flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 03bb8c781c94..5df9c08f1e07 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -1,8 +1,5 @@ -include( - ":app", - ":service", - ":tile" -) +include(":app", ":service", ":tile") + include( ":lib:common", ":lib:endpoint", @@ -13,9 +10,5 @@ include( ":lib:theme", ":lib:common-test" ) -include( - ":test", - ":test:common", - ":test:e2e", - ":test:mockapi" -) + +include(":test", ":test:common", ":test:e2e", ":test:mockapi") diff --git a/android/test/build.gradle.kts b/android/test/build.gradle.kts index e69de29bb2d1..8b137891791f 100644 --- a/android/test/build.gradle.kts +++ b/android/test/build.gradle.kts @@ -0,0 +1 @@ + diff --git a/android/test/common/build.gradle.kts b/android/test/common/build.gradle.kts index 941e79f757f5..99fcb9d2a7c1 100644 --- a/android/test/common/build.gradle.kts +++ b/android/test/common/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.mullvadvpn.test.common" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") @@ -30,9 +26,7 @@ android { androidComponents { beforeVariants { variantBuilder -> - variantBuilder.apply { - enable = name != "release" - } + variantBuilder.apply { enable = name != BuildTypes.RELEASE } } } diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts index 6e8f5a0eb94c..8e24974fcd93 100644 --- a/android/test/e2e/build.gradle.kts +++ b/android/test/e2e/build.gradle.kts @@ -16,13 +16,12 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" targetProjectPath = ":app" + missingDimensionStrategy(FlavorDimensions.BILLING, Flavors.OSS) + missingDimensionStrategy(FlavorDimensions.INFRASTRUCTURE, Flavors.PROD) + fun Properties.addRequiredPropertyAsBuildConfigField(name: String) { val value = getProperty(name) ?: throw GradleException("Missing property: $name") - buildConfigField( - type = "String", - name = name, - value = "\"$value\"" - ) + buildConfigField(type = "String", name = name, value = "\"$value\"") } Properties().apply { @@ -32,33 +31,31 @@ android { } fun MutableMap.addOptionalPropertyAsArgument(name: String) { - val value = rootProject.properties.getOrDefault(name, null) as? String - ?: gradleLocalProperties(rootProject.projectDir).getProperty(name) + val value = + rootProject.properties.getOrDefault(name, null) as? String + ?: gradleLocalProperties(rootProject.projectDir).getProperty(name) if (value != null) { put(name, value) } } - testInstrumentationRunnerArguments += mutableMapOf().apply { - put("clearPackageData", "true") - addOptionalPropertyAsArgument("valid_test_account_token") - addOptionalPropertyAsArgument("invalid_test_account_token") - } + testInstrumentationRunnerArguments += + mutableMapOf().apply { + put("clearPackageData", "true") + addOptionalPropertyAsArgument("valid_test_account_token") + addOptionalPropertyAsArgument("invalid_test_account_token") + } } - testOptions { - execution = "ANDROIDX_TEST_ORCHESTRATOR" - } + testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") @@ -79,7 +76,6 @@ configure { dependencies { implementation(project(Projects.testCommon)) implementation(project(Dependencies.Mullvad.endpointLib)) - implementation(Dependencies.AndroidX.testCore) // Fixes: https://github.com/android/android-test/issues/1589 implementation(Dependencies.AndroidX.testMonitor) diff --git a/android/test/mockapi/build.gradle.kts b/android/test/mockapi/build.gradle.kts index 274892a70372..8be38fdcbc8e 100644 --- a/android/test/mockapi/build.gradle.kts +++ b/android/test/mockapi/build.gradle.kts @@ -13,6 +13,9 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" targetProjectPath = ":app" + missingDimensionStrategy(FlavorDimensions.BILLING, Flavors.OSS) + missingDimensionStrategy(FlavorDimensions.INFRASTRUCTURE, Flavors.PROD) + testInstrumentationRunnerArguments.putAll( mapOf( "clearPackageData" to "true", @@ -20,18 +23,14 @@ android { ) } - testOptions { - execution = "ANDROIDX_TEST_ORCHESTRATOR" - } + testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/android/tile/build.gradle.kts b/android/tile/build.gradle.kts index befd86c63c6f..419ab58dc033 100644 --- a/android/tile/build.gradle.kts +++ b/android/tile/build.gradle.kts @@ -8,18 +8,14 @@ android { namespace = "net.mullvad.mullvadvpn.tile" compileSdk = Versions.Android.compileSdkVersion - defaultConfig { - minSdk = Versions.Android.minSdkVersion - } + defaultConfig { minSdk = Versions.Android.minSdkVersion } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = Versions.jvmTarget - } + kotlinOptions { jvmTarget = Versions.jvmTarget } lint { lintConfig = file("${rootProject.projectDir}/config/lint.xml") diff --git a/build-apk.sh b/build-apk.sh index 041ba75772ac..0056d208a128 100755 --- a/build-apk.sh +++ b/build-apk.sh @@ -13,10 +13,8 @@ echo "" BUILD_TYPE="release" GRADLE_BUILD_TYPE="release" -GRADLE_TASK="assembleRelease" -BUNDLE_TASK="bundleRelease" -BUILT_APK_SUFFIX="-release" -FILE_SUFFIX="" +GRADLE_TASK="createOssProdReleaseDistApk" +BUNDLE_TASK="createPlayProdReleaseDistBundle" CARGO_ARGS="--release" EXTRA_WGGO_ARGS="" BUILD_BUNDLE="no" @@ -27,16 +25,13 @@ while [ ! -z "${1:-""}" ]; do if [[ "${1:-""}" == "--dev-build" ]]; then BUILD_TYPE="debug" GRADLE_BUILD_TYPE="debug" - GRADLE_TASK="assembleDebug" - BUNDLE_TASK="bundleDebug" - BUILT_APK_SUFFIX="-debug" - FILE_SUFFIX="-debug" + GRADLE_TASK="createOssProdDebugDistApk" + BUNDLE_TASK="createOssProdDebugDistBundle" CARGO_ARGS="--features api-override" elif [[ "${1:-""}" == "--fdroid" ]]; then GRADLE_BUILD_TYPE="fdroid" - GRADLE_TASK="assembleFdroid" - BUNDLE_TASK="bundleFdroid" - BUILT_APK_SUFFIX="-fdroid-unsigned" + GRADLE_TASK="createOssProdFdroidDistApk" + BUNDLE_TASK="createOssProdFdroidDistBundle" EXTRA_WGGO_ARGS="--no-docker" elif [[ "${1:-""}" == "--app-bundle" ]]; then BUILD_BUNDLE="yes" @@ -123,15 +118,8 @@ cargo run --bin relay_list $CARGO_ARGS > build/relays.json cd "$SCRIPT_DIR/android" $GRADLE_CMD --console plain "$GRADLE_TASK" -mkdir -p "$SCRIPT_DIR/dist" -cp "$SCRIPT_DIR/android/app/build/outputs/apk/$GRADLE_BUILD_TYPE/app${BUILT_APK_SUFFIX}.apk" \ - "$SCRIPT_DIR/dist/MullvadVPN-${PRODUCT_VERSION}${FILE_SUFFIX}.apk" - if [[ "$BUILD_BUNDLE" == "yes" ]]; then $GRADLE_CMD --console plain "$BUNDLE_TASK" - - cp "$SCRIPT_DIR/android/app/build/outputs/bundle/$GRADLE_BUILD_TYPE/app${BUILT_APK_SUFFIX}.aab" \ - "$SCRIPT_DIR/dist/MullvadVPN-${PRODUCT_VERSION}${FILE_SUFFIX}.aab" fi echo "**********************************" diff --git a/ci/buildserver-build-android.sh b/ci/buildserver-build-android.sh index cb7385fa48cb..5114c0e23182 100755 --- a/ci/buildserver-build-android.sh +++ b/ci/buildserver-build-android.sh @@ -103,7 +103,13 @@ function build_ref { # Pipes all matching names and their new name to mv pushd "$artifact_dir" for original_file in MullvadVPN-*-dev-*{.apk,.aab}; do - new_file=$(echo "$original_file" | sed -nE "s/^(MullvadVPN-.*-dev-.*)(\.apk|\.aab)$/\1$version_suffix\2/p") + # Fix to make sure the sed command below result in the expected file name. + if [[ $original_file == *.play.aab ]]; then + bundle_extension="\.play\.aab" + else + bundle_extension="\.aab" + fi + new_file=$(echo "$original_file" | sed -nE "s/^(MullvadVPN-.*-dev-.*)(\.apk|$bundle_extension)$/\1$version_suffix\2/p") mv "$original_file" "$new_file" done popd