pull/72/head
M66B 6 years ago
parent 597c789ee5
commit 1cc45d6b44

@ -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
<a name="FAQ2"></a>
**(2) Why is there a permanent notification shown?**

@ -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"

@ -27,6 +27,9 @@
#AndroidX
-keep class androidx.appcompat.app.AppCompatViewInflater { <init>(...); }
#IAB
-keep class com.android.vending.billing.**
#JavaMail
-dontshrink
-keep class javax.** {*;}

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:name=".ApplicationEx"

@ -29,6 +29,7 @@ import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.provider.Settings;
@ -44,6 +45,11 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.google.android.material.snackbar.Snackbar;
import java.io.BufferedReader;
@ -72,11 +78,12 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class ActivityView extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
public class ActivityView extends ActivityBase implements FragmentManager.OnBackStackChangedListener, PurchasesUpdatedListener {
private View view;
private DrawerLayout drawerLayout;
private ListView drawerList;
private ActionBarDrawerToggle drawerToggle;
private BillingClient billingClient;
private boolean newIntent = false;
@ -307,6 +314,9 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
}
}.load(this, new Bundle());
billingClient = BillingClient.newBuilder(this).setListener(this).build();
billingClient.startConnection(billingClientStateListener);
checkIntent(getIntent());
}
@ -349,6 +359,9 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
newIntent = false;
getSupportFragmentManager().popBackStack("unified", 0);
}
if (billingClient.isReady())
queryPurchases();
}
@Override
@ -366,6 +379,7 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
@Override
protected void onDestroy() {
billingClient.endConnection();
super.onDestroy();
}
@ -518,8 +532,22 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
}
private void onMenuPro() {
if (Helper.isPlayStoreInstall(this)) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getBoolean("pro", false)) {
Snackbar.make(view, R.string.title_pro_activated, Snackbar.LENGTH_LONG).show();
return;
}
if (Helper.isPlayStoreInstall(this)) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku(BuildConfig.APPLICATION_ID + ".pro")
.setType(BillingClient.SkuType.INAPP)
.build();
int responseCode = billingClient.launchBillingFlow(ActivityView.this, flowParams);
String text = Helper.getBillingResponseText(responseCode);
Log.i(Helper.TAG, "IAB launch billing flow response=" + text);
if (responseCode != BillingClient.BillingResponse.OK)
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
} else
startActivity(getIntentPro());
}
@ -755,4 +783,68 @@ public class ActivityView extends ActivityBase implements FragmentManager.OnBack
}.load(this, args);
}
}
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
private int backoff = 4; // seconds
@Override
public void onBillingSetupFinished(@BillingClient.BillingResponse int responseCode) {
String text = Helper.getBillingResponseText(responseCode);
Log.i(Helper.TAG, "IAB connected response=" + text);
if (responseCode == BillingClient.BillingResponse.OK) {
backoff = 4;
queryPurchases();
} else
Snackbar.make(view, text, Snackbar.LENGTH_LONG).show();
}
@Override
public void onBillingServiceDisconnected() {
backoff *= 2;
Log.i(Helper.TAG, "IAB disconnected retry in " + backoff + " s");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (!billingClient.isReady())
billingClient.startConnection(billingClientStateListener);
}
}, backoff * 1000L);
}
};
@Override
public void onPurchasesUpdated(int responseCode, @android.support.annotation.Nullable List<Purchase> 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<Purchase> 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();
}
}
}

@ -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);
}
}
}

@ -176,6 +176,7 @@
<string name="title_legend_connected">Connected</string>
<string name="title_legend_closing">Closing</string>
<string name="title_pro_activated">All pro features are activated</string>
<string name="title_pro_valid">All pro features activated</string>
<string name="title_pro_invalid">Invalid response</string>

Loading…
Cancel
Save