public class MyApplication extends Application {

    public static AppDatabase database;

    @Override
    public void onCreate() {
        super.onCreate();
        JodaTimeAndroid.init(this);

        String fontPath = "fonts/rooneysans-medium.ttf";
        CalligraphyConfig.initDefault(new CalligraphyConfig.Builder()
                .setDefaultFontPath(fontPath)
                .setFontAttrId(R.attr.fontPath)
                .build()
        );

//        this.deleteDatabase("justintrain.db");
        if (database == null) {
            database = AppDatabase.getAppDatabase(getBaseContext(), false);
        }

        try {
            if (database.stationDao().getAll().size() == 0) {
                this.deleteDatabase("justintrain.db");
                database = AppDatabase.getAppDatabase(getBaseContext(), true);
            }
        } catch (Exception e) {
            this.deleteDatabase("justintrain.db");
            database = AppDatabase.getAppDatabase(getBaseContext(), true);
        }

        FirebaseCrash.setCrashCollectionEnabled(!BuildConfig.DEBUG);

        if (BuildConfig.DEBUG) {
//            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
//                    .detectAll()
//                    .penaltyLog()
//                    .build());
//            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
//                    .detectAll()
//                    .penaltyLog()
//                    .build());
//            SPTestingHelper.setupSPv0(this);
//            SPTestingHelper.setupSPv1(this);
//            Stetho.initializeWithDefaults(this);
        }
    }
} public class AboutActivity extends AppCompatActivity {


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

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

        String text = "Ciao, sono Alberto, lo sviluppatore di JustInTrain - Orari Trenitalia." +
                "\n\nProprio come te, anche io sono un pendolare, e ho sviluppato quest'app con l'obiettivo di rendere a tutti noi la vita un po' più semplice. Ho cercato principalmente di ridurre al minimo le perdite di tempo dovute a interfacce poco ottimizzate o che non tenessero in considerazione le nostre necessità." +
                "\n\nPer questo ho speso una quantità considerevole di tempo nella realizzazione di ciò che vedi, oltre ad un Server che fa funzionare il tutto, che non vedi ma che ha un costo vivo mensile." +
                "\n\nTi chiedo quindi, dato l'impegno e la dedizione che ho versato in questo progetto, di aiutarmi nella sua manutenzione e sviluppo se trovi l'app utile, o se reputi che se lo meriti." +
                "\n\nCosa puoi fare? Anche un piccolo gesto come una buona recensione o il passaparola, o acquistare la versione PRO fanno una grande differenza." +
                "\n\nBuon viaggio!";

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

        View aboutPage = new AboutPageBuilder(this)

                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Feedback").build())
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("L'app ti piace e ti è stata utile?", "Votala sul Google Play Store!")
                        .addOnClickListener(view -> {
                            try {
                                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getPackageName())));
                            } catch (android.content.ActivityNotFoundException anfe) {
                                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + getPackageName())));
                            }
                        }).build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("Feedback, supporto e suggerimenti", "albertogiuntadev@gmail.com")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent(Intent.ACTION_SEND);
                            intent.setType("message/rfc822");
                            intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"albertogiuntadev@gmail.com"});
                            intent.putExtra(Intent.EXTRA_SUBJECT, "Feedback / supporto / suggerimenti");
                            startActivity(Intent.createChooser(intent, "Invia email..."));
                        }).build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("Condividi l'app con un amico!", "GO #TeamJustInTrainers")
                        .addOnClickListener(view -> {
                            Intent sendIntent = new Intent();
                            sendIntent.setAction(Intent.ACTION_SEND);
                            sendIntent.putExtra(Intent.EXTRA_TEXT,
                                    "Dai un'occhiata a quest'app pensata appositamente per i pendolari Trenitalia!\nhttps://play.google.com/store/apps/details?id=com.jaus.albertogiunta.justintrain_oraritreni");
                            sendIntent.setType("text/plain");
                            startActivity(Intent.createChooser(sendIntent, "Consiglia l'app via..."));
                        })
                        .build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("Iscriviti al canale Telegram!", "Per anticipazioni e per votare nuove feature")
                        .addOnClickListener(view -> {
                            if (AboutPageUtils.isAppInstalled(this, "org.telegram.messenger")) {
                                Intent intent = new Intent();
                                intent.setAction(Intent.ACTION_VIEW);
                                intent.addCategory(Intent.CATEGORY_BROWSABLE);
                                intent.setData(Uri.parse("https://telegram.me/justintrain"));
                                startActivity(intent);
                            } else {
                                Toast.makeText(this, "Telegram non sembra essere installato sul tuo dispositivo!", Toast.LENGTH_LONG).show();
                            }
                        })
                        .build())

                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Info sull'app").build())
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("Changelog", "Versione " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")")
                        .addOnClickListener(view -> {
                            handler.sendEmptyMessage(1);
                        })
                        .build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitleSubtitle("Tutorial", "Clicca per visualizzare il Tutorial")
                        .addOnClickListener(view -> {
                            Intent i = new Intent(AboutActivity.this, IntroActivity.class);
                            startActivity(i);
                        })
                        .build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithTitle("Licenze")
                        .addOnClickListener(v -> {
                            Intent myIntent = new Intent(AboutActivity.this, LicencesActivity.class);
                            AboutActivity.this.startActivity(myIntent);
                        })
                        .build())

                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Autore").build())
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithIconTitleSubtitle(R.drawable.about_icon_link, "Sito personale", "www.albertogiunta.it")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent();
                            intent.setAction(Intent.ACTION_VIEW);
                            intent.addCategory(Intent.CATEGORY_BROWSABLE);
                            intent.setData(Uri.parse("http://www.albertogiunta.it"));
                            startActivity(intent);
                        }).build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithIconTitleSubtitle(R.drawable.about_icon_email, "Email", "albertogiuntadev@gmail.com")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent(Intent.ACTION_SEND);
                            intent.setType("message/rfc822");
                            intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"albertogiuntadev@gmail.com"});
                            intent.putExtra(Intent.EXTRA_SUBJECT, "Feedback / supporto / suggerimenti");
                            startActivity(Intent.createChooser(intent, "Invia email..."));
                        }).build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithIconTitleSubtitle(R.drawable.about_icon_twitter, "Twitter", "albigiu")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent();
                            intent.setAction(Intent.ACTION_VIEW);
                            intent.addCategory(Intent.CATEGORY_BROWSABLE);
                            if (AboutPageUtils.isAppInstalled(this, "com.twitter.android")) {
                                intent.setPackage("com.twitter.android");
                                intent.setData(Uri.parse(String.format("twitter://user?screen_name=%s", "albigiu")));
                            } else {
                                intent.setData(Uri.parse(String.format("https://twitter.com/intent/user?screen_name=%s", "albigiu")));
                            }
                            startActivity(intent);
                        })
                        .build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithIconTitleSubtitle(R.drawable.about_icon_instagram, "Instagram", "illbegiu")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent();
                            intent.setAction(Intent.ACTION_VIEW);
                            intent.addCategory(Intent.CATEGORY_BROWSABLE);
                            intent.setData(Uri.parse(String.format("https://www.instagram.com/%s", "illbegiu")));
                            startActivity(intent);
                        })
                        .build())
                .addSeparator() //-----------------------------------------//
                .addItem(new ItemBuilder(this, linearLayout)
                        .addItemWithIconTitleSubtitle(R.drawable.about_icon_github, "Github", "albertogiunta")
                        .addOnClickListener(view -> {
                            Intent intent = new Intent();
                            intent.setAction(Intent.ACTION_VIEW);
                            intent.addCategory(Intent.CATEGORY_BROWSABLE);
                            intent.setData(Uri.parse(String.format("https://github.com/%s", "albertogiunta")));
                            startActivity(intent);
                        })
                        .build())

                .addItem(new ItemBuilder(this, linearLayout).addItemLongText(text).build())

                .build();

        setContentView(aboutPage);

    }

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

    Handler handler = new Handler(message -> {
        AboutPageUtils.showChangelog(this);
        return false;
    });
} public class AboutPageUtils {

    public static Boolean isAppInstalled(Context context, String appName) {
        PackageManager pm = context.getPackageManager();
        boolean        installed;
        try {
            pm.getPackageInfo(appName, PackageManager.GET_ACTIVITIES);
            installed = true;
        } catch (PackageManager.NameNotFoundException e) {
            installed = false;
        }
        return installed;
    }

    public static void showChangelog(Context context) {
        AlertDialog.Builder alertDialog = new AlertDialog.Builder(context);
        View                view        = LayoutInflater.from(context).inflate(R.layout.dialog_changelog, null);
        alertDialog.setView(view)
                .setTitle("Novità versione " + BuildConfig.VERSION_NAME)
                .setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss())
                .setNegativeButton("VOTA SUL PLAY STORE", (dialogInterface, i) -> {
                    try {
                        context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + context.getPackageName())));
                    } catch (android.content.ActivityNotFoundException anfe) {
                        context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + context.getPackageName())));
                    }
                })
                .create();

        Button email    = view.findViewById(R.id.btn_email);
        Button twitter  = view.findViewById(R.id.btn_twitter);
        Button telegram = view.findViewById(R.id.btn_telegram);

        email.setOnClickListener(v -> {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType("message/rfc822");
            intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"albertogiuntadev@gmail.com"});
            intent.putExtra(Intent.EXTRA_SUBJECT, "Feedback / supporto / suggerimenti");
            context.startActivity(Intent.createChooser(intent, "Invia email..."));
        });

        twitter.setOnClickListener(v -> {
            // Create intent using ACTION_VIEW and a normal Twitter url:
            String tweetUrl = "https://twitter.com/intent/tweet?text=@albigiu";
            Intent intent   = new Intent(Intent.ACTION_VIEW, Uri.parse(tweetUrl));
            // Narrow down to official Twitter app, if available:
            List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent, 0);
            for (ResolveInfo info : matches) {
                if (info.activityInfo.packageName.toLowerCase().startsWith("com.twitter")) {
                    intent.setPackage(info.activityInfo.packageName);
                }
            }
            context.startActivity(intent);
        });

        telegram.setOnClickListener(v -> {
            if (AboutPageUtils.isAppInstalled(context, "org.telegram.messenger")) {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_VIEW);
                intent.addCategory(Intent.CATEGORY_BROWSABLE);
                intent.setData(Uri.parse("https://telegram.me/justintrain"));
                context.startActivity(intent);
            } else {
                Toast.makeText(context, "Telegram non sembra essere installato sul tuo dispositivo!", Toast.LENGTH_LONG).show();
            }
        });


        alertDialog.show();
    }

} public class LicenseUpgradeActivity extends AppCompatActivity implements IabHelper.OnIabSetupFinishedListener, IabHelper.OnIabPurchaseFinishedListener {

    AnalyticsHelper analyticsHelper;

    public static final String SKU_UPGRADE  = "premium_upgrade_mp";
    public static final String SKU_DONATION = "donate";

    @BindView(R.id.toolbar)
    Toolbar  toolbar;
    @BindView(R.id.btn_iap_first)
    Button   btnBuyFirst;
    @BindView(R.id.btn_iap_second)
    Button   btnBuySecond;
    @BindView(R.id.tv_already_pro)
    TextView tvAlreadyPro;
    @BindView(R.id.btn_donate_paypal)
    Button   btnAlreadyProPaypal;
    @BindView(R.id.tv_pro_users_number)
    TextView tvProUsersNumber;

    @BindViews({R.id.tv_already_pro, R.id.ll_already_pro_for_paypal})
    List<View> alreadyProViews = new LinkedList<>();

    boolean isAlreadyPro = false;
    private IabHelper billingHelper;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_license_upgrade);
        ButterKnife.bind(this);
        analyticsHelper = AnalyticsHelper.getInstance(LicenseUpgradeActivity.this);

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

        billingHelper = new IabHelper(this, "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnl+N1zhaAmBhLF54yJo7T+ku8m5c/XZEsZ3+zBb7HyKCxpGuQL4+oPTVSNRtNdXNCl2R0eqojv+f3sT6tWL2XHt1DK6F7rYVtvs8rmQR1himMDEV0Tgljn5uBQuMag9nvNeyGIAi1wQeRz9Hk8tGF+laZcD3phR6NFnrwebD8FkG0lc6XHKpjAzCsdu+YbyPH7JuFAUoi+Yrbvs50r0JzeUWqz+v427V4W4kQR9zAmN3evVmou1R+2WXPtD1wGMMSQAs2DOuxfgh85o4sRS21an8xQk/d70EX3E78O3iXzl76CKZNM54O2g8iE3tkNgGfeCkqs0m2v/2E6VDubVf3QIDAQAB");
        billingHelper.startSetup(this);

        btnBuyFirst.setOnClickListener(v -> {
            onIAPButtonClicked();
            analyticsHelper.logScreenEvent(SCREEN_UPGRADE_LICENSE, IAP_TOP);
        });

        btnBuySecond.setOnClickListener(v -> {
            onIAPButtonClicked();
            analyticsHelper.logScreenEvent(SCREEN_UPGRADE_LICENSE, IAP_BOTTOM);
        });

        btnAlreadyProPaypal.setOnClickListener(v -> {
            Uri    uri    = Uri.parse("https://www.paypal.me/albertogiunta");
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(intent);
            analyticsHelper.logScreenEvent(SCREEN_UPGRADE_LICENSE, IAP_PAYPAL);
        });

        Resources res          = getResources();
        String    proUsrNumber = String.format(res.getString(R.string.upgrade_pro_users_number), ((int) FirebaseRemoteConfig.getInstance().getDouble(FIREBASE_PRO_USERS_NUMBER)));
        tvProUsersNumber.setText(proUsrNumber);

    }

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

    public void onIAPButtonClicked() {
        if (isAlreadyPro) {
            purchaseItem(SKU_DONATION);
        } else {
            purchaseItem(SKU_UPGRADE);
        }
    }

    @Override
    public void onIabSetupFinished(IabResult result) {
        if (result.isSuccess()) {
            dealWithIabSetupSuccess(result);
        } else {
            Log.d("Problem setting up In-app Billing: " + result);
            dealWithIabSetupFailure();
        }
    }

    private void dealWithIabSetupSuccess(IabResult result) {
        List<String> l = new LinkedList<>();
        l.add(SKU_UPGRADE);
        l.add(SKU_DONATION);
        if (billingHelper.isSetupDone() && !billingHelper.isAsyncInProgress()) {
            billingHelper.queryInventoryAsync(true, l, (result2, inv) -> {
                if (inv != null && inv.getSkuDetails(SKU_UPGRADE) != null) {
                    if (CustomIABHelper.isOrderOk(result2, inv)) {
                        isAlreadyPro = true;
                        apply(alreadyProViews, VISIBLE);
                        String textForBtn = "SUPPORTA LO SVILUPPO";
                        if (FirebaseRemoteConfig.getInstance().getBoolean(FIREBASE_SHOW_MONEY_IN_IAP)) {
                            String money = "(" + inv.getSkuDetails(SKU_DONATION).getPrice() + ")";
                            textForBtn += " " + money;
                        }
                        btnBuyFirst.setText(textForBtn);
                        btnBuySecond.setText(textForBtn);
                    } else {
                        apply(alreadyProViews, GONE);
                        String textForBtn = FirebaseRemoteConfig.getInstance().getString(FIREBASE_IAP_BTN_MESSAGE);
                        if (FirebaseRemoteConfig.getInstance().getBoolean(FIREBASE_SHOW_MONEY_IN_IAP)) {
                            String money = "(" + inv.getSkuDetails(SKU_UPGRADE).getPrice() + ")";
                            textForBtn += " " + money;
                        }
                        btnBuyFirst.setText(textForBtn);
                        btnBuySecond.setText(textForBtn);
                    }
                } else {
                    FirebaseCrash.report(new Exception("USER TRIED TO BUY AND INVENTORY WAS NULL!"));
                }

                // TODO DEBUG
                boolean debugPurchase = false;
                if (!BuildConfig.DEBUG) debugPurchase = false;
                if (debugPurchase && CustomIABHelper.isOrderOk(result, inv)) {
                    if (inv != null && inv.hasPurchase(SKU_UPGRADE)) {
                        billingHelper.consumeAsync(inv.getPurchase(SKU_UPGRADE), (purchase, result1) -> {
                            if (inv.hasPurchase(SKU_DONATION)) {
                                billingHelper.consumeAsync(inv.getPurchase(SKU_DONATION), null);
                            }
                        });
                        ProPreferences.disableLiveNotification(LicenseUpgradeActivity.this);
                        ProPreferences.disableInstantDelay(LicenseUpgradeActivity.this);
                    }
                }
                // TODO END DEBUG
            });
        }
    }

    private void dealWithIabSetupFailure() {
        Log.d("dealWithIabSetupFailure: setup failed!!!");
        FirebaseCrash.report(new Exception("IAB SETUP FAILED!"));
    }

    protected void purchaseItem(String sku) {
        try {
            if (billingHelper != null) {
                billingHelper.flagEndAsync();
                billingHelper.launchPurchaseFlow(this, sku, 123, this);
            }
        } catch (IllegalStateException e) {
            Log.d("purchaseItem: exception");
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (billingHelper == null) return;
        // Pass on the activity result to the helper for handling
        if (!billingHelper.handleActivityResult(requestCode, resultCode, data)) {
            // not handled, so handle it ourselves (here's where you'd
            // perform any handling of activity results not related to in-app
            // billing...
            super.onActivityResult(requestCode, resultCode, data);
        } else {
            Log.d("onActivityResult handled by IABUtil.");
        }
    }

    /**
     * Security Recommendation: When you receive the purchase response from Google Play, make sure
     * to check the returned data
     * signature, the orderId, and the developerPayload string in the Purchase object to make sure
     * that you are getting the
     * expected values. You should verify that the orderId is a unique value that you have not
     * previously processed, and the
     * developerPayload string matches the token that you sent previously with the purchase request.
     * As a further security
     * precaution, you should perform the verification on your own secure server.
     */
    @Override
    public void onIabPurchaseFinished(IabResult result, Purchase info) {
        if (result.isFailure()) {
            dealWithPurchaseFailed(result);
        } else if (SKU_UPGRADE.equals(info.getSku())) {
            dealWithPurchaseSuccessUpgrade(result, info);
        } else if (SKU_DONATION.equals(info.getSku())) {
            dealWithPurchaseSuccessDonate(result, info);
        }
    }

    protected void dealWithPurchaseFailed(IabResult result) {
        Log.d("Error purchasing: " + result);
        ProPreferences.disablePro(LicenseUpgradeActivity.this);
        FirebaseCrash.report(new Exception("Purchase failed. Code: " + result.getResponse() + " " + result.getMessage()));
    }

    protected void dealWithPurchaseSuccessUpgrade(IabResult result, Purchase info) {
        if (billingHelper.isSetupDone() && !billingHelper.isAsyncInProgress()) {
            billingHelper.queryInventoryAsync(false, (result1, inv) -> {
                ProPreferences.enablePro(LicenseUpgradeActivity.this);
                ProPreferences.enableLiveNotification(LicenseUpgradeActivity.this);
                ProPreferences.enableInstantDelay(LicenseUpgradeActivity.this);
                showDialogCustom(R.layout.dialog_gone_pro);
            });
        }
    }

    protected void dealWithPurchaseSuccessDonate(IabResult result, Purchase info) {
        if (billingHelper.isSetupDone() && !billingHelper.isAsyncInProgress()) {
            billingHelper.queryInventoryAsync(false, (result1, inv) -> {
                billingHelper.consumeAsync(inv.getPurchase(SKU_DONATION), null);
                showDialogCustom(R.layout.dialog_donation);
            });
        }
    }

    @Override
    protected void onDestroy() {
        disposeBillingHelper();
        super.onDestroy();
    }

    private void disposeBillingHelper() {
        if (billingHelper != null) {
            billingHelper.dispose();
        }
        billingHelper = null;
    }

    private void showDialogCustom(int layout) {
        AlertDialog.Builder alertDialog = new AlertDialog.Builder(LicenseUpgradeActivity.this);
        View                view        = LayoutInflater.from(LicenseUpgradeActivity.this).inflate(layout, null);
        alertDialog.setView(view)
                .setPositiveButton("OK", (dialogInterface, i) -> dialogInterface.dismiss())
                .create()
                .show();
    }
} 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)
                        .build())
                .addSeparator()
                // PREEMPTIVE SEARCH
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Cerca con precauzione", "Includendo una prima soluzione in più", SettingsPreferences.isPreemptiveEnabled(this), true, false, isPro, SP_SETT_GET_PREEMPTIVE, SETTINGS_ENABLE_PREEMPTIVE, SETTINGS_DISABLE_PREEMPTIVE)
                        .build())
                .addItem(new ItemBuilder(this, linearLayout).addItemGroupHeader("Filtri di ricerca").build())
                // INCLUDE CHANGES
                .addItem(new ItemBuilder(this, linearLayout).addItemPrefWithToggle(this, "Includi le soluzioni con cambi", "Se disattivata, e non appaiono soluzioni, potrebbe essere necessario cercare ad un orario differente", SettingsPreferences.isIncludeChangesEnabled(this), true, false, isPro, SP_SETT_INCLUDE_CHANGES, SETTINGS_ENABLE_CHANGES, SETTINGS_DISABLE_CHANGES)
                        .build())
                .addSeparator()
                // EXCLUDE CATEGORIES
                .addItem(new ItemBuilder(this, linearLayout).addItemTrainCategories(this)
                        .build())
                .addSeparator()
                .build();

        setContentView(aboutPage);
    }

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (CustomIABHelper.isOrderOk(result, inv)) {
            isPro = true;
        }
        buildView();
    }
} @SuppressWarnings("unused")
public class PreferredJourney {

    private PreferredStation station1;
    private PreferredStation station2;
    private long             timestamp;
    private int              priority;
    private String           category;
    private String           version;

    public PreferredJourney(PreferredStation station1, PreferredStation station2) {
        this.station1 = station1;
        this.station2 = station2;
        timestamp = DateTime.now().toInstant().getMillis();
        priority = -1;
        category = "";
        version = "";
    }

    public PreferredJourney swapStations() {
        PreferredStation temp = station1;
        station1 = station2;
        station2 = temp;
        return this;
    }

    public PreferredStation getStation1() {
        return station1;
    }

    public void setStation1(PreferredStation station1) {
        this.station1 = station1;
    }

    public PreferredStation getStation2() {
        return station2;
    }

    public void setStation2(PreferredStation station2) {
        this.station2 = station2;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    @Override
    public String toString() {
        return "PreferredJourney{" +
                "station1=" + station1 +
                ", station2=" + station2 +
                ", timestamp=" + timestamp +
                ", priority=" + priority +
                ", category='" + category + '\'' +
                ", version='" + version + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        return timestamp == ((PreferredJourney) o).timestamp;
    }
} @Entity(tableName = "station")
public class Station {

    @PrimaryKey
    private Integer sid;

    @ColumnInfo(name = "name_short")
    private String  nameShort;

    @ColumnInfo(name = "name_long")
    private String  nameLong;

    @ColumnInfo(name = "name_fancy")
    private String nameFancy;

    @ColumnInfo(name = "id_short")
    private String  stationShortId;

    @ColumnInfo(name = "id_long")
    private String  stationLongId;

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getStationShortId() {
        return stationShortId;
    }

    public void setStationShortId(String stationShortId) {
        this.stationShortId = stationShortId;
    }

    public String getStationLongId() {
        return stationLongId;
    }

    public void setStationLongId(String stationLongId) {
        this.stationLongId = stationLongId;
    }

    public String getNameFancy() {
        return nameFancy;
    }

    public void setNameFancy(String nameFancy) {
        this.nameFancy = nameFancy;
    }

    public String getNameShort() {
        return nameShort;
    }

    public void setNameShort(String nameShort) {
        this.nameShort = nameShort;
    }

    public String getNameLong() {
        return nameLong;
    }

    public void setNameLong(String nameLong) {
        this.nameLong = nameLong;
    }

    @Override
    public String toString() {
        return "Station{" +
                "stationShortId='" + stationShortId + '\'' +
                ", stationLongId='" + stationLongId + '\'' +
                ", nameLong='" + nameLong + '\'' +
                ", nameShort='" + nameShort + '\'' +
                '}';
    }
} public class JourneyResultsActivity extends AppCompatActivity implements
        JourneyResultsContract.View,
        IabHelper.QueryInventoryFinishedListener {

    public static final String ACTION = "com.jaus.albertogiunta.justintrain_oraritreni.OPEN_DYNAMIC_SHORTCUT";

    JourneyResultsContract.Presenter presenter;
    AnalyticsHelper                  analyticsHelper;
    BroadcastReceiver                messageReceiver;
    CustomIABHelper                  iabHelper;

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.rl_header_2)
    RelativeLayout rlHeader2;

    @BindView(R.id.tv_departure_station_name)
    TextView    tvHeaderDepartureStation;
    @BindView(R.id.tv_arrival_station_name)
    TextView    tvHeaderArrivalStation;
    @BindView(R.id.btn_header_swap_station_names)
    ImageButton btnHeaderSwapStationNames;
    @BindView(R.id.btn_toggle_favourite)
    ImageButton btnHeaderToggleFavorite;

    @BindView(R.id.rl_banner_placeholder)
    RelativeLayout rlBannerPlaceholder;
    @BindView(R.id.adView2)
    AdView         adView2;

    @BindView(R.id.loading_spinner)
    ProgressBar progressBar;

    // NO SOLUTION FOUND
    @BindView(R.id.rl_error)
    RelativeLayout rlEmptyJourneyBox;
    @BindView(R.id.tv_error_message)
    TextView       tvErrorMessage;
    @BindView(R.id.btn_error_button)
    Button         btnErrorMessage;

    //  RESULTS
    @BindView(R.id.rv_journey_solutions)
    RecyclerView rvJourneySolutions;
    @BindView(R.id.btn_refresh)
    ImageButton  btnRefresh;

    JourneyResultsAdapter journeyResultsAdapter;

    private long refreshBtnLastClickTime = SystemClock.elapsedRealtime();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_journey_results);
        ButterKnife.bind(this);
        analyticsHelper = AnalyticsHelper.getInstance(getViewContext());
        iabHelper = CustomIABHelper.getInstance(JourneyResultsActivity.this, this);
        presenter = new JourneyResultsPresenter(this);
        setSupportActionBar(toolbar);
        //noinspection ConstantConditions
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        btnHeaderSwapStationNames.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ACTION_SWAP_STATIONS_FROM_RESULTS);
            presenter.onSwapButtonClick();
        });
        btnHeaderToggleFavorite.setOnClickListener(v -> presenter.onFavouriteButtonClick());

        rvJourneySolutions.setHasFixedSize(true);
        rvJourneySolutions.setLayoutManager(new WrapContentLinearLayoutManager(getViewContext(), LinearLayoutManager.VERTICAL, false));

        journeyResultsAdapter = new JourneyResultsAdapter(this, presenter);
        rvJourneySolutions.setAdapter(journeyResultsAdapter);

        rvJourneySolutions.addOnScrollListener(new HideShowScrollListener() {
            @Override
            public void onHide() {
                btnRefresh.animate().setInterpolator(new LinearInterpolator()).translationY(600).setDuration(100);
            }

            @Override
            public void onShow() {
                btnRefresh.animate().setInterpolator(new LinearInterpolator()).translationY(0).setDuration(100);
            }
        });

        btnRefresh.setOnClickListener(v -> {
            if (SystemClock.elapsedRealtime() - refreshBtnLastClickTime > 1000) {
                AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.LIGHT);
                analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ACTION_REFRESH_JOURNEY);
                presenter.searchFromSearch(true);
            }
            refreshBtnLastClickTime = SystemClock.elapsedRealtime();
        });
        presenter.setState(getIntent().getExtras());
        presenter.searchFromSearch(true);

        messageReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                showSnackbar(intent.getExtras().getString(NotificationService.NOTIFICATION_ERROR_MESSAGE), NONE, Snackbar.LENGTH_SHORT);
            }
        };

        if (ignoreBeingProAndShowAds) {
            loadAds();
        }
    }

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

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                presenter.setState(data.getExtras());
                presenter.searchFromSearch(true);
            }
        }
    }

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(presenter.getState(outState));
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        presenter.setState(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        iabHelper.isUserPro(this);
        LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, new IntentFilter(NotificationService.NOTIFICATION_ERROR_EVENT));
    }

    @Override
    protected void onDestroy() {
        presenter.unsubscribe();
        super.onDestroy();
    }

    @Override
    public Context getViewContext() {
        return JourneyResultsActivity.this;
    }

    @Override
    public void showSnackbar(String message, ENUM_SNACKBAR_ACTIONS intent, int duration) {
        journeyResultsAdapter.notifyItemChanged(0);
        journeyResultsAdapter.notifyItemChanged(journeyResultsAdapter.getItemCount() - 1);
        Log.w(android.R.id.message);
        Snackbar snackbar = Snackbar
                .make(rvJourneySolutions, message, duration);
        ((TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text)).setTextColor(ContextCompat.getColor(this, R.color.txt_white));
        switch (intent) {
            case NONE:
                break;
            case REFRESH:
                snackbar.setAction("Aggiorna", view -> presenter.searchFromSearch(true)).setActionTextColor(ContextCompat.getColor(this, R.color.btn_cyan));
                break;
            default:
                break;
        }
        snackbar.show();
    }

    @Override
    public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
        rvJourneySolutions.setVisibility(View.GONE);
        rlEmptyJourneyBox.setVisibility(View.GONE);
    }

    @Override
    public void hideProgress() {
        progressBar.setVisibility(View.GONE);
        rvJourneySolutions.setVisibility(View.VISIBLE);
    }

    @Override
    public void showErrorMessage(String tvMessage, String btnMessage, ENUM_ERROR_BTN_STATUS intent) {
        progressBar.setVisibility(View.GONE);
        rvJourneySolutions.setVisibility(View.GONE);
        rlEmptyJourneyBox.setVisibility(View.VISIBLE);
        btnErrorMessage.setText(btnMessage);
        tvErrorMessage.setText(tvMessage);
        btnErrorMessage.setOnClickListener(v -> {
            Intent i;
            switch (intent) {
                case CONN_SETTINGS:
                    analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ERROR_CONNECTIVITY);
                    i = new Intent(Settings.ACTION_SETTINGS);
                    startActivity(i);
                    break;
                case GO_TO_SEARCH:
                    i = new Intent(JourneyResultsActivity.this, JourneySearchActivity.class);
                    startActivity(i);
                    break;
                case SEND_REPORT:
                    analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ERROR_SERVER);
                    FirebaseCrash.report(new Exception(SCREEN_JOURNEY_RESULTS + ERROR_SERVER + ": " +
                            "" + presenter.getPreferredJourney().getStation1().getStationLongId() +
                            " > " + presenter.getPreferredJourney().getStation2().getStationLongId() +
                            " @ " + presenter.getTimeOfSearch().toString("HH:mm")));
                    showSnackbar("Messaggio ricevuto! Grazie per l'aiuto!", NONE, Snackbar.LENGTH_LONG);
                    break;
                case NO_SOLUTIONS:
                    analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ERROR_NOT_FOUND_JOURNEY);
                    finish();
                    break;
                case SERVICE_UNAVAILABLE:
                    analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ERROR_SERVICE_UNAVAILABLE);
                    analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ACTION_REFRESH_JOURNEY);
                    presenter.searchFromSearch(true);
                    break;
            }
        });
    }

    @Override
    public void scrollToFirstFeasibleSolution(int position) {
        ((LinearLayoutManager) rvJourneySolutions.getLayoutManager()).scrollToPositionWithOffset(position, 14);
    }

    @Override
    public void updateSolutionsList() {
        rvJourneySolutions.getRecycledViewPool().clear();
        try {
            journeyResultsAdapter.notifyDataSetChanged();
        } catch (Exception e) {
            FirebaseCrash.report(new Exception("CATCHED Inconsistency detected. Invalid view holder adapter positionViewHolder"));
        }
    }

    @Override
    public void updateSolution(int elementIndex) {
        journeyResultsAdapter.notifyItemChanged(elementIndex);
    }

    @Override
    public void setFavouriteButtonStatus(boolean isPreferred) {
        if (isPreferred) {
            analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ACTION_SET_FAVOURITE_FROM_RESULTS);
            this.btnHeaderToggleFavorite.setImageResource(R.drawable.ic_star_black);
            AnimationUtils.animOnPress(this.btnHeaderToggleFavorite, AnimationUtils.ANIM_TYPE.MEDIUM);
        } else {
            analyticsHelper.logScreenEvent(SCREEN_JOURNEY_RESULTS, ACTION_REMOVE_FAVOURITE_FROM_RESULTS);
            this.btnHeaderToggleFavorite.setImageResource(R.drawable.ic_star_border);
        }
    }

    @Override
    public void setStationNames(String departure, String arrival) {
        this.tvHeaderDepartureStation.setText(departure);
        this.tvHeaderArrivalStation.setText(arrival);
    }

    @OnTouch({R.id.hsv_arrival_station_name, R.id.hsv_departure_station_name})
    public boolean openSearchActivity(MotionEvent event) {

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(DateTime.class, new DateTimeAdapter())
                .registerTypeAdapterFactory(new PostProcessingEnabler())
                .create();

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            Intent intent = new Intent(JourneyResultsActivity.this, JourneySearchActivity.class);
            intent.putExtra(I_STATIONS, gson.toJson(presenter.getPreferredJourney()));
            intent.putExtra(I_TIME, presenter.getTimeOfSearch().getMillis());
            intent.putExtra(I_FROM_RESULTS, true);
            JourneyResultsActivity.this.startActivityForResult(intent, 1);
        }
        return false;
    }

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (ignoreBeingProAndShowAds) {
            loadAds();
        } else {
            if (CustomIABHelper.isOrderOk(result, inv)) {
                Ads.removeAds(rlBannerPlaceholder, adView2);
            } else {
                loadAds();
            }
        }

//        btnRefresh.setLayoutParams(createParamsForBottomView(getViewContext()));
    }

    private void loadAds() {
        Ads.initializeAds(getViewContext(), rlBannerPlaceholder, adView2, analyticsHelper, SCREEN_JOURNEY_RESULTS);
        BOTTOM_MARGIN_ACTUAL = BOTTOM_MARGIN_WITH_ADS;
    }
} public class JourneySearchActivity extends AppCompatActivity implements JourneySearchContract.View {

    JourneySearchContract.Presenter presenter;
    AnalyticsHelper                 analyticsHelper;

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.rl_departure)
    RelativeLayout       rlDeparture;
    @BindView(R.id.tv_departure)
    TextView             tvDeparture;
    @BindView(R.id.rl_arrival)
    RelativeLayout       rlArrival;
    @BindView(R.id.tv_arrival)
    TextView             tvArrival;
    @BindView(R.id.btn_swap_station_names)
    ImageButton          btnSwapStationNames;
    @BindView(R.id.tv_minus_one_hour)
    TextView             tvMinusOneHour;
    @BindView(R.id.tv_plus_one_hour)
    TextView             tvPlusOneHour;
    @BindView(R.id.tv_time)
    TextView             tvTime;
    @BindView(R.id.tv_minus_one_day)
    TextView             tvMinusOneDay;
    @BindView(R.id.tv_plus_one_day)
    TextView             tvPlusOneDay;
    @BindView(R.id.tv_date)
    TextView             tvDate;
    @BindView(R.id.btn_add_new_favourite)
    FloatingActionButton btnHeaderToggleFavorite;
    @BindView(R.id.btn_search_journey)
    Button               btnSearchJourney;
    @BindView(R.id.btn_search_train)
    Button               btnSearchTrain;
    @BindView(R.id.et_train_number)
    AppCompatEditText    etTrainNumber;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_journey_search);
        ButterKnife.bind(this);
        analyticsHelper = AnalyticsHelper.getInstance(getViewContext());
        presenter = new JourneySearchPresenter(this);

        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        if (savedInstanceState != null) getIntent().putExtras(savedInstanceState);

        this.tvDeparture.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);
        this.tvArrival.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);
        this.tvTime.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);
        this.tvDate.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);

        this.rlDeparture.setOnClickListener(v -> {
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SELECT_DEPARTURE);
            onStationNameTextViewClick(I_CODE_DEPARTURE);
        });
        this.rlArrival.setOnClickListener(v -> {
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SELECT_ARRIVAL);
            onStationNameTextViewClick(I_CODE_ARRIVAL);
        });

        btnSwapStationNames.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SWAP_STATIONS_FROM_SEARCH);
            presenter.onSwapButtonClick(tvDeparture.getText().toString(), tvArrival.getText().toString());
        });

        btnHeaderToggleFavorite.setOnClickListener(v -> presenter.onFavouriteButtonClick());

        // TIME PANEL
        tvTime.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_TIME_CLICK);
            onTimeClick();
        });
        tvMinusOneHour.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_TIME_MINUS);
            presenter.onTimeChanged(-1);
        });
        tvPlusOneHour.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_TIME_PLUS);
            presenter.onTimeChanged(1);
        });
        presenter.onTimeChanged(0);
        tvDate.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_DATE_CLICK);
            onDateClick();
        });
        tvMinusOneDay.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_DATE_MINUS);
            presenter.onDateChanged(-1);
        });
        tvPlusOneDay.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.MEDIUM);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_DATE_PLUS);
            presenter.onDateChanged(1);
        });
        presenter.onDateChanged(0);

        btnSearchJourney.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.LIGHT);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SEARCH_JOURNEY_FROM_SEARCH);
            presenter.onJourneySearchButtonClick(tvDeparture.getText().toString(), tvArrival.getText().toString());
        });

        btnSearchTrain.setOnClickListener(v -> {
            AnimationUtils.animOnPress(v, AnimationUtils.ANIM_TYPE.LIGHT);
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SEARCH_TRAIN_FROM_SEARCH);
            presenter.onTrainSearchButtonClick(etTrainNumber.getText().toString());
        });


    }

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

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(presenter.getState(outState));
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.setState(getIntent().getExtras());
    }

    @Override
    protected void onDestroy() {
        presenter.unsubscribe();
        super.onDestroy();
    }

    @Override
    public void showSnackbar(String message, ENUM_SNACKBAR_ACTIONS intent, int duration) {
        Log.w(message);
        Snackbar snackbar = Snackbar
                .make(this.rlDeparture, message, Snackbar.LENGTH_LONG);
        ((TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text)).setTextColor(ContextCompat.getColor(this, R.color.txt_white));
        switch (intent) {
            case NONE:
                break;
            case SELECT_DEPARTURE:
                analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ERROR_NOT_FOUND_STATION);
                snackbar.setAction("Seleziona", view -> onStationNameTextViewClick(I_CODE_DEPARTURE))
                        .setActionTextColor(ContextCompat.getColor(this, R.color.btn_cyan));
                break;
            case SELECT_ARRIVAL:
                analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ERROR_NOT_FOUND_STATION);
                snackbar.setAction("Seleziona", view -> onStationNameTextViewClick(I_CODE_ARRIVAL))
                        .setActionTextColor(ContextCompat.getColor(this, R.color.btn_cyan));
                break;
            default:
                break;
        }
        snackbar.show();
    }

    @Override
    public Context getViewContext() {
        return JourneySearchActivity.this;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK && data != null) {
            String stationName = data.getStringExtra("stationName");
            Log.d("onActivityResult: ", stationName);
            if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean(I_FROM_RESULTS, false)) {
                getIntent().putExtra(I_IS_TEMP, true);
            }
            if (requestCode == I_CODE_DEPARTURE) {
                presenter.onDepartureStationNameChanged(stationName);
//                this.tvDeparture.setText(stationName);
            } else if (requestCode == I_CODE_ARRIVAL) {
                presenter.onArrivalStationNameChanged(stationName);
//                this.tvArrival.setText(stationName);
            }
        }
    }

    @Override
    public void setStationNames(PreferredStation departureStationName, PreferredStation arrivalStationName) {
        if (departureStationName != null)
            this.tvDeparture.setText(departureStationName.getNameLong());
        if (arrivalStationName != null) this.tvArrival.setText(arrivalStationName.getNameLong());
    }

    @Override
    public void setStationNames(String departureStationName, String arrivalStationName) {
        if (departureStationName != null) this.tvDeparture.setText(departureStationName);
        if (arrivalStationName != null) this.tvArrival.setText(arrivalStationName);
    }

    @Override
    public void setDepartureStationNames(String departureStationName) {
        if (departureStationName != null) this.tvDeparture.setText(departureStationName);
    }

    @Override
    public void setArrivalStationNames(String arrivalStationName) {
        if (arrivalStationName != null) this.tvArrival.setText(arrivalStationName);
    }



    @Override
    public void setTime(String time) {
        tvTime.setText(time);
    }

    @Override
    public void setDate(String date) {
        tvDate.setText(date);
    }


    @Override
    public void onValidJourneySearchParameters() {
        Intent myIntent = new Intent(JourneySearchActivity.this, JourneyResultsActivity.class);
        myIntent.putExtras(presenter.getState(getIntent().getExtras()));
        if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean(I_FROM_RESULTS, false)) {
            setResult(RESULT_OK, myIntent);
            finish();
        } else {
            startActivity(myIntent);
        }
    }

    @Override
    public void onValidTrainSearchParameters() {
        Intent myIntent = new Intent(JourneySearchActivity.this, TrainDetailsActivity.class);
        myIntent.putExtras(presenter.getTrainSearchState(null));
        if (getIntent().getExtras() != null && getIntent().getExtras().getBoolean(I_FROM_RESULTS, false)) {
            setResult(RESULT_OK, myIntent);
            finish();
        } else {
            startActivity(myIntent);
        }
    }

    @Override
    public void onInvalidSearchParameters() {
    }

    private void onTimeClick() {
        TimePickerDialog mTimePicker;
        //noinspection CodeBlock2Expr
        mTimePicker = new TimePickerDialog(this, (timePicker, selectedHour, selectedMinute) -> {
            presenter.onTimeChanged(selectedHour, selectedMinute);
        }, presenter.getSearchDateTime().getHourOfDay(), presenter.getSearchDateTime().getMinuteOfHour(), true);
        mTimePicker.show();
    }

    private void onDateClick() {
        DatePickerDialog mTimePicker;
        //noinspection CodeBlock2Expr
        mTimePicker = new DatePickerDialog(this, (timePicker, selectedYear, selectedMonth, selectedDayOfMonth) -> {
            presenter.onDateChanged(selectedYear, selectedMonth + 1, selectedDayOfMonth);
        }, presenter.getSearchDateTime().getYear(), presenter.getSearchDateTime().getMonthOfYear() - 1, presenter.getSearchDateTime().getDayOfMonth());
        mTimePicker.show();
    }

    private void onStationNameTextViewClick(int code) {
        Intent myIntent = new Intent(JourneySearchActivity.this, StationSearchActivity.class);
        startActivityForResult(myIntent, code);
    }

    @Override
    public void setFavouriteButtonStatus(boolean isPreferred) {
        if (isPreferred) {
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_SET_FAVOURITE_FROM_SEARCH);
            this.btnHeaderToggleFavorite.setImageDrawable(ContextCompat.getDrawable(getViewContext(), R.drawable.ic_star_black));
            AnimationUtils.animOnPress(this.btnHeaderToggleFavorite, AnimationUtils.ANIM_TYPE.MEDIUM);
        } else {
            analyticsHelper.logScreenEvent(SCREEN_SEARCH_JOURNEY, ACTION_REMOVE_FAVOURITE_FROM_SEARCH);
            this.btnHeaderToggleFavorite.setImageDrawable(ContextCompat.getDrawable(getViewContext(), R.drawable.ic_star_border));
        }
    }
} public class DateTimeAdapter implements JsonDeserializer<DateTime>, JsonSerializer<DateTime> {
    @Override
    public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        // Timestamp format
        // "2012-06-11T20:06+00200" format
        return DateTime.parse(json.getAsString());
    }

    @Override
    public JsonElement serialize(DateTime time, Type typeOfT, JsonSerializationContext context) {
        return new JsonPrimitive(time.toString("yyyy-MM-dd'T'HH:mmZ"));
    }
} public class PostProcessingEnabler implements TypeAdapterFactory {

    public interface PostProcessable {
        void postProcess();
    }

    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);

        return new TypeAdapter<T>() {
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            public T read(JsonReader in) throws IOException {
                T obj = delegate.read(in);
                if (obj instanceof PostProcessable) {
                    ((PostProcessable) obj).postProcess();
                }
                return obj;
            }
        };
    }
} public class ClickActionHelper {
    public static void startActivity(String className, Bundle extras, Context context){
        Class cls;
        if (className != null && extras != null && context != null) {
            try {
                cls = Class.forName(className);
                Intent i = new Intent(context, cls);
                i.putExtras(extras);
                context.startActivity(i);
            } catch (ClassNotFoundException | NullPointerException e) {
                Log.d("startActivity: class not found or something was null");
                //means you made a wrong input in firebase console
            }
        }
    }
} public class NotificationService extends Service {
    public static final String ACTION_START_NOTIFICATION                        = "com.jaus.albertogiunta.justintrain_oraripendolaritrenitalia.action.START_NOTIFICATION";
    public static final String ACTION_STOP_NOTIFICATION                         = "com.jaus.albertogiunta.justintrain_oraripendolaritrenitalia.action.STOP_NOTIFICATION";
    public static final String ACTION_UPDATE_NOTIFICATION                       = "com.jaus.albertogiunta.justintrain_oraripendolaritrenitalia.action.UPDATE_NOTIFICATION";
    public static final String EXTRA_NOTIFICATION_DATA                          = "com.jaus.albertogiunta.justintrain_oraripendolaritrenitalia.extra.NOTIFICATION_DATA";
    public static final String EXTRA_NOTIFICATION_DEPARTURE                     = "departureData";
    public static final String EXTRA_NOTIFICATION_ARRIVAL                       = "arrivalData";
    public static final String EXTRA_NOTIFICATION_SOLUTION                      = "solutionData";
    public static final String EXTRA_NOTIFICATION_INDEXOUTOFJOURNEYTOBENOTIFIED = "indexoutofjourneytobenotifiedData";
    public static final String EXTRA_NOTIFICATION_SHOULDPRIORITYBEHIGH          = "shouldprioritybehighData";
    public static final String NOTIFICATION_ERROR_EVENT                         = "EVENT_NOTIFICATION_ERROR";
    public static final String NOTIFICATION_ERROR_MESSAGE                       = "NOTIFICATION_ERROR_MESSAGE";
    public static final String NOTIFICATION_ERROR_MESSAGE_TRAIN_NOT_FOUND       = "Impossibile settare la notifica. \nIl treno potrebbe aver cambiato numero od orario.";
    public static final String NOTIFICATION_ERROR_MESSAGE_STATION_NOT_FOUND     = "Impossibile settare la notifica a causa di un problema con le stazioni di partenza o arrivo.";
    public static final String NOTIFICATION_ERROR_MESSAGE_TRAIN_ARRIVED_TO_DEST = "Il treno è già arrivato alla destinazione da te selezionata";

    AnalyticsHelper analyticsHelper;

    BroadcastReceiver mReceiver;

    Gson gson = new GsonBuilder()
            .registerTypeAdapter(DateTime.class, new DateTimeAdapter())
            .create();


    @Override
    public void onCreate() {
        super.onCreate();
        analyticsHelper = AnalyticsHelper.getInstance(getBaseContext());
        mReceiver = new ScreenOnReceiver();
        if (ProPreferences.isLiveNotificationEnabled(NotificationService.this)) {
            registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
        }
    }

    @Override
    public void onDestroy() {
        Log.d("onDestroy: DESTROYED SERVICE, UNSUBBED FROM RECEIVER");
        try {
            if (mReceiver != null) {
                unregisterReceiver(mReceiver);
                mReceiver = null;
            }
        } catch (Exception e) {
            Log.e("onDestroy: " + e.getMessage());
        }
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void startActionStartNotification(PreferredStation journeyDepartureStation, PreferredStation journeyArrivalStation, Journey.Solution sol, Integer indexOfJourneyToBeNotified, Boolean shouldPriorityBeHigh) {
        String  trainId;
        String  journeyDepartureStationId = journeyDepartureStation.getStationShortId();
        String  journeyArrivalStationId   = journeyArrivalStation.getStationShortId();
        boolean justUpdate                = indexOfJourneyToBeNotified != null;

        if (sol.hasChanges()) {
            if (indexOfJourneyToBeNotified == null) {
                indexOfJourneyToBeNotified = 0;
                for (Journey.Solution.Change c : sol.getChangesList()) {
                    if ((c.getTimeDifference() != null
                            && c.getTimeDifference() > 0 && c.getArrivalTime().plus(c.getTimeDifference()).isAfter(DateTime.now()))
                            || (c.getTimeDifference() == null && c.getArrivalTime().isAfter(DateTime.now()))) {
                        indexOfJourneyToBeNotified = sol.getChangesList().indexOf(c);
                        break;
                    }
                }
            }

            trainId = sol.getChangesList().get(indexOfJourneyToBeNotified).getTrainId();
            try {
                journeyDepartureStationId = DatabaseHelper.getStation4DatabaseObject(sol.getChangesList().get(indexOfJourneyToBeNotified).getDepartureStationName()).getStationShortId();
                journeyArrivalStationId = DatabaseHelper.getStation4DatabaseObject(sol.getChangesList().get(indexOfJourneyToBeNotified).getArrivalStationName()).getStationShortId();

                getData(journeyDepartureStationId,
                        journeyArrivalStationId,
                        trainId, this, justUpdate, shouldPriorityBeHigh);
            } catch (Exception e) {
                FirebaseCrash.report(new Exception("Requested notification and Station was not found. SOLUTION IS " + sol.toString() + " AND REQUESTED STATIONSs ARE " + sol.getChangesList().get(indexOfJourneyToBeNotified).getDepartureStationName() + " " + sol.getChangesList().get(indexOfJourneyToBeNotified).getArrivalStationName()));
                Intent notificationErrorIntent = new Intent(NOTIFICATION_ERROR_EVENT);
                notificationErrorIntent.putExtra(NOTIFICATION_ERROR_MESSAGE, NOTIFICATION_ERROR_MESSAGE_STATION_NOT_FOUND);
                LocalBroadcastManager.getInstance(this.getApplicationContext()).sendBroadcast(notificationErrorIntent);
            }
        } else {
            trainId = sol.getTrainId();

            getData(journeyDepartureStationId,
                    journeyArrivalStationId,
                    trainId, getApplicationContext(), justUpdate, shouldPriorityBeHigh);
        }
    }

    private void getData(String journeyDepartureStationId, String journeyArrivalStationId, String trainId, Context context, boolean justUpdate, boolean shouldPriorityBeHigh) {
        Log.d("getData:", journeyDepartureStationId, journeyArrivalStationId, trainId, justUpdate, shouldPriorityBeHigh);
        Intent  notificationErrorIntent           = new Intent(NOTIFICATION_ERROR_EVENT);
        boolean isPro                             = ProPreferences.isPro(context);
        boolean isCompactNotificationEnabled      = isPro && ProPreferences.isCompactNotificationEnabled(context);
        boolean isAutoRemovingNotificationEnabled = isPro && ProPreferences.isAutoRemoveNotificationEnabled(context);

        APINetworkingFactory.createRetrofitService(JourneyService.class)
                .getDelay(journeyDepartureStationId, journeyArrivalStationId, trainId)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(trainHeader -> {
                    if (justUpdate) {
                        Log.d("onNext: just updating for a specific train from train details screen");
                        startForeground(1337, TrainNotification.notify(context, trainHeader, true, shouldPriorityBeHigh, isCompactNotificationEnabled));
                        return;
                    }
                    if (trainHeader.getJourneyArrivalStationVisited()) {
                        Journey.Solution s = NotificationPreferences.getNotificationSolution(context);
                        if (s.hasChanges()) {
                            List<Journey.Solution.Change> l = s.getChangesList();
                            for (int i = 0; i < l.size(); i++) {
                                if (trainHeader.getTrainId().equalsIgnoreCase(l.get(i).getTrainId())) {
                                    if (i < l.size() - 1) {
                                        Log.d("onNext: going to get the next one");
                                        String journeyDepartureStationId2;
                                        String journeyArrivalStationId2;
                                        try {
                                            journeyDepartureStationId2 = DatabaseHelper.getStation4DatabaseObject(l.get(i + 1).getDepartureStationName()).getStationShortId();
                                            journeyArrivalStationId2 = DatabaseHelper.getStation4DatabaseObject(l.get(i + 1).getArrivalStationName()).getStationShortId();
                                            getData(journeyDepartureStationId2, journeyArrivalStationId2, l.get(i + 1).getTrainId(), context, false, true);
                                        } catch (Exception e) {
                                            FirebaseCrash.report(new Exception("Requested new train in notification and Station was not found. SOLUTION IS " + s.toString() + " AND REQUESTED STATIONSs ARE " + l.get(i + 1).getDepartureStationName() + " " + l.get(i + 1).getArrivalStationName()));
                                            notificationErrorIntent.putExtra(NOTIFICATION_ERROR_MESSAGE, NOTIFICATION_ERROR_MESSAGE_STATION_NOT_FOUND);
                                            LocalBroadcastManager.getInstance(context).sendBroadcast(notificationErrorIntent);
                                        }
                                        return;
                                    } else {
                                        Log.d("onNext: displaying the last train of the solution (which has arrived)");
                                        if (isAutoRemovingNotificationEnabled) {
                                            notificationErrorIntent.putExtra(NOTIFICATION_ERROR_MESSAGE, NOTIFICATION_ERROR_MESSAGE_TRAIN_ARRIVED_TO_DEST);
                                            LocalBroadcastManager.getInstance(context).sendBroadcast(notificationErrorIntent);
                                            cancelNotification(context);
                                        } else {
                                            startForeground(1337, TrainNotification.notify(context, trainHeader, true, shouldPriorityBeHigh, isCompactNotificationEnabled));
                                        }
                                    }
                                }
                            }
                        } else {
                            Log.d("onNext: displaying a single train solution which has arrived");
                            if (isAutoRemovingNotificationEnabled) {
                                notificationErrorIntent.putExtra(NOTIFICATION_ERROR_MESSAGE, NOTIFICATION_ERROR_MESSAGE_TRAIN_ARRIVED_TO_DEST);
                                LocalBroadcastManager.getInstance(context).sendBroadcast(notificationErrorIntent);
                                cancelNotification(context);
                            } else {
                                startForeground(1337, TrainNotification.notify(context, trainHeader, true, shouldPriorityBeHigh, isCompactNotificationEnabled));
                            }
                        }
                    } else {
                        Log.d("onNext: displaying a train of a solution which hasn't arrived yet");
                        startForeground(1337, TrainNotification.notify(context, trainHeader, true, shouldPriorityBeHigh, isCompactNotificationEnabled));
                    }
                }, throwable -> {
                    Log.d(throwable.getMessage());
                    FirebaseCrash.report(new Exception("NOTIFICATION ERROR for departure " + journeyDepartureStationId + " arrival " + journeyArrivalStationId + " train " + trainId));
                    notificationErrorIntent.putExtra(NOTIFICATION_ERROR_MESSAGE, NOTIFICATION_ERROR_MESSAGE_TRAIN_NOT_FOUND);
                    LocalBroadcastManager.getInstance(context).sendBroadcast(notificationErrorIntent);
                });
    }

    private void cancelNotification(Context context) {
        Intent i = new Intent(context, NotificationService.class);
        i.setAction(ACTION_STOP_NOTIFICATION);
        context.startService(i);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            final String action = intent.getAction();
            Log.d(action);
            if (ACTION_START_NOTIFICATION.equals(action)) {
                PreferredStation departureStation              = gson.fromJson(intent.getStringExtra(EXTRA_NOTIFICATION_DEPARTURE), PreferredStation.class);
                PreferredStation arrivalStation                = gson.fromJson(intent.getStringExtra(EXTRA_NOTIFICATION_ARRIVAL), PreferredStation.class);
                Journey.Solution solution                      = gson.fromJson(intent.getStringExtra(EXTRA_NOTIFICATION_SOLUTION), Journey.Solution.class);
                Integer          indexoutofjourneytobenotified = intent.getIntExtra(EXTRA_NOTIFICATION_INDEXOUTOFJOURNEYTOBENOTIFIED, -1);

                indexoutofjourneytobenotified = indexoutofjourneytobenotified == -1 ? null : indexoutofjourneytobenotified;

                startActionStartNotification(departureStation, arrivalStation, solution, indexoutofjourneytobenotified, true);

            } else if (ACTION_STOP_NOTIFICATION.equals(action)) {
                analyticsHelper.logScreenEvent(SCREEN_NOTIFICATION, ACTION_REMOVE_NOTIFICATION);
                NotificationPreferences.removeNotificationData(getBaseContext());
                TrainNotification.cancel(this);
                this.stopSelf();
            } else if (ACTION_UPDATE_NOTIFICATION.equals(action)) {
                analyticsHelper.logScreenEvent(SCREEN_NOTIFICATION, ACTION_REFRESH_NOTIFICAITON);
                if (intent.getStringExtra(EXTRA_NOTIFICATION_DATA) != null) {
                    TrainHeader trainHeader = gson.fromJson(intent.getStringExtra(EXTRA_NOTIFICATION_DATA), TrainHeader.class);
                    getData(trainHeader.getJourneyDepartureStationId(),
                            trainHeader.getJourneyArrivalStationId(),
                            trainHeader.getTrainId(), getApplicationContext(), false, false);
                } else {
                    Journey.Solution s = gson.fromJson(NotificationPreferences.getNotificationSolutionString(getBaseContext()), Journey.Solution.class);
                    if (s != null) {
                        if (s.hasChanges()) {
                            startActionStartNotification(new PreferredStation(s.getDepartureStationShortId(), s.getDepartureStationId(), s.getDepartureStationName(), s.getDepartureStationName()),
                                    new PreferredStation(s.getArrivalStationShortId(), s.getArrivalStationId(), s.getArrivalStationName(), s.getArrivalStationName()),
                                    s, null, false);
                        } else {
                            getData(s.getDepartureStationShortId(),
                                    s.getArrivalStationShortId(),
                                    s.getTrainId(), getApplicationContext(), false, false);
                        }
                    }
                }
            }
        }
        return START_STICKY;
    }
} public class IntroActivity extends AppIntro2 {

    AnalyticsHelper analyticsHelper;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        analyticsHelper = AnalyticsHelper.getInstance(this);
        addSlide(AppIntro2Fragment.newInstance("Percorri sempre la stessa tratta?",
                "Aggiungi le tue tratte e i tuoi treni abituali ai preferiti, per averli sempre a portata di mano!", R.drawable.tut1, Color.parseColor("#717C8D")));
        addSlide(AppIntro2Fragment.newInstance("Andata? Ritorno?", "No problem. Trascina la tratta preferita verso dove vuoi andare. Proprio come ti verrebbe naturale fare.", R.drawable.tut2, Color.parseColor("#717C8D")));
        addSlide(AppIntro2Fragment.newInstance("Treno poco affidabile?", "Tienilo sempre sotto controllo tramite la notifica!", R.drawable.tut3, Color.parseColor("#717C8D")));
    }

    @Override
    public void onDonePressed(Fragment currentFragment) {
        super.onDonePressed(currentFragment);
        analyticsHelper.logScreenEvent(SCREEN_TUTORIAL, ACTION_TUTORIAL_DONE);
        finish();
    }

    @Override
    public void onSkipPressed(Fragment currentFragment) {
        super.onSkipPressed(currentFragment);
        analyticsHelper.logScreenEvent(SCREEN_TUTORIAL, ACTION_TUTORIAL_SKIP);
        finish();
    }

    @Override
    public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
        super.onSlideChanged(oldFragment, newFragment);
        analyticsHelper.logScreenEvent(SCREEN_TUTORIAL, ACTION_TUTORIAL_NEXT);
    }


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

    public static int BOTTOM_MARGIN_WITH_ADS    = 60;
    public static int BOTTOM_MARGIN_WITHOUT_ADS = 16;
    public static int BOTTOM_MARGIN_ACTUAL      = 16;

    public static boolean ignoreBeingProAndShowAds = BuildConfig.DEBUG && false;

    public static void initializeAds(Context context, NativeExpressAdView adView) {
        MobileAds.initialize(context, "ca-app-pub-8963908741443055~4285788324");
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)        // All emulators
                .addTestDevice("EF67DDDDDDD0896B1B02876D927AC309")  // An example device ID
                .build();
        Log.d("initializeAds: ", adView.getAdUnitId());
        adView.loadAd(adRequest);
    }

    public static void initializeAds(Context context, View bannerPlaceholder, NativeExpressAdView adView, AnalyticsHelper analyticsHelper, String screenName) {
        initializeAds(context, adView);

        adView.setAdListener(new AdListener() {
            @Override
            public void onAdLoaded() {
                apply(bannerPlaceholder, GONE);
                apply(adView, VISIBLE);
            }

            @Override
            public void onAdFailedToLoad(int i) {
                analyticsHelper.logScreenEvent(screenName, AD_FAILED_TO_LOAD);
            }

            @Override
            public void onAdClicked() {
                analyticsHelper.logScreenEvent(screenName, AD_CLICKED);
            }
        });
    }

    public static void removeAds(View bannerPlaceholder, NativeExpressAdView adView) {
        apply(bannerPlaceholder, GONE);
        apply(adView, GONE);
        adView.setEnabled(false);

        ViewGroup parent = (ViewGroup) adView.getParent();
        if (parent != null) {
            parent.removeView(adView);
            parent.invalidate();

            parent = (ViewGroup) bannerPlaceholder.getParent();
            parent.removeView(bannerPlaceholder);
            parent.invalidate();
        }
    }

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

    public static void initializeAds(Context context, AdView adView) {
        MobileAds.initialize(context, "ca-app-pub-8963908741443055~4285788324");
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)        // All emulators
                .addTestDevice("EF67DDDDDDD0896B1B02876D927AC309")  // An example device ID
                .build();
        Log.d("initializeAds: ", adView.getAdUnitId());
        adView.loadAd(adRequest);
    }

    public static void initializeAds(Context context, View bannerPlaceholder, AdView adView, AnalyticsHelper analyticsHelper, String screenName) {
        initializeAds(context, adView);

        adView.setAdListener(new AdListener() {
            @Override
            public void onAdLoaded() {
                apply(bannerPlaceholder, GONE);
                apply(adView, VISIBLE);
            }

            @Override
            public void onAdFailedToLoad(int i) {
                analyticsHelper.logScreenEvent(screenName, AD_FAILED_TO_LOAD);
            }

            @Override
            public void onAdClicked() {
                analyticsHelper.logScreenEvent(screenName, AD_CLICKED);
            }
        });
    }

    public static void removeAds(View bannerPlaceholder, AdView adView) {

        boolean ignoreBeingProAndShowAds = false;
        if (BuildConfig.DEBUG && ignoreBeingProAndShowAds) {
            return;
        }

        if (bannerPlaceholder != null) apply(bannerPlaceholder, GONE);
        BOTTOM_MARGIN_ACTUAL = BOTTOM_MARGIN_WITHOUT_ADS;
        apply(adView, GONE);
        adView.setEnabled(false);

        if (bannerPlaceholder != null) {
            ViewGroup parent = (ViewGroup) adView.getParent();
            if (parent != null) {
                parent.removeView(adView);
                parent.invalidate();

                parent = (ViewGroup) bannerPlaceholder.getParent();
                parent.removeView(bannerPlaceholder);
                parent.invalidate();
            }
        }
    }

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

    public static CoordinatorLayout.LayoutParams createParamsForBottomView(Context context) {
        int defaultMargin = ViewsUtils.convertDPtoPX(context, 16);
        int bottomMargin  = ViewsUtils.convertDPtoPX(context, BOTTOM_MARGIN_ACTUAL);

        CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MATCH_PARENT, ViewsUtils.convertDPtoPX(context, 50));
        params.topMargin = defaultMargin;
        params.leftMargin = defaultMargin;
        params.rightMargin = defaultMargin;
        params.bottomMargin = bottomMargin;
        params.gravity = Gravity.BOTTOM;

        return params;
    }
} public class AnimationUtils {

    public enum ANIM_TYPE {
        LIGHT(1.05f), MEDIUM(1.2f), HEAVY(1.2f);

        private float scalingOffset;

        ANIM_TYPE(float scalingOffset) {
            this.scalingOffset = scalingOffset;
        }

        public float getScalingOffset() {
            return scalingOffset;
        }
    }

    private final static float SCALING_OFFSET = 1.2f;

    public static void animOnPress(View v, ANIM_TYPE animType) {
        v.setScaleX(animType.getScalingOffset());
        v.setScaleY(animType.getScalingOffset());
        v.animate().setInterpolator(new OvershootInterpolator()).scaleX(1).scaleY(1).setDuration(250);
    }

    public static void onCompare(View v) {
        v.setScaleX(0f);
        v.setScaleY(0f);
        v.animate().setInterpolator(new OvershootInterpolator()).scaleX(1).scaleY(1).setDuration(150);
    }

} public abstract class HideShowScrollListener extends RecyclerView.OnScrollListener {
    private static final int HIDE_THRESHOLD = 20;
    private int scrolledDistance = 0;
    private boolean controlsVisible = true;

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
            onHide();
            controlsVisible = false;
            scrolledDistance = 0;
        } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
            onShow();
            controlsVisible = true;
            scrolledDistance = 0;
        }

        if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
            scrolledDistance += dy;
        }
    }

    public abstract void onHide();

    public abstract void onShow();

} public class ViewsUtils {

    public static final ButterKnife.Action<View> VISIBLE = (view, index) -> view.setVisibility(View.VISIBLE);

    public static final ButterKnife.Action<View> GONE = (view, index) -> view.setVisibility(View.GONE);

    public static final ButterKnife.Action<View> INVISIBLE = (view, index) -> view.setVisibility(View.INVISIBLE);



    public enum COLORS {
        WHITE, YELLOW, ORANGE, RED, GREEN, BLACK, GREY_LIGHTER, BLUE
    }

    public static int getTimeDifferenceColor(Context context, COLORS color) {
        switch (color) {
            case WHITE:
                return getColor(context, R.color.txt_white);
            case YELLOW:
                return getColor(context, R.color.txt_yellow);
            case ORANGE:
                return getColor(context, R.color.txt_orange);
            case RED:
                return getColor(context, R.color.txt_red);
            case GREEN:
                return getColor(context, R.color.txt_green);
            case BLACK:
                return getColor(context, R.color.txt_dark);
            case GREY_LIGHTER:
                return getColor(context, R.color.txt_grey_lighter);
            case BLUE:
                return getColor(context, R.color.btn_dark_cyan);
            default:
                return getColor(context, R.color.txt_dark);
        }
    }

    public static int getTimeDifferenceColor(Context context, int timeDifference) {
        int color = getColor(context, R.color.txt_dark);
        if (timeDifference == 0) {
            color = getColor(context, R.color.ontime);
        } else if (timeDifference > 0) {
            color = getColor(context, R.color.late1);
        } else if (timeDifference < 0) {
            color = getColor(context, R.color.early1);
        }
        return color;
    }

    public static int getColor(Context context, int resource) {
        return ContextCompat.getColor(context, resource);
    }

    public static int convertDPtoPX(Context context, float dp) {
        float d = context.getResources().getDisplayMetrics().density;
        return (int) (dp * d); // margin in pixels
    }

} public class CONST_VERSION_TAG {

    public static final int V100 = 37;
    public static final int V125 = 60;

} public enum ENUM_SNACKBAR_ACTIONS {

    NONE, REFRESH, SELECT_DEPARTURE, SELECT_ARRIVAL, SELECT_TRAIN_NUMBER, ACCEPT

} public class AnalyticsHelper {

    /*

    adb shell setprop log.tag.FA VERBOSE
    adb shell setprop log.tag.FA-SVC VERBOSE
    adb logcat -v time -s FA FA-SVC

     */

    private FirebaseAnalytics firebase = null;

    private static AnalyticsHelper analyticsHelper;

    public static AnalyticsHelper getInstance(Context context) {
        if (analyticsHelper == null) {
            analyticsHelper = new AnalyticsHelper(context);
        }
        return analyticsHelper;
    }

    private AnalyticsHelper(Context context) {
        firebase = FirebaseAnalytics.getInstance(context);
        firebase.setMinimumSessionDuration(3000);
//        firebase.setUserId();
//        firebase.setUserProperty();
    }

    public void logScreenEvent(String screen, String action) {
        Bundle bundle = new Bundle();
        bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, screen);
        bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, action);
        bundle.putString(FirebaseAnalytics.Param.ITEM_ID, action);
        firebase.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
    }
} 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 ServerConfigsHelper {

    public static String getAPIEndpoint() {
        return FirebaseRemoteConfig.getInstance().getString(FIREBASE_SERVER_CONFIG);
    }

    public static void removeAPIEndpoint(Context context) {
        SharedPreferencesHelper.removeSharedPreferenceObject(context, SP_SP_SERVER_CONFIG);
    }

    public static int getProUsersNumberFromSharedPreferences(Context context) {
        return SharedPreferencesHelper.getSharedPreferenceInt(context, SP_SP_PRO_USERS_NUMBER, 2);
    }

    public static void setProUsersInSharedPreferences(Context context, int number) {
        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_PRO_USERS_NUMBER, number);
    }

} public class ShortcutHelper {

    public static void updateShortcuts(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
            ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
                shortcutManager.removeAllDynamicShortcuts();
                List<PreferredJourney> l;
            if ((l = PreferredStationsPreferences.getAllPreferredJourneys(context)).size() > 0) {
                    shortcutManager.addDynamicShortcuts(setShortcuts(context, l.get(0)));
                }
        }
    }

    private static List<ShortcutInfo> setShortcuts(Context context, PreferredJourney j) {
        Intent i1 = buildIntent(context, j);
        Intent i2 = buildIntent(context, j.swapStations());
        j.swapStations();

        ShortcutInfo       shortcut1 = buildShortcut(context, i1, j.getStation1().getNameShort(), j.getStation2().getNameShort(), "shortcut_dynamic1");
        ShortcutInfo       shortcut2 = buildShortcut(context, i2, j.getStation2().getNameShort(), j.getStation1().getNameShort(), "shortcut_dynamic2");
        List<ShortcutInfo> l         = new LinkedList<>();
        l.add(shortcut2);
        l.add(shortcut1);
        return l;
    }

    private static Intent buildIntent(Context context, PreferredJourney journey) {
        Intent intent = new Intent(context, JourneyResultsActivity.class);
        intent.putExtras(bundleJourney(journey));
        intent.setAction(JourneyResultsActivity.ACTION);
        return intent;
    }

    private static ShortcutInfo buildShortcut(Context context, Intent i, String name1, String name2, String id) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
            return new ShortcutInfo.Builder(context, id)
                    .setShortLabel(shortenStationName(name1) + " > " + shortenStationName(name2))
                    .setLongLabel(name1 + " > " + name2)
                    .setIcon(Icon.createWithResource(context, R.drawable.ic_arrow_forward))
                    .setIntents(new Intent[]{
                            new Intent(Intent.ACTION_MAIN, Uri.EMPTY, context, FavouriteJourneysActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
                            i,
                    })
                    .build();
        } else {
            return null;
        }
    }

    private static Bundle bundleJourney(PreferredJourney journey) {
        Bundle bundle = new Bundle();

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(DateTime.class, new DateTimeAdapter())
                .registerTypeAdapterFactory(new PostProcessingEnabler())
                .create();

        bundle.putString(I_STATIONS, gson.toJson(journey));
        return bundle;
    }

    private static String shortenStationName(String s) {
        return s.length() > 5 ? s.substring(0, 5).concat("...") : s;
    }
} public class MigrationHelper {

    /*
    nuova installaziione => fai default e sala sp_version a SP_VERSION
                            se il bomber elimina i settings ricomincia da zero e quindi risalva i settings

    vecchia installazione => o sp_version esiste ed è minore rispetto a quella dell'app, allora devi fare la migration
                             oppure sp_version non esiste e allora è il caso da -1 a 0 (perchè in tutti gli altri casi è alla prima installazione)
     */

    public static void migrateIfDue(Context context) {
//        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_VERSION, -1); // use for testing purposes
        int oldSPVersion = SharedPreferencesHelper.getSharedPreferenceInt(context, SP_SP_VERSION, -1);
        if (oldSPVersion == -1 && SP_VERSION == 0) {
            SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_VERSION, SP_VERSION);
            migrationFromNothingToVersionZero(context);
        }
    }

    private static void migrationFromNothingToVersionZero(Context context) {

        // migro il firstStart
        SharedPreferencesHelper.setSharedPreferenceBoolean(context, SP_SP_FIRST_START, SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP0_FIRST_START, true));
        SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_FIRST_START);

        // migro il savedVersion
        SharedPreferencesHelper.setSharedPreferenceInt(context, SP_SP_SAVED_VERSION, SharedPreferencesHelper.getSharedPreferenceInt(context, SP0_SAVED_VERSION, BuildConfig.VERSION_CODE));
        SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_SAVED_VERSION);

        // rimuovo e basta il server config perchè dovrebbe bastare firebase
        SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_SERVER_CONFIG);

        // migro il lightning
        SharedPreferencesHelper.setSharedPreferenceBoolean(context, SP_SETT_LIGHTNING, SharedPreferencesHelper.getSharedPreferenceBoolean(context, SP0_PREF_LIGHTNING, false));
        SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_PREF_LIGHTNING);

        // migro il notificationsolution
        String notificationSolution = SharedPreferencesHelper.getSharedPreferenceString(context, SP0_NOTIFICATION_SOLUTION, "");
        if (!notificationSolution.equalsIgnoreCase("")) {
            SharedPreferencesHelper.setSharedPreferenceString(context, SP_NOTIFICATION_SOLUTION, SharedPreferencesHelper.getSharedPreferenceString(context, SP0_NOTIFICATION_SOLUTION, ""));
            SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_NOTIFICATION_SOLUTION);
        }

        // migro il notificationprefjourney
        String notificationPrefJourney = SharedPreferencesHelper.getSharedPreferenceString(context, SP0_NOTIFICATION_PREF_JOURNEY, "");
        if (!notificationPrefJourney.equalsIgnoreCase("")) {
            SharedPreferencesHelper.setSharedPreferenceString(context, SP_NOTIFICATION_PREF_JOURNEY, SharedPreferencesHelper.getSharedPreferenceString(context, SP0_NOTIFICATION_PREF_JOURNEY, ""));
            SharedPreferencesHelper.removeSharedPreferenceObject(context, SP0_NOTIFICATION_PREF_JOURNEY);
        }

        // migro tutti le preferredJourneys
        List<PreferredJourney> preferredJourneysList = getAllOLDPreferredJourneys(context);
        for (PreferredJourney preferredJourney : preferredJourneysList) {
            PreferredStationsPreferences.setPreferredJourney(context, preferredJourney);
            SharedPreferencesHelper.removeSharedPreferenceObject(context, buildOLDPreferredJourneyId(preferredJourney));
        }
    }

    public static String buildOLDPreferredJourneyId(PreferredJourney preferredJourney) {
        return buildOLDPreferredJourneyId(preferredJourney.getStation1().getStationShortId(), preferredJourney.getStation2().getStationShortId());
    }


    public static String buildOLDPreferredJourneyId(PreferredStation departureStation, PreferredStation arrivalStation) {
        return buildOLDPreferredJourneyId(departureStation.getStationShortId(), arrivalStation.getStationShortId());
    }

    public static String buildOLDPreferredJourneyId(String id1, String id2) {
        int cod1 = Integer.parseInt(id1);
        int cod2 = Integer.parseInt(id2);
        return Math.min(cod1, cod2) + "-" + Math.max(cod1, cod2);
    }

    /**
     * SHOULD ONLY BE USED FOR MIGRATION STUFF
     *
     * @return a list of preferred journeys
     */
    public static List<PreferredJourney> getAllOLDPreferredJourneys(Context context) {
        Gson                   gson = new Gson();
        List<PreferredJourney> list = new LinkedList<>();
        Map<String, ?>         m    = getAll(context);
        for (String el : m.keySet()) {
            if (!el.equals(SP_SP_FIRST_START)
                    && !el.equals(SP_SP_SAVED_VERSION)
                    && !el.equals(SP_SP_SERVER_CONFIG)
                    && !el.equals(SP_NOTIFICATION_PREF_JOURNEY)
                    && !el.equals(SP_NOTIFICATION_SOLUTION)
                    && !el.equals(SP_SP_VERSION)
                    && !el.equals(SP_SETT_LIGHTNING)) {
                Log.d("getAllPreferredJourneys: ", el);
                PreferredJourney temp = gson.fromJson(((String) m.get(el)), PreferredJourney.class);
                if (temp.getStation1() != null && temp.getStation2() != null) list.add(temp);
            }
        }
        Collections.sort(list, (o1, o2) -> {
            if (o1.getTimestamp() < o2.getTimestamp()) {
                return -1;
            } else if (o1.getTimestamp() > o2.getTimestamp()) {
                return 1;
            } else {
                return 0;
            }
        });
        return list;
    }
} 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 class SharedPreferencesHelper {

    private final static String PREF_FILE = "PREF";

    /**
     * Set a string shared preference
     *
     * @param key - Key to set shared preference
     * @param value - Value for the key
     */
    public static void setSharedPreferenceString(Context context, String key, String value) {
        SharedPreferences        settings = context.getSharedPreferences(PREF_FILE, 0);
        SharedPreferences.Editor editor   = settings.edit();
        editor.putString(key, value);
        editor.apply();
    }

    /**
     * Set a integer shared preference
     *
     * @param key - Key to set shared preference
     * @param value - Value for the key
     */
    public static void setSharedPreferenceInt(Context context, String key, int value) {
        SharedPreferences        settings = context.getSharedPreferences(PREF_FILE, 0);
        SharedPreferences.Editor editor   = settings.edit();
        editor.putInt(key, value);
        editor.apply();
    }

    /**
     * Set an object shared preference
     *
     * @param key - Key to set shared preference
     * @param value - Value for the key
     */
    public static void setSharedPreferenceObject(Context context, String key, String value) {
        SharedPreferences        settings = context.getSharedPreferences(PREF_FILE, 0);
        SharedPreferences.Editor editor   = settings.edit();
        editor.putString(key, value);
        editor.apply();
    }

    /**
     * Set a Boolean shared preference
     *
     * @param key - Key to set shared preference
     * @param value - Value for the key
     */
    public static void setSharedPreferenceBoolean(Context context, String key, boolean value) {
        SharedPreferences        settings = context.getSharedPreferences(PREF_FILE, 0);
        SharedPreferences.Editor editor   = settings.edit();
        editor.putBoolean(key, value);
        editor.apply();
    }

    /**
     * Get a string shared preference
     *
     * @param key - Key to look up in shared preferences.
     * @param defValue - Default value to be returned if shared preference isn't found.
     * @return value - String containing value of the shared preference if found.
     */
    public static String getSharedPreferenceString(Context context, String key, String defValue) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        return settings.getString(key, defValue);
    }

    /**
     * Get a integer shared preference
     *
     * @param key - Key to look up in shared preferences.
     * @param defValue - Default value to be returned if shared preference isn't found.
     * @return value - String containing value of the shared preference if found.
     */
    public static int getSharedPreferenceInt(Context context, String key, int defValue) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        int               value    = settings.getInt(key, defValue);

        return value;
    }

    /**
     * Get an object shared preference
     *
     * @param key - Key to set shared preference
     */
    public static String getSharedPreferenceObject(Context context, String key) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        return settings.getString(key, null);
    }

    /**
     * Get a boolean shared preference
     *
     * @param key - Key to look up in shared preferences.
     * @param defValue - Default value to be returned if shared preference isn't found.
     * @return value - String containing value of the shared preference if found.
     */
    public static boolean getSharedPreferenceBoolean(Context context, String key, boolean defValue) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        boolean           value    = settings.getBoolean(key, defValue);

        return value;
    }

    /**
     * Remove an object shared preference
     *
     * @param key - Key to set shared preference
     */
    public static void removeSharedPreferenceObject(Context context, String key) {
        SharedPreferences        settings = context.getSharedPreferences(PREF_FILE, 0);
        SharedPreferences.Editor editor   = settings.edit();
        editor.remove(key);
        editor.apply();
    }

    /**
     * Remove all the shared preferences\
     */
    public static void removeAllSharedPreferences(Context context) {
        for (String key : getAll(context).keySet()) {
            removeSharedPreferenceObject(context, key);
        }
    }

    /**
     * Get all the shared preferences as a map of strings and objects (will need to be casted)
     */
    public static Map<String, ?> getAll(Context context) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        return settings.getAll();
    }

    public static Map<String, ?> getAllMatchingPrefix(Context context, String prefix) {
        SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
        Map<String, ?>    allPrefs = settings.getAll();
        Iterator<String>  iterator = allPrefs.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            if (!Pattern.matches(prefix + ".*", key)) {
                iterator.remove();
            }
        }
        return allPrefs;
    }
} public static int BOTTOM_MARGIN_ACTUAL      = 16; public static int BOTTOM_MARGIN_WITH_ADS    = 60; public static boolean ignoreBeingProAndShowAds = BuildConfig.DEBUG && false; public static final ButterKnife.Action<View> GONE = (view, index) -> view.setVisibility(View.GONE); public static final ButterKnife.Action<View> VISIBLE = (view, index) -> view.setVisibility(View.VISIBLE); public static final String ACTION_AR_FROM_POPUP                  = "Search AR from popup"; public static final String ACTION_NO_SWIPE_BUT_CLICK             = "No swipe but click"; public static final String ACTION_ON_SALE_CLICK                  = "Action on update click"; public static final String ACTION_ON_UPDATE_CLICK                = "Action on update click"; public static final String ACTION_RA_FROM_POPUP                  = "Search RA from popup"; public static final String ACTION_REMOVE_FROM_POPUP              = "Remove journey from popup"; public static final String ACTION_SEARCH_JOURNEY_FROM_FAVOURITES = "Search journey from favourites"; public static final String ACTION_SWIPE_LEFT_TO_RIGHT            = "Swipe left to right"; public static final String ACTION_SWIPE_RIGHT_TO_LEFT            = "Swipe right to left"; public static final String ACTION_TRANSFORM_TO_FAVS              = "Remove journey from popup"; public static final String IAP_SEARCH        = "IAP tap search screen"; public static final String SCREEN_FAVOURITE_JOURNEYS = "FAV"; public static final String SCREEN_JOURNEY_RESULTS    = "RESJ"; public static final String FIREBASE_DISCOUNT_MESSAGE         = "discount_message"; public static final String FIREBASE_IS_DISCOUNT_SET          = "is_discount_set"; public static final String FIREBASE_IS_MAINTENANCE_SET       = "is_maintenance_message_set"; public static final String FIREBASE_IS_STRIKE_SET            = "is_strike_message_set"; public static final String FIREBASE_LATEST_VERSION           = "latest_version"; public static final String FIREBASE_MAINTENANCE              = "maintenance_message"; public static final String FIREBASE_STRIKE                   = "strike_message"; public static final String FIREBASE_UPDATE_MESSAGE           = "update_message"; public static final String FIREBASE_UPGRADE_BTN_HOME_MESSAGE = "upgrade_btn_home_message"; public static final String I_FROM_SWIPE   = "fromSwipe"; public static final String I_STATIONS     = "stations"; public static final String SP_SP_FIRST_START            = "settings>isFirstStart";
import com.google.android.gms.ads.AdView;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.jaus.albertogiunta.justintrain_oraritreni.BuildConfig;
import com.jaus.albertogiunta.justintrain_oraritreni.MyApplication;
import com.jaus.albertogiunta.justintrain_oraritreni.R;
import com.jaus.albertogiunta.justintrain_oraritreni.aboutAndSettings.AboutActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.aboutAndSettings.AboutPageUtils;
import com.jaus.albertogiunta.justintrain_oraritreni.aboutAndSettings.LicenseUpgradeActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.aboutAndSettings.SettingsActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.data.PreferredJourney;
import com.jaus.albertogiunta.justintrain_oraritreni.db.Station;
import com.jaus.albertogiunta.justintrain_oraritreni.journeyResults.JourneyResultsActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.journeySearch.JourneySearchActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.networking.DateTimeAdapter;
import com.jaus.albertogiunta.justintrain_oraritreni.networking.PostProcessingEnabler;
import com.jaus.albertogiunta.justintrain_oraritreni.notification.ClickActionHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.notification.NotificationService;
import com.jaus.albertogiunta.justintrain_oraritreni.tutorial.IntroActivity;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.Ads;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.components.AnimationUtils;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.components.HideShowScrollListener;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.components.ViewsUtils;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_VERSION_TAG;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.ENUM_SNACKBAR_ACTIONS;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.AnalyticsHelper;
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.helpers.ServerConfigsHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.helpers.ShortcutHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.MigrationHelper;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.ProPreferences;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.SettingsPreferences;
import com.jaus.albertogiunta.justintrain_oraritreni.utils.sharedPreferences.SharedPreferencesHelper;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.IFlexible;
import org.joda.time.DateTime;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import trikita.log.Log;
import static butterknife.ButterKnife.apply;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.Ads.BOTTOM_MARGIN_ACTUAL;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.Ads.BOTTOM_MARGIN_WITH_ADS;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.Ads.ignoreBeingProAndShowAds;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.components.ViewsUtils.GONE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.components.ViewsUtils.VISIBLE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_AR_FROM_POPUP;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_NO_SWIPE_BUT_CLICK;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_ON_SALE_CLICK;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_ON_UPDATE_CLICK;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_RA_FROM_POPUP;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_REMOVE_FROM_POPUP;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_SEARCH_JOURNEY_FROM_FAVOURITES;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_SWIPE_LEFT_TO_RIGHT;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_SWIPE_RIGHT_TO_LEFT;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.ACTION_TRANSFORM_TO_FAVS;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.IAP_SEARCH;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SCREEN_FAVOURITE_JOURNEYS;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_ANALYTICS.SCREEN_JOURNEY_RESULTS;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_DISCOUNT_MESSAGE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_IS_DISCOUNT_SET;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_IS_MAINTENANCE_SET;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_IS_STRIKE_SET;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_LATEST_VERSION;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_MAINTENANCE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_STRIKE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_UPDATE_MESSAGE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_FIREBASE.FIREBASE_UPGRADE_BTN_HOME_MESSAGE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_INTENT.I_FROM_SWIPE;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_INTENT.I_STATIONS;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.CONST_SP_V0.SP_SP_FIRST_START;
import static com.jaus.albertogiunta.justintrain_oraritreni.utils.constants.ENUM_SNACKBAR_ACTIONS.NONE;
        adapter.setSwipeEnabled(true);

        btnSearch.setScaleX(0);
        btnSearch.setScaleY(0);
        new Handler().postDelayed(() -> AnimationUtils.onCompare(btnSearch), 500);

        rvFavouriteJourneys.addOnScrollListener(new HideShowScrollListener() {
            @Override
            public void onHide() {
                btnSearch.animate().setInterpolator(new AccelerateDecelerateInterpolator()).translationY(600).setDuration(150);
                btnIAP.animate().setInterpolator(new AccelerateDecelerateInterpolator()).translationY(600).setDuration(150);
            }

            @Override
            public void onShow() {
                btnSearch.animate().setInterpolator(new AccelerateDecelerateInterpolator()).translationY(0).setDuration(150);
                btnIAP.animate().setInterpolator(new AccelerateDecelerateInterpolator()).translationY(0).setDuration(150);
            }
        });

        messageReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                showSnackbar(intent.getExtras().getString(NotificationService.NOTIFICATION_ERROR_MESSAGE), NONE, Snackbar.LENGTH_SHORT);
            }
        };

        apply(btnIAP, GONE);
        btnIAP.setOnClickListener(v -> {
            analyticsHelper.logScreenEvent(SCREEN_FAVOURITE_JOURNEYS, IAP_SEARCH);
            Intent i = new Intent(FavouriteJourneysActivity.this, LicenseUpgradeActivity.class);
            FavouriteJourneysActivity.this.startActivity(i);
        });

        if (ignoreBeingProAndShowAds) {
            apply(btnIAP, VISIBLE);
            loadAds();
        }

        AsyncTask task = new LoadCursorTask(this).execute();

        startService(new Intent(this, NotificationService.class));
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        checkIntent(intent);
    }

    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (result.isFailure()) {

        } else {
            if (CustomIABHelper.isOrderOk(result, inv)) {
                if (ignoreBeingProAndShowAds) {
                    apply(btnIAP, VISIBLE);
                    loadAds();
                } else {
                    ProPreferences.enablePro(getViewContext());
                    if (isFirstStart) ProPreferences.enableAllPro(getViewContext());
                    if (isFirstStartAfterUpdate && previousVersionCode < 47)
                        ProPreferences.enableCompactNotification(getViewContext());
                    apply(btnIAP, GONE);
                    Ads.removeAds(rlBannerPlaceholder, adView2);
                    hideIAPButton();
                    shouldDisplayDiscountMessage = false;
                    invalidateOptionsMenu();
                }
            } else {
                ProPreferences.disableAllPro(getViewContext());
                apply(btnIAP, VISIBLE);
                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) btnSearch.getLayoutParams();
                lp.addRule(RelativeLayout.ALIGN_PARENT_END);
                btnSearch.setLayoutParams(lp);
                shouldDisplayDiscountMessage = true;
                loadAds();

            }
        }
        setVisibilityOfProBedge();
    }

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

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.default_menu, menu);
        this.menu = menu;
        setVisibilityOfProBedge();
        return true;
    }

    private void setVisibilityOfProBedge() {
        if (ProPreferences.isPro(getViewContext()) && menu != null) {
            MenuItem item = menu.findItem(R.id.label_pro);
            if (item != null) item.setVisible(true);
        }
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (!shouldDisplayDiscountMessage) {
            menu.findItem(R.id.action_join).setTitle("Supporta l'app");
        }
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_join:
                FavouriteJourneysActivity.this.startActivity(new Intent(FavouriteJourneysActivity.this, LicenseUpgradeActivity.class));
                return true;
            case R.id.action_settings:
                FavouriteJourneysActivity.this.startActivity(new Intent(FavouriteJourneysActivity.this, SettingsActivity.class));