@Override
  public void uncaughtException(@NonNull Thread thread, Throwable ex) {
    ex.printStackTrace();
    Logger.e(TAG, "Caught an unhandled exception!!!", ex);

    // https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/15
    // https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/433
    final String stackTrace = Logger.getStackTrace(ex);
    if (ex instanceof NullPointerException) {
      if (stackTrace.contains(
              "android.inputmethodservice.IInputMethodSessionWrapper.executeMessage(IInputMethodSessionWrapper.java")
          || stackTrace.contains(
              "android.inputmethodservice.IInputMethodWrapper.executeMessage(IInputMethodWrapper.java")) {
        Logger.w(TAG, "An OS bug has been adverted. Move along, there is nothing to see here.");
        return;
      }
    } else if (ex instanceof java.util.concurrent.TimeoutException
        && stackTrace.contains(".finalize")) {
      Logger.w(TAG, "An OS bug has been adverted. Move along, there is nothing to see here.");
      return;
    }

    StringBuilder reportMessage = new StringBuilder();
    reportMessage
        .append("Hi. It seems that we have crashed.... Here are some details:")
        .append(NEW_LINE)
        .append("****** UTC Time: ")
        .append(DateFormat.format("kk:mm:ss dd.MM.yyyy", System.currentTimeMillis()))
        .append(NEW_LINE)
        .append("****** Application name: ")
        .append(getAppDetails())
        .append(NEW_LINE)
        .append("******************************")
        .append(NEW_LINE)
        .append(ex.getClass().getName())
        .append(NEW_LINE)
        .append("****** Exception message: ")
        .append(ex.getMessage())
        .append(NEW_LINE)
        .append(HEADER_BREAK_LINE)
        .append(NEW_LINE)
        .append("****** Trace trace:")
        .append(NEW_LINE)
        .append(stackTrace)
        .append(NEW_LINE)
        .append("******************************")
        .append(NEW_LINE)
        .append("****** Device information:")
        .append(NEW_LINE)
        .append(ChewbaccaUtils.getSysInfo(mApp))
        .append(NEW_LINE);
    if (ex instanceof OutOfMemoryError
        || (ex.getCause() != null && ex.getCause() instanceof OutOfMemoryError)) {
      reportMessage
          .append("******************************")
          .append(NEW_LINE)
          .append("****** Memory: ")
          .append(Runtime.getRuntime().totalMemory())
          .append(NEW_LINE)
          .append("Free: ")
          .append(Runtime.getRuntime().freeMemory())
          .append(NEW_LINE)
          .append("Max: ")
          .append(Runtime.getRuntime().maxMemory())
          .append(NEW_LINE);
    }
    reportMessage
        .append("******************************")
        .append(NEW_LINE)
        .append("****** Log-Cat: ")
        .append(NEW_LINE)
        .append(Logger.getAllLogLines())
        .append(NEW_LINE);

    try (OutputStreamWriter writer =
        new OutputStreamWriter(
            mApp.openFileOutput(NEW_CRASH_FILENAME, Context.MODE_PRIVATE),
            Charset.forName("UTF-8"))) {
      writer.write(reportMessage.toString());
      Logger.i(TAG, "Wrote crash report to %s.", NEW_CRASH_FILENAME);
      Logger.d(TAG, "Crash report:");
      for (String line : TextUtils.split(reportMessage.toString(), NEW_LINE)) {
        Logger.d(TAG, "err: %s", line);
      }
    } catch (Exception writeEx) {
      Logger.e(TAG, writeEx, "Failed to write crash report file!");
    }
    // and sending to the OS
    if (mOsDefaultHandler != null) {
      Logger.i(TAG, "Sending the exception to OS exception handler...");
      mOsDefaultHandler.uncaughtException(thread, ex);
    }
  }
@Test
  public void testDoesNotCrashOnNullPreviousHandler() {
    TestableChewbaccaUncaughtExceptionHandler underTest =
        new TestableChewbaccaUncaughtExceptionHandler(
            ApplicationProvider.getApplicationContext(),
            null,
            Mockito.mock(NotificationDriver.class));

    underTest.uncaughtException(Thread.currentThread(), new IOException("an error"));
  }