diff --git a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java index 9491204bab..6f7f63399b 100644 --- a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java +++ b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java @@ -21,6 +21,7 @@ package com.amaze.filemanager.application; import java.lang.ref.WeakReference; +import java.util.concurrent.Callable; import org.acra.ACRA; import org.acra.annotation.AcraCore; @@ -65,7 +66,7 @@ reportSenderFactoryClasses = AcraReportSenderFactory.class) public class AppConfig extends GlideApplication { - public static final String TAG = AppConfig.class.getSimpleName(); + private static final String TAG = AppConfig.class.getSimpleName(); private UtilitiesProvider utilsProvider; private RequestQueue requestQueue; @@ -175,14 +176,24 @@ public static void toast(Context context, String message) { } /** - * Run a runnable in the main application thread + * Run a {@link Runnable} in the main application thread * - * @param r Runnable to run + * @param r {@link Runnable} to run */ - public void runInApplicationThread(Runnable r) { + public void runInApplicationThread(@NonNull Runnable r) { Completable.fromRunnable(r).subscribeOn(AndroidSchedulers.mainThread()).subscribe(); } + /** + * Convenience method to run a {@link Callable} in the main application thread. Use when the + * callable's return value is not processed. + * + * @param c {@link Callable} to run + */ + public void runInApplicationThread(@NonNull Callable c) { + Completable.fromCallable(c).subscribeOn(AndroidSchedulers.mainThread()).subscribe(); + } + public static synchronized AppConfig getInstance() { return instance; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98e1a29b4d..0d2c281a1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -728,5 +728,6 @@ You only need to do this once, until the next time you select a new location for Select by type Select by date Select similar + Error fetching product list from Google Play. diff --git a/app/src/play/java/com/amaze/filemanager/utils/Billing.java b/app/src/play/java/com/amaze/filemanager/utils/Billing.java index d7e910a026..bf15f8702c 100644 --- a/app/src/play/java/com/amaze/filemanager/utils/Billing.java +++ b/app/src/play/java/com/amaze/filemanager/utils/Billing.java @@ -24,8 +24,11 @@ import java.util.List; import com.afollestad.materialdialogs.MaterialDialog; +import com.amaze.filemanager.BuildConfig; import com.amaze.filemanager.R; import com.amaze.filemanager.adapters.holders.DonationViewHolder; +import com.amaze.filemanager.application.AppConfig; +import com.amaze.filemanager.databinding.AdapterDonationBinding; import com.amaze.filemanager.ui.activities.superclasses.BasicActivity; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClientStateListener; @@ -38,7 +41,6 @@ import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; -import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -52,9 +54,10 @@ public class Billing extends RecyclerView.Adapter implements PurchasesUpdatedListener { + private static final String TAG = Billing.class.getSimpleName(); + private BasicActivity activity; private List skuList; - private LayoutInflater layoutInflater; private List skuDetails; // create new donations client @@ -62,7 +65,7 @@ public class Billing extends RecyclerView.Adapter /** True if billing service is connected now. */ private boolean isServiceConnected; - public Billing(BasicActivity activity) { + public Billing(@NonNull BasicActivity activity) { this.activity = activity; skuList = new ArrayList<>(); @@ -71,8 +74,6 @@ public Billing(BasicActivity activity) { skuList.add("donations_3"); skuList.add("donations_4"); - layoutInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - billingClient = BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases().build(); initiatePurchaseFlow(); @@ -103,9 +104,18 @@ private void initiatePurchaseFlow() { billingClient.querySkuDetailsAsync( params.build(), (responseCode, skuDetailsList) -> { - // Successfully fetched product details - skuDetails = skuDetailsList; - popProductsList(responseCode, skuDetailsList); + if (skuDetailsList != null && skuDetailsList.size() > 0) { + // Successfully fetched product details + skuDetails = skuDetailsList; + popProductsList(responseCode, skuDetailsList); + } else { + AppConfig.toast(activity, R.string.error_fetching_google_play_product_list); + if (BuildConfig.DEBUG) { + Log.w( + TAG, + "Error fetching product list - looks like you are running a DEBUG build."); + } + } }); }; @@ -128,13 +138,14 @@ private void popProductsList(BillingResult response, List skuDetails @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View rootView = layoutInflater.inflate(R.layout.adapter_donation, parent, false); + View rootView = + AdapterDonationBinding.inflate(LayoutInflater.from(this.activity), parent, false).getRoot(); return new DonationViewHolder(rootView); } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { - if (holder instanceof DonationViewHolder) { + if (holder instanceof DonationViewHolder && skuDetails.size() > 0) { String titleRaw = skuDetails.get(position).getTitle(); ((DonationViewHolder) holder) .TITLE.setText(titleRaw.subSequence(0, titleRaw.lastIndexOf("("))); @@ -199,10 +210,7 @@ private void startServiceConnection(final Runnable executeOnSuccess) { new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResponse) { - Log.d( - Billing.this.getClass().getSimpleName(), - "Setup finished. Response code: " + billingResponse); - + Log.d(TAG, "Setup finished. Response code: " + billingResponse.getResponseCode()); if (billingResponse.getResponseCode() == BillingClient.BillingResponseCode.OK) { isServiceConnected = true; if (executeOnSuccess != null) { @@ -226,11 +234,22 @@ public void destroyBillingInstance() { } private void showPaymentsDialog(final BasicActivity context) { - final MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.donate); - builder.adapter(this, null); - builder.theme(context.getAppTheme().getMaterialDialogTheme()); - builder.cancelListener(dialog -> purchaseProduct.purchaseCancel()); - builder.show(); + /* + * As of Billing library 4.0, all callbacks are running on background thread. + * Need to use AppConfig.runInApplicationThread() for UI interactions + * + * + */ + AppConfig.getInstance() + .runInApplicationThread( + () -> { + final MaterialDialog.Builder builder = new MaterialDialog.Builder(context); + builder.title(R.string.donate); + builder.adapter(this, null); + builder.theme(context.getAppTheme().getMaterialDialogTheme()); + builder.cancelListener(dialog -> purchaseProduct.purchaseCancel()); + builder.show(); + return null; + }); } } diff --git a/build.gradle b/build.gradle index e1b1ff85fd..38eb493f01 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { slf4jVersion = "1.7.25" mockitoVersion = "3.9.0" mockitoKotlinVersion = "3.2.0" - androidBillingVersion = "2.1.0" + androidBillingVersion = "4.0.0" junrarVersion = "7.4.0" zip4jVersion = "2.6.4" espressoVersion = "3.3.0"