diff --git a/metadata/tech.ula.yml b/metadata/tech.ula.yml
index 0dd95d8526..64e97a6cc7 100644
--- a/metadata/tech.ula.yml
+++ b/metadata/tech.ula.yml
@@ -8,30 +8,6 @@ IssueTracker: https://github.com/CypherpunkArmory/UserLAnd/issues
Changelog: https://github.com/CypherpunkArmory/UserLAnd/releases
AutoName: UserLAnd
-Description: |-
- UserLAnd provides the easiest way to run GNU/Linux Distros on Android.
-
- Features:
- * Run full linux distros or specific applications on top of Android.
- * Install and uninstall like a regular app.
- * No root required.
-
- There are two ways to use UserLAnd
-
- Using single-click apps:
- * Click an app.
- * Fill out the required information.
- * You're good to go!
- Using user-defined custom sessions:
-
- * Define a session - This describes what filesystem you are going to use, and
- what kind of service you want to use when connecting to it (ssh or vnc).
- * Define a filesystem - This describes what distribution of Linux you want to
- install.
- * Once defined, just tap on the session to start up. This will download
- necessary assets, setup the filesystem, start the server, and connect to it.
- This will take several minutes for the first start up, but will be quicker
- afterwards.
RepoType: git
Repo: https://github.com/CypherpunkArmory/UserLAnd
@@ -232,6 +208,28 @@ Builds:
- echo "tracepotHttpsEndpoint=\"\"" >> tracepot.properties
- sed -i -e 's/versionCode vcode/versionCode $$VERCODE$$/' build.gradle
+ - versionName: 2.8.3
+ versionCode: 7438725
+ commit: v2.8.3
+ subdir: app
+ patch:
+ - disable-gms-billing.patch
+ gradle:
+ - yes
+ prebuild:
+ - echo "tracepotHttpsEndpoint=\"\"" >> tracepot.properties
+ - sed -i -e 's/versionCode vcode/versionCode $$VERCODE$$/' build.gradle
+ - sed -i -e '/com.google.firebase/d' -e '/firebaseCrashlytics/d' -e '/com.google.android.play/d'
+ -e '/com.google.gms/d' -e '/com.google.android.gms/d' -e '/billingclient/d'
+ build.gradle
+ - sed -i -e "s/buildConfigField 'boolean', 'ENABLE_PLAY_SERVICES', 'true'/buildConfigField
+ 'boolean', 'ENABLE_PLAY_SERVICES', 'false'/" build.gradle
+ buildjni:
+ - ../termux-app/terminal-emulator/src/main/jni
+ ndk: r21b
+ preassemble:
+ - androidDependencies
+
MaintainerNotes: |-
We can't enable AUM since 2.5.14, versionCode is generated at build time and depends on the actual date.
Actual versionCode is copied from the upstream release.
@@ -240,5 +238,5 @@ MaintainerNotes: |-
AutoUpdateMode: None
UpdateCheckMode: None
-CurrentVersion: 2.7.2
-CurrentVersionCode: 2927098
+CurrentVersion: 2.8.3
+CurrentVersionCode: 7438725
diff --git a/metadata/tech.ula/disable-gms-billing.patch b/metadata/tech.ula/disable-gms-billing.patch
new file mode 100644
index 0000000000..86a9e76b72
--- /dev/null
+++ b/metadata/tech.ula/disable-gms-billing.patch
@@ -0,0 +1,252 @@
+diff --git a/app/src/main/java/tech/ula/MainActivity.kt b/app/src/main/java/tech/ula/MainActivity.kt
+index ee2daa7..93209aa 100644
+--- a/app/src/main/java/tech/ula/MainActivity.kt
++++ b/app/src/main/java/tech/ula/MainActivity.kt
+@@ -83,7 +83,7 @@ class MainActivity : AppCompatActivity(), SessionListFragment.SessionSelection,
+ }
+
+ val billingManager by lazy {
+- BillingManager(this, contributionPrompter.onEntitledSubPurchases, contributionPrompter.onEntitledInAppPurchases, contributionPrompter.onPurchase, contributionPrompter.onSubscriptionSupportedChecked)
++ BillingManager(this, contributionPrompter.onSubscriptionSupportedChecked)
+ }
+
+ private val contributionPrompter by lazy {
+@@ -735,4 +735,4 @@ class MainActivity : AppCompatActivity(), SessionListFragment.SessionSelection,
+ else -> true
+ }
+ }
+-}
+\ No newline at end of file
++}
+diff --git a/app/src/main/java/tech/ula/utils/BillingManager.kt b/app/src/main/java/tech/ula/utils/BillingManager.kt
+index 5e44366..f725d9a 100644
+--- a/app/src/main/java/tech/ula/utils/BillingManager.kt
++++ b/app/src/main/java/tech/ula/utils/BillingManager.kt
+@@ -2,17 +2,6 @@ package tech.ula.utils
+
+ import android.app.Activity
+ import android.util.Log
+-import com.android.billingclient.api.AcknowledgePurchaseParams
+-import com.android.billingclient.api.BillingClient
+-import com.android.billingclient.api.BillingClient.BillingResponseCode
+-import com.android.billingclient.api.BillingClient.FeatureType
+-import com.android.billingclient.api.BillingClientStateListener
+-import com.android.billingclient.api.BillingFlowParams
+-import com.android.billingclient.api.BillingResult
+-import com.android.billingclient.api.Purchase
+-import com.android.billingclient.api.PurchasesUpdatedListener
+-import com.android.billingclient.api.SkuDetails
+-import com.android.billingclient.api.SkuDetailsParams
+ import java.util.* // ktlint-disable no-wildcard-imports
+ import kotlin.collections.HashMap
+
+@@ -25,58 +14,11 @@ import kotlin.collections.HashMap
+ */
+ class BillingManager(
+ private val activity: Activity,
+- private val onEntitledSubPurchases: (List) -> Unit,
+- private val onEntitledInAppPurchases: (List) -> Unit,
+- private val onPurchase: (Purchase) -> Unit,
+ private val onSubscriptionSupportedChecked: (Boolean) -> Unit
+ ) {
+
+- private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
+- when (billingResult.responseCode) {
+- BillingResponseCode.OK -> {
+- purchases?.let {
+- for (purchase in purchases) {
+- when (purchase.purchaseState) {
+- Purchase.PurchaseState.PURCHASED -> {
+- onPurchase(purchase)
+- if (!purchase.isAcknowledged) {
+- val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
+- .setPurchaseToken(purchase.purchaseToken)
+- .build()
+- billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
+- log("acknowledgePurchase(), billingResult=$billingResult")
+- }
+- }
+- }
+- Purchase.PurchaseState.PENDING -> {
+- // Here you can confirm to the user that they've started the pending
+- // purchase, and to complete it, they should follow instructions that
+- // are given to them. You can also choose to remind the user in the
+- // future to complete the purchase if you detect that it is still
+- // pending.
+- }
+- }
+- }
+- }
+- log("onPurchasesUpdated(), $purchases")
+- }
+- BillingResponseCode.USER_CANCELED -> log("onPurchasesUpdated() - user cancelled the purchase flow - skipping")
+- else -> log("onPurchasesUpdated() got unknown resultCode: ${billingResult.responseCode}")
+- }
+- }
+-
+- private val skuDetailsMap = HashMap()
+-
+- private val billingClient: BillingClient = BillingClient.newBuilder(activity)
+- .enablePendingPurchases()
+- .setListener(purchasesUpdatedListener)
+- .build()
+-
+ private var isBillingServiceConnected = false
+
+- val populateSkus: (List) -> Unit = {
+- it.forEach { skuDetailsMap.put(it.sku, it) }
+- }
+ private fun handlePopulateSkuError(code: Int, message: String) {
+ log("Error trying to populate skus. code: $code message: $message")
+ }
+@@ -84,102 +26,27 @@ class BillingManager(
+ init {
+ startServiceConnection {
+ onSubscriptionSupportedChecked(isSubscriptionPurchaseSupported())
+- querySubPurchases()
+- queryInAppPurchases()
+- querySubscriptionSkuDetails(listOf(Sku.US1_MONTHLY, Sku.US5_MONTHLY, Sku.US10_MONTHLY, Sku.US20_MONTHLY, Sku.US1_YEARLY, Sku.US5_YEARLY, Sku.US10_YEARLY, Sku.US20_YEARLY), populateSkus, ::handlePopulateSkuError)
+- queryInAppSkuDetails(listOf(Sku.US1_ONETIME, Sku.US5_ONETIME, Sku.US10_ONETIME, Sku.US20_ONETIME), populateSkus, ::handlePopulateSkuError)
+ }
+ }
+
+ fun querySubPurchases() {
+- if (isSubscriptionPurchaseSupported()) {
+- val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
+- if (purchasesResult.responseCode == BillingResponseCode.OK) {
+- onEntitledSubPurchases(Collections.unmodifiableList(purchasesResult.purchasesList))
+- } else {
+- log("Error trying to query purchases: $purchasesResult")
+- }
+- }
+ }
+
+ fun queryInAppPurchases() {
+- val purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
+- if (purchasesResult.responseCode == BillingResponseCode.OK) {
+- onEntitledInAppPurchases(Collections.unmodifiableList(purchasesResult.purchasesList))
+- } else {
+- log("Error trying to query purchases: $purchasesResult")
+- }
+ }
+
+ fun startPurchaseFlow(productId: String) {
+- val sku = skuDetailsMap.get(productId)
+- if (sku != null) {
+- startServiceConnection {
+- val flowParams = BillingFlowParams.newBuilder().setSkuDetails(sku).build()
+- val billingResult = billingClient.launchBillingFlow(activity, flowParams)
+- log("startPurchaseFlow(...), billingResult=$billingResult")
+- }
+- }
+ }
+
+ fun destroy() {
+ log("destroy()")
+- if (billingClient.isReady) {
+- billingClient.endConnection()
+- }
+ }
+
+ private fun startServiceConnection(task: () -> Unit) {
+- if (isBillingServiceConnected) {
+- task()
+- } else {
+- billingClient.startConnection(object : BillingClientStateListener {
+- override fun onBillingSetupFinished(billingResult: BillingResult) {
+- log("onBillingSetupFinished(...), billingResult=$billingResult")
+- if (billingResult.responseCode == BillingResponseCode.OK) {
+- isBillingServiceConnected = true
+- task()
+- }
+- }
+-
+- override fun onBillingServiceDisconnected() {
+- log("onBillingServiceDisconnected()")
+- isBillingServiceConnected = false
+- // Try to restart the connection on the next request to
+- // Google Play by calling the startConnection() method.
+- }
+- })
+- }
+- }
+-
+- private fun querySubscriptionSkuDetails(skus: List, onSuccess: (List) -> Unit, onError: (code: Int, message: String) -> Unit) {
+- val params = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.SUBS)
+- billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
+- if (billingResult.responseCode == BillingResponseCode.OK && skuDetailsList != null) {
+- onSuccess(skuDetailsList)
+- } else {
+- onError(billingResult.responseCode, billingResult.debugMessage)
+- }
+- }
+- }
+-
+- private fun queryInAppSkuDetails(skus: List, onSuccess: (List) -> Unit, onError: (code: Int, message: String) -> Unit) {
+- val params = SkuDetailsParams.newBuilder().setSkusList(skus).setType(BillingClient.SkuType.INAPP)
+- billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
+- if (billingResult.responseCode == BillingResponseCode.OK && skuDetailsList != null) {
+- onSuccess(skuDetailsList)
+- } else {
+- onError(billingResult.responseCode, billingResult.debugMessage)
+- }
+- }
+ }
+
+ private fun isSubscriptionPurchaseSupported(): Boolean {
+- val response = billingClient.isFeatureSupported(FeatureType.SUBSCRIPTIONS)
+- if (response.responseCode != BillingResponseCode.OK) {
+- log("isSubscriptionPurchaseSupported(), not supported, error response: $response")
+- }
+- return response.responseCode == BillingResponseCode.OK
++ return false
+ }
+
+ private fun log(message: String) {
+diff --git a/app/src/main/java/tech/ula/utils/UserPrompter.kt b/app/src/main/java/tech/ula/utils/UserPrompter.kt
+index b61a5a3..1e0aae4 100644
+--- a/app/src/main/java/tech/ula/utils/UserPrompter.kt
++++ b/app/src/main/java/tech/ula/utils/UserPrompter.kt
+@@ -11,7 +11,6 @@ import android.widget.SeekBar
+ import android.widget.TextView
+ import android.widget.Toast
+ import androidx.annotation.StringRes
+-import com.android.billingclient.api.Purchase
+ import tech.ula.MainActivity
+ import tech.ula.R
+
+@@ -358,34 +357,6 @@ class ContributionPrompter(private val activity: MainActivity, private val viewG
+ subscriptionSupported = it
+ }
+
+- val onEntitledSubPurchases: (List) -> Unit = {
+- processSubPurchases(it)
+- }
+-
+- val onEntitledInAppPurchases: (List) -> Unit = {
+- processInAppPurchases(it)
+- }
+-
+- val onPurchase: (Purchase) -> Unit = {
+- processPurchase(it)
+- }
+-
+- private fun processSubPurchases(purchases: List) {
+- setHasMadeSubPurchase(!purchases.isEmpty())
+- }
+-
+- private fun processInAppPurchases(purchases: List) {
+- setHasMadeInAppPurchase(!purchases.isEmpty())
+- }
+-
+- private fun processPurchase(purchase: Purchase) {
+- if (purchase.sku.endsWith("onetime"))
+- setHasMadeInAppPurchase(true)
+- else
+- setHasMadeSubPurchase(true)
+- Toast.makeText(savedActivity, "Thanks for your contribution", Toast.LENGTH_LONG).show()
+- }
+-
+ override val initialPrompt: Int
+ get() = R.string.contribution_primary
+ override val initialPosBtnText: Int