class RetryingInstaller {

  @NotNull
  public RetryingInstallerResult install() {
    InstallResult result = null;
    boolean retry = true;
    int retryCount = 0;
    StopWatch totalDuration = new StopWatch();
    StopWatch lastInstallDuration = new StopWatch();

    while (!myLaunchStatus.isLaunchTerminated() && retry) {
      retryCount++;
      lastInstallDuration.restart();
      result = myInstaller.installApp(myDevice, myLaunchStatus);
      lastInstallDuration.stop();

      if (result.installOutput != null) {
        if (result.failureCode == InstallResult.FailureCode.NO_ERROR) {
          myPrinter.stdout(result.installOutput);
        }
        else {
          myPrinter.stderr(result.installOutput);
        }
      }

      String reason;
      switch (result.failureCode) {
        case DEVICE_NOT_RESPONDING:
          int waitTime = 2;
          myPrinter.stdout("Device is not ready. Waiting for " + waitTime + " seconds.");
          try {
            TimeUnit.SECONDS.sleep(waitTime);
          }
          catch (InterruptedException e) {
            Logger.getInstance(RetryingInstaller.class).info(e);
          }
          retry = true;
          break;
        case INSTALL_FAILED_VERSION_DOWNGRADE:
          reason = AndroidBundle
            .message("deployment.failed.uninstall.prompt.text", AndroidBundle.message("deployment.failed.reason.version.downgrade"));
          retry = myPrompter.showQuestionPrompt(reason) && uninstallPackage(myDevice, myApplicationId);
          break;
        case INSTALL_FAILED_UPDATE_INCOMPATIBLE:
        case INCONSISTENT_CERTIFICATES:
          reason = AndroidBundle
            .message("deployment.failed.uninstall.prompt.text", AndroidBundle.message("deployment.failed.reason.different.signature"));
          retry = myPrompter.showQuestionPrompt(reason) && uninstallPackage(myDevice, myApplicationId);
          break;
        case INSTALL_FAILED_DEXOPT:
          reason =
            AndroidBundle.message("deployment.failed.uninstall.prompt.text", AndroidBundle.message("deployment.failed.reason.dexopt"));
          retry = myPrompter.showQuestionPrompt(reason) && uninstallPackage(myDevice, myApplicationId);
          break;
        case NO_CERTIFICATE:
          myPrinter.stderr(AndroidBundle.message("deployment.failed.no.certificates.explanation"));
          myPrompter.showErrorMessage(AndroidBundle.message("deployment.failed.no.certificates.explanation"));
          retry = false;
          break;
        case INSTALL_FAILED_OLDER_SDK: // TODO: this should not happen and should have been caught by the device picker
          String message = AndroidBundle.message("deployment.failed.reason.oldersdk", myDevice.getVersion().toString());
          myPrinter.stderr(message);
          retry = false;
          break;
        case DEVICE_NOT_FOUND:
          reason = AndroidBundle.message("deployment.failed.reason.devicedisconnected", myDevice.getName());
          myPrompter.showErrorMessage(reason);
          retry = false;
          break;
        case UNTYPED_ERROR:
          reason = AndroidBundle.message("deployment.failed.uninstall.prompt.generic.text", result.failureMessage);
          retry = myPrompter.showQuestionPrompt(reason) && uninstallPackage(myDevice, myApplicationId);
          break;
        default:
          retry = false;
          break;
      }
    }

    totalDuration.stop();
    return new RetryingInstallerResult(result != null && result.failureCode == InstallResult.FailureCode.NO_ERROR,
                                       retryCount,
                                       totalDuration.getDuration(),
                                       lastInstallDuration.getDuration());
  }

  public  RetryingInstaller(@NotNull Project project,
                           @NotNull IDevice device,
                           @NotNull Installer installer,
                           @NotNull String applicationId,
                           @NotNull ConsolePrinter printer,
                           @NotNull LaunchStatus launchStatus);
  @VisibleForTesting public  RetryingInstaller(@NotNull IDevice device,
                           @NotNull Installer installer,
                           @NotNull String applicationId,
                           @NotNull Prompter prompter,
                           @NotNull ConsolePrinter printer,
                           @NotNull LaunchStatus launchStatus);

}