public class CustomIABHelper implements IabHelper.QueryInventoryFinishedListener {

    private IabHelper billingHelper;

    private static CustomIABHelper instance = null;

    private boolean isSetupDone = false;
    private boolean isPro       = false;

    private CustomIABHelper(Context context, IabHelper.QueryInventoryFinishedListener listener) {
        billingHelper = new IabHelper(context.getApplicationContext(), "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnl+N1zhaAmBhLF54yJo7T+ku8m5c/XZEsZ3+zBb7HyKCxpGuQL4+oPTVSNRtNdXNCl2R0eqojv+f3sT6tWL2XHt1DK6F7rYVtvs8rmQR1himMDEV0Tgljn5uBQuMag9nvNeyGIAi1wQeRz9Hk8tGF+laZcD3phR6NFnrwebD8FkG0lc6XHKpjAzCsdu+YbyPH7JuFAUoi+Yrbvs50r0JzeUWqz+v427V4W4kQR9zAmN3evVmou1R+2WXPtD1wGMMSQAs2DOuxfgh85o4sRS21an8xQk/d70EX3E78O3iXzl76CKZNM54O2g8iE3tkNgGfeCkqs0m2v/2E6VDubVf3QIDAQAB");

        try {
            if (!isSetupDone) {
                billingHelper.startSetup(result -> {
                    isSetupDone = true;
                    isUserPro(listener);
                });
            }
        } catch (IllegalStateException e) {
            Log.d("onResume: setup already done");
        }

    }

    public static CustomIABHelper getInstance(Context context, IabHelper.QueryInventoryFinishedListener listener) {
        if (instance == null) {
            instance = new CustomIABHelper(context, listener);
        } else {
            instance.isUserPro(listener);
        }
        return CustomIABHelper.instance;
    }

    public void isUserPro(IabHelper.QueryInventoryFinishedListener listener) {
        if (billingHelper.isSetupDone() && !billingHelper.isAsyncInProgress()) {
            try {
                billingHelper.queryInventoryAsync(false, listener);
            } catch (Exception e) {
                Log.e(e.getMessage());
            }
        }
    }

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        isPro = (inv.hasPurchase(SKU_UPGRADE) &&
                inv.getPurchase(SKU_UPGRADE).getPurchaseState() == Purchase.PurchaseState.PURCHASED);
    }

    public static boolean isOrderOk(IabResult result, Inventory inv) {
        return result.getResponse() == IabHelper.BILLING_RESPONSE_RESULT_OK &&
                inv.hasPurchase(SKU_UPGRADE) &&
                (inv.getPurchase(SKU_UPGRADE).getPurchaseState() == Purchase.PurchaseState.PURCHASED);
    }
} public class IabHelper {

    // Is debug logging enabled?
    boolean mDebugLog = true;
    String  mDebugTag = "SIAPv3";

    // Is setup done?
    boolean mSetupDone = false;

    // Is an asynchronous operation in progress?
    // (only one at a time can be in progress)
    boolean mAsyncInProgress = false;

    // (for logging/debugging)
    // if mAsyncInProgress == true, what asynchronous operation is in progress?
    String mAsyncOperation = "";

    // Context we were passed during initialization
    Context mContext;

    // Connection to the service
    IInAppBillingService mService;
    ServiceConnection    mServiceConn;

    // The request code used to launch purchase flow
    int mRequestCode;

    // Public key for verifying signature, in base64 encoding
    String mSignatureBase64 = null;

    // Billing response codes
    public static final int BILLING_RESPONSE_RESULT_OK                  = 0;
    public static final int BILLING_RESPONSE_RESULT_USER_CANCELED       = 1;
    public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
    public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE    = 4;
    public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR     = 5;
    public static final int BILLING_RESPONSE_RESULT_ERROR               = 6;
    public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED  = 7;
    public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED      = 8;

    // IAB Helper error codes
    public static final int IABHELPER_ERROR_BASE                = -1000;
    public static final int IABHELPER_REMOTE_EXCEPTION          = -1001;
    public static final int IABHELPER_BAD_RESPONSE              = -1002;
    public static final int IABHELPER_VERIFICATION_FAILED       = -1003;
    public static final int IABHELPER_SEND_INTENT_FAILED        = -1004;
    public static final int IABHELPER_USER_CANCELLED            = -1005;
    public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
    public static final int IABHELPER_MISSING_TOKEN             = -1007;
    public static final int IABHELPER_UNKNOWN_ERROR             = -1008;

    // Keys for the responses from InAppBillingService
    public static final String RESPONSE_CODE                     = "RESPONSE_CODE";
    public static final String RESPONSE_GET_SKU_DETAILS_LIST     = "DETAILS_LIST";
    public static final String RESPONSE_BUY_INTENT               = "BUY_INTENT";
    public static final String RESPONSE_INAPP_PURCHASE_DATA      = "INAPP_PURCHASE_DATA";
    public static final String RESPONSE_INAPP_SIGNATURE          = "INAPP_DATA_SIGNATURE";
    public static final String RESPONSE_INAPP_ITEM_LIST          = "INAPP_PURCHASE_ITEM_LIST";
    public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
    public static final String RESPONSE_INAPP_SIGNATURE_LIST     = "INAPP_DATA_SIGNATURE_LIST";
    public static final String INAPP_CONTINUATION_TOKEN          = "INAPP_CONTINUATION_TOKEN";

    // Item type: in-app item
    public static final String ITEM_TYPE_INAPP = "inapp";

    // some fields on the getSkuDetails response bundle
    public static final String GET_SKU_DETAILS_ITEM_LIST      = "ITEM_ID_LIST";
    public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";

    /**
     * Creates an instance. After creation, it will not yet be ready to use. You must perform
     * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does
     * not
     * block and is safe to call from a UI thread.
     *
     * @param ctx Your application or Activity context. Needed to bind to the in-app billing
     * service.
     * @param base64PublicKey Your application's public key, encoded in base64.
     * This is used for verification of purchase signatures. You can find your app's base64-encoded
     * public key in your application's page on Google Play Developer Console. Note that this
     * is NOT your "developer public key".
     */
    public IabHelper(Context ctx, String base64PublicKey) {
        mContext = ctx.getApplicationContext();
        mSignatureBase64 = base64PublicKey;
        logDebug("IAB helper created.");
    }

    /**
     * Enables or disable debug logging through LogCat.
     */
    public void enableDebugLogging(boolean enable, String tag) {
        mDebugLog = enable;
        mDebugTag = tag;
    }

    public void enableDebugLogging(boolean enable) {
        mDebugLog = enable;
    }

    /**
     * Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
     * when the setup process is complete.
     */
    public interface OnIabSetupFinishedListener {

        /**
         * Called to notify that setup is complete.
         *
         * @param result The result of the setup process.
         */
        void onIabSetupFinished(IabResult result);
    }

    /**
     * Starts the setup process. This will start up the setup process asynchronously.
     * You will be notified through the listener when the setup process is complete.
     * This method is safe to call from a UI thread.
     *
     * THIS WILL NOT WORK ON an EMULATOR, YOU NEED A REAL DEVICE
     *
     * @param listener The listener to notify when the setup process is complete.
     */
    public void startSetup(final OnIabSetupFinishedListener listener) {
        // If already set up, can't do it again.
        if (mSetupDone)
            throw new IllegalStateException("IAB helper is already set up.");

        // Connection to IAB service
        logDebug("Starting in-app billing setup.");
        mServiceConn = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
                logDebug("Billing service disconnected.");
                mService = null;
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                logDebug("Billing service connected.");
                mService = IInAppBillingService.Stub.asInterface(service);
                String packageName = mContext.getPackageName();
                try {
                    logDebug("Checking for in-app billing 3 support.");
                    int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
                    if (response != BILLING_RESPONSE_RESULT_OK) {
                        if (listener != null) {
                            listener.onIabSetupFinished(new IabResult(response,
                                    "Error checking for billing v3 support."));
                        }
                        return;
                    }
                    logDebug("In-app billing version 3 supported for " + packageName);
                    mSetupDone = true;
                } catch (RemoteException e) {
                    if (listener != null) {
                        listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
                                "RemoteException while setting up in-app billing."));
                    }
                    e.printStackTrace();
                }

                if (listener != null) {
                    listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
                }
            }
        };

        boolean attempt = mContext.getApplicationContext().bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND").setPackage("com.android.vending"),
                mServiceConn, Context.BIND_AUTO_CREATE);

        if (!attempt) {
            Log.e(mDebugTag, "Failed to bind to Service with name com.android.vending.billing.InAppBillingService.BIND");
        }
    }

    /**
     * Dispose of object, releasing resources. It's very important to call this
     * method when you are done with this object. It will release any resources
     * used by it such as service connections. Naturally, once the object is
     * disposed of, it can't be used again.
     */
    public void dispose() {
        logDebug("Disposing.");
        mSetupDone = false;
        if (mServiceConn != null) {
            logDebug("Unbinding from service.");
            if (mContext != null) {
                mContext.unbindService(mServiceConn);
            }
            mServiceConn = null;
            mService = null;
            mPurchaseListener = null;
        }
    }

    /**
     * Callback that notifies when a purchase is finished.
     */
    public interface OnIabPurchaseFinishedListener {

        /**
         * Called to notify that an in-app purchase finished. If the purchase was successful,
         * then the sku parameter specifies which item was purchased. If the purchase failed,
         * the sku and extraData parameters may or may not be null, depending on how far the
         * purchase
         * process went.
         *
         * @param result The result of the purchase.
         * @param info The purchase information (null if purchase failed)
         */
        void onIabPurchaseFinished(IabResult result, Purchase info);
    }

    // The listener registered on launchPurchaseFlow, which we have to call back when
    // the purchase finishes
    OnIabPurchaseFinishedListener mPurchaseListener;

    /**
     * Same as calling {@link #launchPurchaseFlow(Activity, String, int,
     * OnIabPurchaseFinishedListener, String)} with null as
     * extraData.
     */
    public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
        launchPurchaseFlow(act, sku, requestCode, listener, "");
    }

    /**
     * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app
     * purchase,
     * which will involve bringing up the Google Play screen. The calling activity will be paused
     * while
     * the user interacts with Google Play, and the result will be delivered via the activity's
     * {@link Activity#onActivityResult} method, at which point you must call
     * this object's {@link #handleActivityResult} method to continue the purchase flow. This
     * method
     * MUST be called from the UI thread of the Activity.
     *
     * @param act The calling activity.
     * @param sku The sku of the item to purchase.
     * @param requestCode A request code (to differentiate from other responses --
     * as in {@link Activity#startActivityForResult}).
     * @param listener The listener to notify when the purchase process finishes
     * @param extraData Extra data (developer payload), which will be returned with the purchase
     * data
     * when the purchase completes. This extra data will be permanently bound to that purchase
     * and will always be returned when the purchase is queried.
     */
    public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) {
        checkSetupDone("launchPurchaseFlow");
        flagStartAsync("launchPurchaseFlow");
        IabResult result;

        try {
            //            String purchaseToken = "inapp:" + act.getPackageName() + ":android.test.purchased";
            //            mService.consumePurchase(3, act.getPackageName(), purchaseToken);

            logDebug("Constructing buy intent for " + sku);
            Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, ITEM_TYPE_INAPP, extraData);
            int    response        = getResponseCodeFromBundle(buyIntentBundle);
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logError("Unable to buy item, Error response: " + getResponseDesc(response));

                result = new IabResult(response, "Unable to buy item");
                if (listener != null) {
                    listener.onIabPurchaseFinished(result, null);
                }
                return;
            }

            PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
            logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
            mRequestCode = requestCode;
            mPurchaseListener = listener;
            act.startIntentSenderForResult(pendingIntent.getIntentSender(),
                    requestCode, new Intent(),
                    Integer.valueOf(0), Integer.valueOf(0),
                    Integer.valueOf(0));
        } catch (SendIntentException e) {
            logError("SendIntentException while launching purchase flow for sku " + sku);
            e.printStackTrace();

            result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
            if (listener != null) {
                listener.onIabPurchaseFinished(result, null);
            }
        } catch (RemoteException e) {
            logError("RemoteException while launching purchase flow for sku " + sku);
            e.printStackTrace();

            result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
            if (listener != null) {
                listener.onIabPurchaseFinished(result, null);
            }
        }
    }

    /**
     * Handles an activity result that's part of the purchase flow in in-app billing. If you
     * are calling {@link #launchPurchaseFlow}, then you must call this method from your
     * Activity's {@link Activity@onActivityResult} method. This method
     * MUST be called from the UI thread of the Activity.
     *
     * @param requestCode The requestCode as you received it.
     * @param resultCode The resultCode as you received it.
     * @param data The data (Intent) as you received it.
     * @return Returns true if the result was related to a purchase flow and was handled;
     * false if the result was not related to a purchase, in which case you should
     * handle it normally.
     */
    public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
        IabResult result;
        if (requestCode != mRequestCode)
            return false;

        checkSetupDone("handleActivityResult");

        // end of async purchase operation
        flagEndAsync();

        if (data == null) {
            logError("Null data in IAB activity result.");
            result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
            if (mPurchaseListener != null) {
                mPurchaseListener.onIabPurchaseFinished(result, null);
            }
            return true;
        }

        int    responseCode  = getResponseCodeFromIntent(data);
        String purchaseData  = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
        String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

        if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
            logDebug("Successful resultcode from purchase activity.");
            logDebug("Purchase data: " + purchaseData);
            logDebug("Data signature: " + dataSignature);
            logDebug("Extras: " + data.getExtras());

            if (purchaseData == null || dataSignature == null) {
                logError("BUG: either purchaseData or dataSignature is null.");
                logDebug("Extras: " + data.getExtras().toString());
                result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
                if (mPurchaseListener != null) {
                    mPurchaseListener.onIabPurchaseFinished(result, null);
                }
                return true;
            }

            Purchase purchase = null;
            try {
                purchase = new Purchase(purchaseData, dataSignature);
                String sku = purchase.getSku();

                // Verify signature
                if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
                    logError("Purchase signature verification FAILED for sku " + sku);
                    result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
                    if (mPurchaseListener != null) {
                        mPurchaseListener.onIabPurchaseFinished(result, purchase);
                    }
                    return true;
                }
                logDebug("Purchase signature successfully verified.");
            } catch (JSONException e) {
                logError("Failed to parse purchase data.");
                e.printStackTrace();
                result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
                if (mPurchaseListener != null) {
                    mPurchaseListener.onIabPurchaseFinished(result, null);
                }
                return true;
            }

            if (mPurchaseListener != null) {
                mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
            }
        } else if (resultCode == Activity.RESULT_OK) {
            // result code was OK, but in-app billing response was not OK.
            logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
            if (mPurchaseListener != null) {
                result = new IabResult(responseCode, "Problem purchashing item.");
                mPurchaseListener.onIabPurchaseFinished(result, null);
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
            result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
            if (mPurchaseListener != null) {
                mPurchaseListener.onIabPurchaseFinished(result, null);
            }
        } else {
            logError("Purchase failed. Result code: " + Integer.toString(resultCode)
                    + ". Response: " + getResponseDesc(responseCode));
            result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
            if (mPurchaseListener != null) {
                mPurchaseListener.onIabPurchaseFinished(result, null);
            }
        }
        return true;
    }

    /**
     * Queries the inventory. This will query all owned items from the server, as well as
     * information on additional skus, if specified. This method may block or take long to execute.
     * Do not call from a UI thread. For that, use the non-blocking version
     * refreshInventoryAsync.
     *
     * @param querySkuDetails if true, SKU details (price, description, etc) will be queried as
     * well
     * as purchase information.
     * @param moreSkus additional skus to query information on, regardless of ownership. Ignored
     * if null or if querySkuDetails is false.
     * @throws IabException if a problem occurs while refreshing the inventory.
     */
    public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
        checkSetupDone("queryInventory");
        try {
            Inventory inv = new Inventory();
            int       r   = queryPurchases(inv);
            if (r != BILLING_RESPONSE_RESULT_OK)
                throw new IabException(r, "Error refreshing inventory (querying owned items).");

            if (querySkuDetails) {
                r = querySkuDetails(inv, moreSkus);
                if (r != BILLING_RESPONSE_RESULT_OK)
                    throw new IabException(r, "Error refreshing inventory (querying prices of items).");
            }
            return inv;
        } catch (RemoteException e) {
            throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
        } catch (JSONException e) {
            throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
        }
    }

    /**
     * Listener that notifies when an inventory query operation completes.
     */
    public interface QueryInventoryFinishedListener {

        /**
         * Called to notify that an inventory query operation completed.
         *
         * @param result The result of the operation.
         * @param inv The inventory.
         */
        void onQueryInventoryFinished(IabResult result, Inventory inv);
    }

    /**
     * Asynchronous wrapper for inventory query. This will perform an inventory
     * query as described in {@link #queryInventory}, but will do so asynchronously
     * and call back the specified listener upon completion. This method is safe to
     * call from a UI thread.
     *
     * @param querySkuDetails as in {@link #queryInventory}
     * @param moreSkus as in {@link #queryInventory}
     * @param listener The listener to notify when the refresh operation completes.
     */
    public void queryInventoryAsync(final boolean querySkuDetails,
                                    final List<String> moreSkus,
                                    final QueryInventoryFinishedListener listener) {
        final Handler handler = new Handler();
        checkSetupDone("queryInventory");
        flagStartAsync("refresh inventory");
        new Thread(new Runnable() {

            @Override
            public void run() {
                IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
                Inventory inv    = null;
                try {
                    inv = queryInventory(querySkuDetails, moreSkus);
                } catch (IabException ex) {
                    result = ex.getResult();
                }

                flagEndAsync();

                final IabResult result_f = result;
                final Inventory inv_f    = inv;
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        listener.onQueryInventoryFinished(result_f, inv_f);
                    }
                });
            }
        }).start();
    }

    public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
        queryInventoryAsync(true, null, listener);
    }

    public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
        queryInventoryAsync(querySkuDetails, null, listener);
    }

    /**
     * Consumes a given in-app product. Consuming can only be done on an item
     * that's owned, and as a result of consumption, the user will no longer own it.
     * This method may block or take long to return. Do not call from the UI thread.
     * For that, see {@link #consumeAsync}.
     *
     * @param itemInfo The PurchaseInfo that represents the item to consume.
     * @throws IabException if there is a problem during consumption.
     */
    void consume(Purchase itemInfo) throws IabException {
        checkSetupDone("consume");
        try {
            String token = itemInfo.getToken();
            String sku   = itemInfo.getSku();
            if (token == null || token.equals("")) {
                logError("Can't consume " + sku + ". No token.");
                throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
                        + sku + " " + itemInfo);
            }

            logDebug("Consuming sku: " + sku + ", token: " + token);
            int response = mService.consumePurchase(3, mContext.getPackageName(), token);
            if (response == BILLING_RESPONSE_RESULT_OK) {
                logDebug("Successfully consumed sku: " + sku);
            } else {
                logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
                throw new IabException(response, "Error consuming sku " + sku);
            }
        } catch (RemoteException e) {
            throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
        } catch (NullPointerException e) {
            Log.d("consume: ", e.getMessage());
        }
    }

    /**
     * Callback that notifies when a consumption operation finishes.
     */
    public interface OnConsumeFinishedListener {

        /**
         * Called to notify that a consumption has finished.
         *
         * @param purchase The purchase that was (or was to be) consumed.
         * @param result The result of the consumption operation.
         */
        void onConsumeFinished(Purchase purchase, IabResult result);
    }

    /**
     * Callback that notifies when a multi-item consumption operation finishes.
     */
    public interface OnConsumeMultiFinishedListener {

        /**
         * Called to notify that a consumption of multiple items has finished.
         *
         * @param purchases The purchases that were (or were to be) consumed.
         * @param results The results of each consumption operation, corresponding to each
         * sku.
         */
        void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
    }

    /**
     * Asynchronous wrapper to item consumption. Works like {@link #consume}, but
     * performs the consumption in the background and notifies completion through
     * the provided listener. This method is safe to call from a UI thread.
     *
     * @param purchase The purchase to be consumed.
     * @param listener The listener to notify when the consumption operation finishes.
     */
    public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
        checkSetupDone("consume");
        List<Purchase> purchases = new ArrayList<Purchase>();
        purchases.add(purchase);
        consumeAsyncInternal(purchases, listener, null);
    }

    /**
     * Same as consumeAsync, but for multiple items at once.
     *
     * @param purchases The list of PurchaseInfo objects representing the purchases to consume.
     * @param listener The listener to notify when the consumption operation finishes.
     */
    public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
        checkSetupDone("consume");
        consumeAsyncInternal(purchases, null, listener);
    }

    /**
     * Returns a human-readable description for the given response code.
     *
     * @param code The response code
     * @return A human-readable string explaining the result code.
     * It also includes the result code numerically.
     */
    public static String getResponseDesc(int code) {
        String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
                "3:Billing Unavailable/4:Item unavailable/" +
                "5:Developer Error/6:Error/7:Item Already Owned/" +
                "8:Item not owned").split("/");
        String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
                "-1002:Bad response received/" +
                "-1003:Purchase signature verification failed/" +
                "-1004:Send intent failed/" +
                "-1005:User cancelled/" +
                "-1006:Unknown purchase response/" +
                "-1007:Missing token/" +
                "-1008:Unknown error").split("/");

        if (code <= IABHELPER_ERROR_BASE) {
            int index = IABHELPER_ERROR_BASE - code;
            if (index >= 0 && index < iabhelper_msgs.length)
                return iabhelper_msgs[index];
            else
                return String.valueOf(code) + ":Unknown IAB Helper Error";
        } else if (code < 0 || code >= iab_msgs.length)
            return String.valueOf(code) + ":Unknown";
        else
            return iab_msgs[code];
    }

    // Checks that setup was done; if not, throws an exception.
    void checkSetupDone(String operation) {
        if (!mSetupDone) {
            logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
            throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
        }
    }

    // Workaround to bug where sometimes response codes come as Long instead of Integer
    int getResponseCodeFromBundle(Bundle b) {
        Object o = b.get(RESPONSE_CODE);
        if (o == null) {
            logDebug("Bundle with null response code, assuming OK (known issue)");
            return BILLING_RESPONSE_RESULT_OK;
        } else if (o instanceof Integer)
            return ((Integer) o).intValue();
        else if (o instanceof Long)
            return (int) ((Long) o).longValue();
        else {
            logError("Unexpected type for bundle response code.");
            logError(o.getClass().getName());
            throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
        }
    }

    // Workaround to bug where sometimes response codes come as Long instead of Integer
    int getResponseCodeFromIntent(Intent i) {
        Object o = i.getExtras().get(RESPONSE_CODE);
        if (o == null) {
            logError("Intent with no response code, assuming OK (known issue)");
            return BILLING_RESPONSE_RESULT_OK;
        } else if (o instanceof Integer)
            return ((Integer) o).intValue();
        else if (o instanceof Long)
            return (int) ((Long) o).longValue();
        else {
            logError("Unexpected type for intent response code.");
            logError(o.getClass().getName());
            throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
        }
    }

    void flagStartAsync(String operation) {
        if (mAsyncInProgress) {
            throw new IllegalStateException("Can't start async operation (" +
                    operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
        } else {
            mAsyncOperation = operation;
            mAsyncInProgress = true;
            logDebug("Starting async operation: " + operation);
        }
    }

    public void flagEndAsync() {
        logDebug("Ending async operation: " + mAsyncOperation);
        mAsyncOperation = "";
        mAsyncInProgress = false;
    }

    int queryPurchases(Inventory inv) throws JSONException, RemoteException {
        // Query purchases
        logDebug("Querying owned items...");
        logDebug("Package name: " + mContext.getPackageName());
        boolean verificationFailed = false;
        String  continueToken      = null;

        do {
            logDebug("Calling getPurchases with continuation token: " + continueToken);
            if (mService == null || mContext == null) {
                logError("Our service and/or our context are null.  Exiting.");
                return IABHELPER_UNKNOWN_ERROR;
            }
            Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
                    ITEM_TYPE_INAPP, continueToken);

            int response = getResponseCodeFromBundle(ownedItems);
            logDebug("Owned items response: " + String.valueOf(response));
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logDebug("getPurchases() failed: " + getResponseDesc(response));
                return response;
            }
            if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
                    || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
                logError("Bundle returned from getPurchases() doesn't contain required fields.");
                return IABHELPER_BAD_RESPONSE;
            }

            ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_ITEM_LIST);
            ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_PURCHASE_DATA_LIST);
            ArrayList<String> signatureList = ownedItems.getStringArrayList(
                    RESPONSE_INAPP_SIGNATURE_LIST);

            for (int i = 0; i < purchaseDataList.size(); ++i) {
                String purchaseData = purchaseDataList.get(i);
                String signature    = signatureList.get(i);
                String sku          = ownedSkus.get(i);
                if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
                    logDebug("Sku is owned: " + sku);
                    Purchase purchase = new Purchase(purchaseData, signature);

                    if (TextUtils.isEmpty(purchase.getToken())) {
                        logWarn("BUG: empty/null token!");
                        logDebug("Purchase data: " + purchaseData);
                    }

                    // Record ownership and token
                    inv.addPurchase(purchase);
                } else {
                    logWarn("Purchase signature verification **FAILED**. Not adding item.");
                    logDebug("   Purchase data: " + purchaseData);
                    logDebug("   Signature: " + signature);
                    verificationFailed = true;
                }
            }

            continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
            logDebug("Continuation token: " + continueToken);
        } while (!TextUtils.isEmpty(continueToken));

        return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
    }

    int querySkuDetails(Inventory inv, List<String> moreSkus) throws RemoteException, JSONException {
        logDebug("Querying SKU details.");
        ArrayList<String> skuList = new ArrayList<String>();
        skuList.addAll(inv.getAllOwnedSkus());
        if (moreSkus != null) {
            skuList.addAll(moreSkus);
        }

        if (skuList.size() == 0) {
            logDebug("queryPrices: nothing to do because there are no SKUs.");
            return BILLING_RESPONSE_RESULT_OK;
        }

        Bundle querySkus = new Bundle();
        querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
        Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
                ITEM_TYPE_INAPP, querySkus);

        if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
            int response = getResponseCodeFromBundle(skuDetails);
            if (response != BILLING_RESPONSE_RESULT_OK) {
                logDebug("getSkuDetails() failed: " + getResponseDesc(response));
                return response;
            } else {
                logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
                return IABHELPER_BAD_RESPONSE;
            }
        }

        ArrayList<String> responseList = skuDetails.getStringArrayList(
                RESPONSE_GET_SKU_DETAILS_LIST);

        for (String thisResponse : responseList) {
            SkuDetails d = new SkuDetails(thisResponse);
            logDebug("Got sku details: " + d);
            inv.addSkuDetails(d);
        }
        return BILLING_RESPONSE_RESULT_OK;
    }

    void consumeAsyncInternal(final List<Purchase> purchases,
                              final OnConsumeFinishedListener singleListener,
                              final OnConsumeMultiFinishedListener multiListener) {
        final Handler handler = new Handler();
        flagStartAsync("consume");
        new Thread(new Runnable() {

            @Override
            public void run() {
                final List<IabResult> results = new ArrayList<IabResult>();
                for (Purchase purchase : purchases) {
                    try {
                        consume(purchase);
                        results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
                    } catch (IabException ex) {
                        results.add(ex.getResult());
                    }
                }

                flagEndAsync();
                if (singleListener != null) {
                    handler.post(new Runnable() {

                        @Override
                        public void run() {
                            singleListener.onConsumeFinished(purchases.get(0), results.get(0));
                        }
                    });
                }
                if (multiListener != null) {
                    handler.post(new Runnable() {

                        @Override
                        public void run() {
                            multiListener.onConsumeMultiFinished(purchases, results);
                        }
                    });
                }
            }
        }).start();
    }

    public boolean isAsyncInProgress() {
        return mAsyncInProgress;
    }

    public boolean isSetupDone() {
        return mSetupDone;
    }

    void logDebug(String msg) {
        if (mDebugLog) {
            Log.d(mDebugTag, msg);
        }
    }

    void logError(String msg) {
        FirebaseCrash.report(new Exception("In-app billing error: " + msg));
        Log.e(mDebugTag, "In-app billing error: " + msg);
    }

    void logWarn(String msg) {
        Log.w(mDebugTag, "In-app billing warning: " + msg);
    }
} public class IabResult {

    int    mResponse;
    String mMessage;

    public IabResult(int response, String message) {
        mResponse = response;
        if (message == null || message.trim().length() == 0) {
            mMessage = IabHelper.getResponseDesc(response);
        } else {
            mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
        }
    }

    public int getResponse() {
        return mResponse;
    }

    public String getMessage() {
        return mMessage;
    }

    public boolean isSuccess() {
        return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK;
    }

    public boolean isFailure() {
        return !isSuccess();
    }

    @Override
    public String toString() {
        return "IabResult: " + getMessage();
    }
} public class Inventory {

    Map<String, SkuDetails> mSkuMap      = new HashMap<String, SkuDetails>();
    Map<String, Purchase>   mPurchaseMap = new HashMap<String, Purchase>();

    Inventory() {
    }

    /** Returns the listing details for an in-app product. */
    public SkuDetails getSkuDetails(String sku) {
        return mSkuMap.get(sku);
    }

    /** Returns purchase information for a given product, or null if there is no purchase. */
    public Purchase getPurchase(String sku) {
        return mPurchaseMap.get(sku);
    }

    /** Returns whether or not there exists a purchase of the given product. */
    public boolean hasPurchase(String sku) {
        return mPurchaseMap.containsKey(sku);
    }

    /** Return whether or not details about the given product are available. */
    public boolean hasDetails(String sku) {
        return mSkuMap.containsKey(sku);
    }

    /**
     * Erase a purchase (locally) from the inventory, given its product ID. This just
     * modifies the Inventory object locally and has no effect on the server! This is
     * useful when you have an existing Inventory object which you know to be up to date,
     * and you have just consumed an item successfully, which means that erasing its
     * purchase data from the Inventory you already have is quicker than querying for
     * a new Inventory.
     */
    public void erasePurchase(String sku) {
        if (mPurchaseMap.containsKey(sku)) {
            mPurchaseMap.remove(sku);
        }
    }

    /** Returns a list of all owned product IDs. */
    List<String> getAllOwnedSkus() {
        return new ArrayList<String>(mPurchaseMap.keySet());
    }

    /** Returns a list of all purchases. */
    List<Purchase> getAllPurchases() {
        return new ArrayList<Purchase>(mPurchaseMap.values());
    }

    void addSkuDetails(SkuDetails d) {
        mSkuMap.put(d.getSku(), d);
    }

    void addPurchase(Purchase p) {
        mPurchaseMap.put(p.getSku(), p);
    }
} public class ProPreferences {

    public static void enableAllPro(Context context) {
        ProPreferences.enablePro(context);
        ProPreferences.enableLiveNotification(context);
        ProPreferences.enableInstantDelay(context);
        ProPreferences.enableCompactNotification(context);
        ProPreferences.enableAutoRemoveNotification(context);
    }

    public static void disableAllPro(Context context) {
        ProPreferences.disablePro(context);
        ProPreferences.disableLiveNotification(context);
        ProPreferences.disableInstantDelay(context);
        ProPreferences.disableCompactNotification(context);
        ProPreferences.disableAutoRemoveNotification(context);
    }

    // instant delay
    public static boolean isPro(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SP_IS_PRO, false);
    }

    public static void enablePro(Context context) {
        enableGenericSetting(context, SP_SP_IS_PRO);
    }

    public static void disablePro(Context context) {
        disableGenericSetting(context, SP_SP_IS_PRO);
    }

    // instant delay
    public static boolean isInstantDelayEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_INSTANT_DELAY, true);
    }

    public static void enableInstantDelay(Context context) {
        enableGenericSetting(context, SP_SETT_INSTANT_DELAY);
    }

    public static void disableInstantDelay(Context context) {
        disableGenericSetting(context, SP_SETT_INSTANT_DELAY);
    }

    // live notification
    public static boolean isLiveNotificationEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_NOTIF_LIVE, true);
    }

    public static void enableLiveNotification(Context context) {
        enableGenericSetting(context, SP_SETT_NOTIF_LIVE);
    }

    public static void disableLiveNotification(Context context) {
        disableGenericSetting(context, SP_SETT_NOTIF_LIVE);
    }

    // live notification
    public static boolean isCompactNotificationEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_NOTIF_COMPACT, true);
    }

    public static void enableCompactNotification(Context context) {
        enableGenericSetting(context, SP_SETT_NOTIF_COMPACT);
    }

    public static void disableCompactNotification(Context context) {
        disableGenericSetting(context, SP_SETT_NOTIF_COMPACT);
    }

    // auto removing notification
    public static boolean isAutoRemoveNotificationEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_NOTIF_AUTO_REMOVE, true);
    }

    public static void enableAutoRemoveNotification(Context context) {
        enableGenericSetting(context, SP_SETT_NOTIF_AUTO_REMOVE);
    }

    public static void disableAutoRemoveNotification(Context context) {
        disableGenericSetting(context, SP_SETT_NOTIF_AUTO_REMOVE);
    }

} public class SettingsPreferences {

    // recent
    public static boolean isRecentEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_RECENT, true);
    }

    public static void enableRecent(Context context) {
        enableGenericSetting(context, SP_SETT_RECENT);
    }

    public static void disableRecent(Context context) {
        disableGenericSetting(context, SP_SETT_RECENT);
    }

    // recent hint
    public static boolean isRecentHintEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SP_RECENT_HINT, true);
    }

    public static void enableRecentHint(Context context) {
        enableGenericSetting(context, SP_SP_RECENT_HINT);
    }

    public static void disableRecentHint(Context context) {
        disableGenericSetting(context, SP_SP_RECENT_HINT);
    }

    // vibration
    public static boolean isVibrationEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_NOTIF_VIBRATION, true);
    }

    public static void enableVibration(Context context) {
        enableGenericSetting(context, SP_SETT_NOTIF_VIBRATION);
    }

    public static void disableVibration(Context context) {
        disableGenericSetting(context, SP_SETT_NOTIF_VIBRATION);
    }

    // lightning
    public static boolean isLightningEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_LIGHTNING, false);
    }

    public static void enableLightning(Context context) {
        enableGenericSetting(context, SP_SETT_LIGHTNING);
    }

    public static void disableLightning(Context context) {
        disableGenericSetting(context, SP_SETT_LIGHTNING);
    }

    // preemptive
    public static boolean isPreemptiveEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_GET_PREEMPTIVE, true);
    }

    public static void enablePreemptive(Context context) {
        enableGenericSetting(context, SP_SETT_GET_PREEMPTIVE);
    }

    public static void disablePreemptive(Context context) {
        disableGenericSetting(context, SP_SETT_GET_PREEMPTIVE);
    }

    // changes
    public static boolean isIncludeChangesEnabled(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP_SETT_INCLUDE_CHANGES, true);
    }

    public static void enableIncludeChanges(Context context) {
        enableGenericSetting(context, SP_SETT_INCLUDE_CHANGES);
    }

    public static void disableIncludeChanges(Context context) {
        disableGenericSetting(context, SP_SETT_INCLUDE_CHANGES);
    }

    // train categories
    public static boolean isCategoryEnabled(Context context, ENUM_CATEGORIES category) {
        return isGenericSettingEnabled(context, PREFIX_CATEGORIES + category.name(), true);
    }

    public static void enableAllCategories(Context context) {
        for (ENUM_CATEGORIES cat : ENUM_CATEGORIES.values()) {
            enableCategory(context, cat.name());
        }
    }

    public static void disableAllCategories(Context context) {
        for (ENUM_CATEGORIES cat : ENUM_CATEGORIES.values()) {
            disableCategory(context, cat.name());
        }
    }

    public static void enableCategory(Context context, String catName) {
        enableGenericSetting(context, PREFIX_CATEGORIES + catName);
    }

    public static void disableCategory(Context context, String catName) {
        disableGenericSetting(context, PREFIX_CATEGORIES + catName);
    }

    public static void setCategory(Context context, String catName, boolean isEnabled) {
        if (isEnabled) {
            enableCategory(context, catName);
        } else {
            disableCategory(context, catName);
        }
    }

    public static boolean isPossibleToDisableCheckbox(Context context) {
        return getEnabledCategoriesAsStringList(context).size() > 1;
    }

    public static String[] getEnabledCategoriesAsStringArray(Context context) {
        return getEnabledCategoriesAsStringList(context).toArray(new String[0]);
    }

    public static List<String> getEnabledCategoriesAsStringList(Context context) {
        List<String> categoriesArray = new ArrayList<>();
        for (ENUM_CATEGORIES cat : ENUM_CATEGORIES.values()) {
            if (isCategoryEnabled(context, cat)) {
                categoriesArray.add(cat.name());
            }
        }
        return categoriesArray;
    }

    public static String getCategoriesAsString(Context context, String separator) {
        String categoriesString = "";
        for (String s : getEnabledCategoriesAsStringList(context)) {
            categoriesString += s + separator;
        }
        if (categoriesString.length() > separator.length()) {
            categoriesString = categoriesString.substring(0, categoriesString.length() - separator.length());
        }
        return categoriesString;
    }


    // version code
    public static int getPreviouslySavedVersionCode(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceInt(context, SP_SP_SAVED_VERSION, 0);
    }

    public static void setCurrentVersionCode(Context context) {
        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_SAVED_VERSION, BuildConfig.VERSION_CODE);
    }

    // generic
    public static boolean isGenericSettingEnabled(Context context, String CONST, boolean defaultValue) {
        return SharedPreferencesHelper.getSharedPreferenceBoolean(context, CONST, defaultValue);
    }

    public static void enableGenericSetting(Context context, String CONST) {
        SharedPreferencesHelper.setSharedPreferenceBoolean(context, CONST, true);
    }

    public static void disableGenericSetting(Context context, String CONST) {
        SharedPreferencesHelper.setSharedPreferenceBoolean(context, CONST, false);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    private static void setNewSettingsNotPreviouslyIncludedBefore6(Context context) {
        // 6 - 0.7.4
        disableLightning(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore12(Context context) {
        // 12 - 0.8.2
        enableVibration(context);
        enablePreemptive(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore13(Context context) {
        // 13 - 0.8.4
        enableIncludeChanges(context);
        enableAllCategories(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore30(Context context) {
        // 27 - 1.0.0
        disableInstantDelay(context);
        disableLiveNotification(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore47(Context context) {
        // 43 - 1.0.0
        disableCompactNotification(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore48(Context context) {
        // 48 - 1.1.5
        enableRecentHint(context);
        enableRecent(context);
    }

    private static void setNewSettingsNotPreviouslyIncludedBefore51(Context context) {
        // 51 - 1.2.0
        disableAutoRemoveNotification(context);
    }

    public static void setDefaultSharedPreferencesOnFirstStart(Context context) {
        // preferences
        SharedPreferencesHelper.setSharedPreferenceBoolean(context, SP_SP_FIRST_START, false);
        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_SAVED_VERSION, BuildConfig.VERSION_CODE);
        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_VERSION, SP_VERSION);

        // settings screen
        setNewSettingsNotPreviouslyIncludedBefore6(context);
        setNewSettingsNotPreviouslyIncludedBefore12(context);
        setNewSettingsNotPreviouslyIncludedBefore13(context);
        setNewSettingsNotPreviouslyIncludedBefore30(context);
        setNewSettingsNotPreviouslyIncludedBefore47(context);
        setNewSettingsNotPreviouslyIncludedBefore48(context);
        setNewSettingsNotPreviouslyIncludedBefore51(context);
    }

    // inclusion of settings - update together with onFirstStart
    public static void setNewSettingsNotPreviouslyIncludedBefore(Context context) {
        if (getPreviouslySavedVersionCode(context) < 12) {
            setNewSettingsNotPreviouslyIncludedBefore12(context);
        }
        if (getPreviouslySavedVersionCode(context) < 13) {
            setNewSettingsNotPreviouslyIncludedBefore13(context);
        }
        if (getPreviouslySavedVersionCode(context) < 30) {
            setNewSettingsNotPreviouslyIncludedBefore30(context);
        }
        if (getPreviouslySavedVersionCode(context) < 47) {
            setNewSettingsNotPreviouslyIncludedBefore47(context);
        }
        if (getPreviouslySavedVersionCode(context) < 48) {
            setNewSettingsNotPreviouslyIncludedBefore48(context);
        }
        if (getPreviouslySavedVersionCode(context) < 51) {
            setNewSettingsNotPreviouslyIncludedBefore48(context);
        }
    }

    private static boolean areAllCategoriesEnabled(Context context) {
        return getEnabledCategoriesAsStringArray(context).length == ENUM_CATEGORIES.values().length;
    }

    public static boolean isAnySearchFilterEnabled(Context context) {
        return !isIncludeChangesEnabled(context) || !areAllCategoriesEnabled(context);
    }
} public static final String SETTINGS_DISABLE_AUTOREMOVE_NOTIFICATION = "Settings disable autoremove notification"; public static final String SETTINGS_DISABLE_CHANGES              = "Settings disable changes"; public static final String SETTINGS_DISABLE_COMPACT_NOTIFICATION    = "Settings disable compact notification"; public static final String SETTINGS_DISABLE_INSTANT_DELAY    = "Settings disable instant delay"; public static final String SETTINGS_DISABLE_LIGHTNING               = "Settings disable lightning"; public static final String SETTINGS_DISABLE_LIVE_NOTIFICATION       = "Settings disable live notification"; public static final String SETTINGS_DISABLE_PREEMPTIVE              = "Settings disable preemptive"; public static final String SETTINGS_DISABLE_RECENT_SEARCHES  = "Settings disable recent searches"; public static final String SETTINGS_DISABLE_VIBRATION               = "Settings disable vibration"; public static final String SETTINGS_ENABLE_AUTOREMOVE_NOTIFICATION  = "Settings enable autoremove notification"; public static final String SETTINGS_ENABLE_CHANGES                  = "Settings enable changes"; public static final String SETTINGS_ENABLE_COMPACT_NOTIFICATION     = "Settings enable compact notification"; public static final String SETTINGS_ENABLE_INSTANT_DELAY     = "Settings enable instant delay"; public static final String SETTINGS_ENABLE_LIGHTNING                = "Settings enable lightning"; public static final String SETTINGS_ENABLE_LIVE_NOTIFICATION        = "Settings enable live notification"; public static final String SETTINGS_ENABLE_PREEMPTIVE               = "Settings enable preemptive"; public static final String SETTINGS_ENABLE_RECENT_SEARCHES   = "Settings enable recent searches"; public static final String SETTINGS_ENABLE_VIBRATION         = "Settings enable vibration"; public static final String SP_SETT_GET_PREEMPTIVE       = "settings>isPreemptiveSearchEnabled"; public static final String SP_SETT_INCLUDE_CHANGES      = "settings>includeSolutionWithChanges"; public static final String SP_SETT_INSTANT_DELAY        = "settings>isInstantDelayEnabled"; public static final String SP_SETT_LIGHTNING            = "settings>isLightningEnabled"; public static final String SP_SETT_NOTIF_AUTO_REMOVE    = "settings>isNotifAutoRemoveEnabled"; public static final String SP_SETT_NOTIF_COMPACT        = "settings>isNotifCompactEnabled"; public static final String SP_SETT_NOTIF_LIVE           = "settings>isNotifLiveEnabled"; public static final String SP_SETT_NOTIF_VIBRATION      = "settings>isNotifVibrationEnabled"; public static final String SP_SETT_RECENT               = "settings>isRecentEnabled";
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import com.jaus.albertogiunta.justintrain_oraritreni.R;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.CustomIABHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.IAB.IabHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.IAB.IabResult;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.IAB.Inventory;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.ProPreferences;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.SettingsPreferences;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
import java.util.LinkedList;
import java.util.List;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_AUTOREMOVE_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_CHANGES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_COMPACT_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_INSTANT_DELAY;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_LIGHTNING;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_LIVE_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_PREEMPTIVE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_RECENT_SEARCHES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_DISABLE_VIBRATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_AUTOREMOVE_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_CHANGES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_COMPACT_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_INSTANT_DELAY;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_LIGHTNING;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_LIVE_NOTIFICATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_PREEMPTIVE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_RECENT_SEARCHES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SETTINGS_ENABLE_VIBRATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_GET_PREEMPTIVE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_INCLUDE_CHANGES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_INSTANT_DELAY;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_LIGHTNING;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_NOTIF_AUTO_REMOVE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_NOTIF_COMPACT;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_NOTIF_LIVE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_NOTIF_VIBRATION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SETT_RECENT;
package com.jaus.albertogiunta.justintrain_oraritreni.aboutAndSettings;






public class SettingsActivity extends AppCompatActivity implements IabHelper.QueryInventoryFinishedListener {


    private CustomIABHelper iabHelper;
    private boolean            isPro         = false;
    private List<CheckBox>     checkBoxes    = new LinkedList<>();
    private List<SwitchCompat> switchCompats = new LinkedList<>();


    LinearLayout linearLayout;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }

        View           view1;
        LayoutInflater inflater = LayoutInflater.from(this);
        view1 = inflater.inflate(R.layout.activity_about_settings, null);
        linearLayout = view1.findViewById(R.id.ll_container);

        this.iabHelper = CustomIABHelper.getInstance(SettingsActivity.this, this);


    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void buildView() {
        View aboutPage = new AboutPageBuilder(this)
                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Preferiti").build())
                // RECENT SEARCHES
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Ricerche recenti", "Visualizza le tue ultime tratte ricercate", SettingsPreferences.isRecentEnabled(this), true, false, isPro, SP_SETT_RECENT, SETTINGS_ENABLE_RECENT_SEARCHES, SETTINGS_DISABLE_RECENT_SEARCHES)
                        .build())
                // FAV TRAIN VIBRATION
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Ritardo ISTANTANEO (PRO)", "Quando accedi alla schermata iniziale", ProPreferences.isInstantDelayEnabled(this), true, true, isPro, SP_SETT_INSTANT_DELAY, SETTINGS_ENABLE_INSTANT_DELAY, SETTINGS_DISABLE_INSTANT_DELAY)
                        .build())
                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Notifica").build())
                // NOTIFICATION VIBRATION
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Vibrazione nella notifica", "Quando la attivi o la aggiorni", SettingsPreferences.isVibrationEnabled(this), true, false, isPro, SP_SETT_NOTIF_VIBRATION, SETTINGS_ENABLE_VIBRATION, SETTINGS_DISABLE_VIBRATION)
                        .build())
                // LIVE NOTIFICATION
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Notifiche LIVE (PRO)", "Sempre aggiornate all'ultimo secondo, ogni volta che accendi lo schermo", ProPreferences.isLiveNotificationEnabled(this), true, true, isPro, SP_SETT_NOTIF_LIVE, SETTINGS_ENABLE_LIVE_NOTIFICATION, SETTINGS_DISABLE_LIVE_NOTIFICATION)
                        .build())
                // LIVE NOTIFICATION
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Notifiche COMPATTE (PRO)", "Per vedere tutte le informazioni che ti servono in maniera sempre più veloce", ProPreferences.isCompactNotificationEnabled(this), true, true, isPro, SP_SETT_NOTIF_COMPACT, SETTINGS_ENABLE_COMPACT_NOTIFICATION, SETTINGS_DISABLE_COMPACT_NOTIFICATION)
                        .build())
                // AUTO REMOVE NOTIFICATION
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Notifiche SMART (PRO)", "La notifica saprà quando sei arrivato a destinazione e se ne andrà da sola, senza che tu debba far nulla", ProPreferences.isCompactNotificationEnabled(this), true, true, isPro, SP_SETT_NOTIF_AUTO_REMOVE, SETTINGS_ENABLE_AUTOREMOVE_NOTIFICATION, SETTINGS_DISABLE_AUTOREMOVE_NOTIFICATION)
                        .build())
                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Risultati").build())
                // SMART AUTOSCROLL (LIGHTNING)
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Autoscroll intelligente", "Sulla prima soluzione prendibile", SettingsPreferences.isLightningEnabled(this), true, false, isPro, SP_SETT_LIGHTNING, SETTINGS_ENABLE_LIGHTNING, SETTINGS_DISABLE_LIGHTNING)