diff --git a/FAQ.md b/FAQ.md
index bc2ddee89d..be7896205b 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -11,7 +11,8 @@ Frequently Asked Questions
* View network connections (ACCESS_NETWORK_STATE): to monitor internet connectivity changes
* Run at startup (RECEIVE_BOOT_COMPLETED): to start monitoring on device start
* Optional: read your contacts (READ_CONTACTS): to autocomplete addresses
-* ... (FOREGROUND_SERVICE): to run a foreground service on Android 9 Pie and later, see also the next question.
+* ... (BILLING): to offer in-app purchases
+* ... (FOREGROUND_SERVICE): to run a foreground service on Android 9 Pie and later, see also the next question
**(2) Why is there a permanent notification shown?**
diff --git a/app/build.gradle b/app/build.gradle
index 79deb3ba76..2551904e65 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -55,6 +55,8 @@ dependencies {
// https://mvnrepository.com/artifact/androidx.room/room-runtime
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
+ // https://developer.android.com/google/play/billing/billing_library_releases_notes
+
// https://javaee.github.io/javamail/
// https://jsoup.org/
// http://www.freeutils.net/source/jcharset/
@@ -70,6 +72,7 @@ dependencies {
def lifecycle_version = "2.0.0-rc01"
def room_version = "2.0.0-rc01"
def paging_version = "2.0.0-rc01"
+ def billingclient_version = "1.1"
def javamail_version = "1.6.0"
def jsoup_version = "1.11.3"
def jcharset_version = "2.0"
@@ -88,6 +91,8 @@ dependencies {
implementation "androidx.paging:paging-runtime:$paging_version"
+ implementation "com.android.billingclient:billing:$billingclient_version"
+
implementation "com.sun.mail:android-mail:$javamail_version"
implementation "com.sun.mail:android-activation:$javamail_version"
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 251617c02d..0752a1ecf3 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -27,6 +27,9 @@
#AndroidX
-keep class androidx.appcompat.app.AppCompatViewInflater { (...); }
+#IAB
+-keep class com.android.vending.billing.**
+
#JavaMail
-dontshrink
-keep class javax.** {*;}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fdac5dbf1e..028edcc5ce 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
+
purchases) {
+ String text = Helper.getBillingResponseText(responseCode);
+ Log.i(Helper.TAG, "IAB purchases updated response=" + text);
+ if (responseCode == BillingClient.BillingResponse.OK)
+ checkPurchases(purchases);
+ else
+ Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
+ }
+
+ private void queryPurchases() {
+ Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
+ String text = Helper.getBillingResponseText(result.getResponseCode());
+ Log.i(Helper.TAG, "IAB query purchases response=" + text);
+ if (result.getResponseCode() == BillingClient.BillingResponse.OK)
+ checkPurchases(result.getPurchasesList());
+ else
+ Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
+ }
+
+ private void checkPurchases(List purchases) {
+ if (purchases != null) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove("pro");
+ for (Purchase purchase : purchases) {
+ Log.i(Helper.TAG, "IAB SKU=" + purchase.getSku());
+ if ((BuildConfig.APPLICATION_ID + ".pro").equals(purchase.getSku())) {
+ editor.putBoolean("pro", true);
+ Log.i(Helper.TAG, "IAB pro features activated");
+ }
+ }
+ editor.apply();
+ }
+ }
}
diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java
index eb12f8d16d..0e106335f7 100644
--- a/app/src/main/java/eu/faircode/email/Helper.java
+++ b/app/src/main/java/eu/faircode/email/Helper.java
@@ -31,6 +31,7 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
+import com.android.billingclient.api.BillingClient;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.io.BufferedReader;
@@ -188,4 +189,55 @@ public class Helper {
sb.append(String.format("%02x", b));
return sb.toString();
}
+
+ static String getBillingResponseText(@BillingClient.BillingResponse int responseCode) {
+ switch (responseCode) {
+ case BillingClient.BillingResponse.BILLING_UNAVAILABLE:
+ // Billing API version is not supported for the type requested
+ return "BILLING_UNAVAILABLE";
+
+ case BillingClient.BillingResponse.DEVELOPER_ERROR:
+ // Invalid arguments provided to the API.
+ return "DEVELOPER_ERROR";
+
+ case BillingClient.BillingResponse.ERROR:
+ // Fatal error during the API action
+ return "ERROR";
+
+ case BillingClient.BillingResponse.FEATURE_NOT_SUPPORTED:
+ // Requested feature is not supported by Play Store on the current device.
+ return "FEATURE_NOT_SUPPORTED";
+
+ case BillingClient.BillingResponse.ITEM_ALREADY_OWNED:
+ // Failure to purchase since item is already owned
+ return "ITEM_ALREADY_OWNED";
+
+ case BillingClient.BillingResponse.ITEM_NOT_OWNED:
+ // Failure to consume since item is not owned
+ return "ITEM_NOT_OWNED";
+
+ case BillingClient.BillingResponse.ITEM_UNAVAILABLE:
+ // Requested product is not available for purchase
+ return "ITEM_UNAVAILABLE";
+
+ case BillingClient.BillingResponse.OK:
+ // Success
+ return "OK";
+
+ case BillingClient.BillingResponse.SERVICE_DISCONNECTED:
+ // Play Store service is not connected now - potentially transient state.
+ return "SERVICE_DISCONNECTED";
+
+ case BillingClient.BillingResponse.SERVICE_UNAVAILABLE:
+ // Network connection is down
+ return "SERVICE_UNAVAILABLE";
+
+ case BillingClient.BillingResponse.USER_CANCELED:
+ // User pressed back or canceled a dialog
+ return "USER_CANCELED";
+
+ default:
+ return Integer.toString(responseCode);
+ }
+ }
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4ea3d588e0..780adf4b89 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -176,6 +176,7 @@
Connected
Closing
+ All pro features are activated
All pro features activated
Invalid response