@Override
  public void onCalculatorEvent(
      @Nonnull CalculatorEventData calculatorEventData,
      @Nonnull CalculatorEventType calculatorEventType,
      @Nullable Object data) {
    final Context context;

    final Object source = calculatorEventData.getSource();
    if (source instanceof Context) {
      context = ((Context) source);
    } else {
      context = App.getApplication();
    }

    switch (calculatorEventType) {
      case show_create_matrix_dialog:
        App.getUiThreadExecutor()
            .execute(
                new Runnable() {
                  @Override
                  public void run() {
                    final Intent intent = new Intent(context, CalculatorMatrixActivity.class);
                    Android.addIntentFlags(intent, false, context);
                    context.startActivity(intent);
                  }
                });
        break;
      case show_create_var_dialog:
        App.getUiThreadExecutor()
            .execute(
                new Runnable() {
                  @Override
                  public void run() {
                    CalculatorActivityLauncher.tryCreateVar(context);
                  }
                });
        break;
      case show_create_function_dialog:
        App.getUiThreadExecutor()
            .execute(
                new Runnable() {
                  @Override
                  public void run() {
                    CalculatorActivityLauncher.tryCreateFunction(context);
                  }
                });
        break;
      case show_evaluation_error:
        final String errorMessage = (String) data;
        if (errorMessage != null) {
          App.getUiThreadExecutor()
              .execute(
                  new Runnable() {
                    @Override
                    public void run() {
                      showEvaluationError(context, errorMessage);
                    }
                  });
        }
        break;
      case show_wiki_description:
        App.getUiThreadExecutor()
            .execute(
                new Runnable() {
                  @Override
                  public void run() {
                    final Intent intent = new Intent(context, CalculatorWikiActivity.class);
                    Android.addIntentFlags(intent, false, context);
                    context.startActivity(intent);
                  }
                });
        break;
      case show_message_dialog:
        final DialogData dialogData = (DialogData) data;
        if (dialogData != null) {
          App.getUiThreadExecutor()
              .execute(
                  new Runnable() {
                    @Override
                    public void run() {
                      CalculatorDialogActivity.showDialog(context, dialogData);
                    }
                  });
        }
        break;
    }
  }
  @Override
  public void onCreate() {
    ACRA.init(this);

    App.init(this);

    final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
    CalculatorPreferences.setDefaultValues(preferences);

    preferences.registerOnSharedPreferenceChangeListener(this);

    setTheme(preferences);

    super.onCreate();

    final AndroidCalculator calculator = new AndroidCalculator(this);

    final EditorTextProcessor editorTextProcessor = new EditorTextProcessor();

    Locator.getInstance()
        .init(
            calculator,
            new AndroidCalculatorEngine(this),
            new AndroidCalculatorClipboard(this),
            new AndroidCalculatorNotifier(this),
            new AndroidCalculatorHistory(this, calculator),
            new AndroidCalculatorLogger(),
            new AndroidCalculatorPreferenceService(this),
            new AndroidCalculatorKeyboard(this, new CalculatorKeyboardImpl(calculator)),
            new AndroidCalculatorPlotter(this, new CalculatorPlotterImpl(calculator)),
            editorTextProcessor);

    editorTextProcessor.init(this);

    listeners.add(new CalculatorActivityLauncher());
    for (CalculatorEventListener listener : listeners) {
      calculator.addCalculatorEventListener(listener);
    }

    calculator.addCalculatorEventListener(broadcaster);

    Locator.getInstance().getCalculator().init();

    BillingDB.init(CalculatorApplication.this);

    if (withAds) {
      AdsController.getInstance()
          .init(
              this,
              ADMOB_USER_ID,
              AD_FREE_PRODUCT_ID,
              new BillingController.IConfiguration() {

                @Override
                public byte[] getObfuscationSalt() {
                  return new byte[] {
                    81, -114, 32, -127, -32, -104, -40, -15, -47, 57, -13, -41, -33, 67, -114, 7,
                    -11, 53, 126, 82
                  };
                }

                @Override
                public String getPublicKey() {
                  return CalculatorSecurity.getPK();
                }
              });
    }

    BillingController.registerObserver(new DefaultBillingObserver(this, null));

    // init billing controller
    new Thread(
            new Runnable() {
              @Override
              public void run() {
                BillingController.checkBillingSupported(CalculatorApplication.this);
                AdsController.getInstance().isAdFree(CalculatorApplication.this);

                try {
                  // prepare engine
                  Locator.getInstance().getEngine().getMathEngine0().evaluate("1+1");
                  Locator.getInstance().getEngine().getMathEngine0().evaluate("1*1");
                } catch (Throwable e) {
                  Log.e(TAG, e.getMessage(), e);
                }
              }
            })
        .start();

    Locator.getInstance().getLogger().debug(TAG, "Application started!");
    Locator.getInstance().getNotifier().showDebugMessage(TAG, "Application started!");
  }