gt:         error = t.getMessage();
context: /*
   * Copyright (c) 2015 OpenSilk Productions LLC
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package syncthing.android.ui.welcome;
  
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.Context;
  import android.content.Intent;
  import android.database.ContentObserver;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Looper;
  
  import org.apache.commons.lang3.StringUtils;
  import org.opensilk.common.core.dagger2.ForApplication;
  import org.opensilk.common.core.dagger2.ScreenScope;
  import org.opensilk.common.ui.mortar.ActivityResultsController;
  import org.opensilk.common.ui.mortar.DialogPresenter;
  import org.opensilk.common.ui.mortarfragment.FragmentManagerOwner;
  
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicReference;
  
  import javax.inject.Inject;
  
  import mortar.ViewPresenter;
  import rx.Observable;
  import rx.Scheduler;
  import rx.Subscriber;
  import rx.Subscription;
  import rx.android.schedulers.AndroidSchedulers;
  import rx.observers.Subscribers;
  import rx.schedulers.Schedulers;
  import syncthing.android.R;
  import syncthing.api.Credentials;
  import syncthing.android.service.ConfigXml;
  import syncthing.android.service.ServiceSettings;
  import syncthing.android.service.SyncthingInstance;
  import syncthing.android.service.SyncthingUtils;
  import syncthing.android.settings.AppSettings;
  import syncthing.android.ui.ManageActivity;
  import syncthing.android.ui.login.LoginFragment;
  import syncthing.api.SessionManager;
  import syncthing.api.SynchingApiWrapper;
  import syncthing.api.SyncthingApi;
  import syncthing.api.SyncthingApiConfig;
  import syncthing.api.model.Config;
  import syncthing.api.model.DeviceConfig;
  import syncthing.api.model.Ok;
  import timber.log.Timber;
  
  @ScreenScope
  public class WelcomePresenter extends ViewPresenter<WelcomeScreenView>{
  
      class InitializedListener extends ContentObserver {
          public InitializedListener(Handler handler) {
              super(handler);
          }
  
          @Override
          public void onChange(boolean selfChange) {
              onInstanceInitialized();
          }
      }
  
      enum State {
          NONE,
          GENERATING,
          SUCCESS,
          ERROR,
      }
  
      final Context context;
      final AppSettings appSettings;
      final ActivityResultsController activityResultsController;
      final FragmentManagerOwner fragmentManager;
      final SessionManager manager;
      final ServiceSettings serviceSettings;
      final DialogPresenter dialogPresenter;
  
      final InitializedListener initializedListener = new InitializedListener(new Handler(Looper.getMainLooper()));
      final SyncthingApiConfig.Builder configBuilder = SyncthingApiConfig.builder();
      final AtomicReference<Credentials> credentials = new AtomicReference<>();
  
      int page = 0;
      State state = State.NONE;
      String error;
  
      Subscription subscription;
      Subscription splashSubscription;
      Subscriber<Boolean> initializedSubscriber;
  
      @Inject
      public WelcomePresenter(
              @ForApplication Context context,
              AppSettings appSettings,
              ActivityResultsController activityResultsController,
              FragmentManagerOwner fragmentManager,
              SessionManager manager,
              ServiceSettings serviceSettings,
              DialogPresenter dialogPresenter
      ) {
          this.context = context;
          this.appSettings = appSettings;
          this.activityResultsController = activityResultsController;
          this.fragmentManager = fragmentManager;
          this.manager = manager;
          this.serviceSettings = serviceSettings;
          this.dialogPresenter = dialogPresenter;
      }
  
      @Override
      protected void onExitScope() {
          super.onExitScope();
          context.getContentResolver().unregisterContentObserver(initializedListener);
          if (splashSubscription != null) {
              splashSubscription.unsubscribe();
          }
          unsubscribeS();
      }
  
      @Override
      protected void onLoad(Bundle savedInstanceState) {
          super.onLoad(savedInstanceState);
          switch (state) {
              case NONE:
                  delayHideSplash();
                  break;
              case SUCCESS:
                  exitSuccess();
                  break;
              case ERROR:
                  showError();
                  break;
          }
      }
  
      void reload() {
          if (!hasView())
              return;
          getView().setPage(page);
      }
  
      void updatePage(int page) {
          this.page = page;
      }
  
      void delayHideSplash() {
          splashSubscription = Observable.timer(10, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
                  .subscribe(subscriber -> hideSplash());
      }
  
      void hideSplash() {
          if (page != 0)
              return;
          if (!hasView())
              return;
          getView().hideSplash();
      }
  
      void unsubscribeS() {
          if (subscription != null) {
              final Subscription s = subscription;
              final Scheduler.Worker worker = Schedulers.io().createWorker();
              worker.schedule(() -> {
                  s.unsubscribe();
                  worker.unsubscribe();
              });
          }
      }
  
      /*
       * Im sure your looking at this and thinking for the love of god why
       * We use this rather complicated observable to fetch our credential information
       * and save a generated password all in one fail swoop.
       * The observable handles acquiring and releasing the session, (note we must acquire
       * two sessions so the post will succeed), the observable also handles retries.
       * By using 'defer' we create new OkHttp Calls on each retry, this is mandatory as
       * calls are oneshot deals.
       */
      void onInstanceInitialized() {
          configBuilder.setUrl(SyncthingUtils.buildUrl(ConfigXml.LOCALHOST_IP, ConfigXml.DEFAULT_PORT, true));
          subscription = Observable.using(
                  () -> {
                      Timber.d("Acquiring session");
                      return manager.acquire(configBuilder.build());
                  },
                  s -> {
                      final SyncthingApi api = SynchingApiWrapper.wrap(s.api(), Schedulers.io());
                      return Observable.defer(() -> Observable.zip(api.system(), api.config(), (systemInfo, config) -> {
                          //build our localhost credentials
                          Credentials.Builder builder = Credentials.builder();
                          builder.setUrl(SyncthingUtils.buildUrl(ConfigXml.LOCALHOST_IP,
                                  ConfigXml.DEFAULT_PORT, true));
                          builder.setApiKey(config.gui.apiKey);
                          builder.setId(systemInfo.myID);
                          for (DeviceConfig d : config.devices) {
                              if (StringUtils.equals(d.deviceID, systemInfo.myID)) {
                                  builder.setAlias(SyncthingUtils.getDisplayName(d));
                                  break;
                              }
                          }
                          builder.setCaCert(SyncthingUtils.getSyncthingCACert(context));
  
                          //save and set as default
                          Credentials c = builder.build();
                          credentials.set(c);
                          appSettings.saveCredentials(c);
                          appSettings.setDefaultCredentials(c);
  
                          //create random user/pass
                          String username = SyncthingUtils.generateUsername();
                          String password = SyncthingUtils.generatePassword();
                          Timber.i("Generated GUI username and password (%s, %s)", username, password);
                          config.gui.user = username;
                          config.gui.password = password;
  
                          return config;
                      })).retryWhen(observable -> observable.flatMap(throwable -> {
                          String msg = throwable.getMessage();
                          if (msg == null ||
                                  !(msg.contains("ECONNREFUSED") ||
                                  msg.contains("EOFException") ||
                                  msg.contains("timeout") ||
                                  msg.contains("Connection reset by peer"))) {
                              // Another service running, propagate error and show to user
                              Timber.d("Unknown error %s", msg);
                              return Observable.error(throwable);
                          }
                          Timber.d("Retrying in 5 seconds");
                          return Observable.timer(5, TimeUnit.SECONDS);
                      }));
                  },
                  s -> {
                      Timber.d("Releasing session");
                      manager.release(s);
                  },
                  true // eagerly release
          ).observeOn(AndroidSchedulers.mainThread()
          ).flatMap(this::saveConfigObservable //want subscribe on main thread
          ).observeOn(AndroidSchedulers.mainThread()
          ).subscribe(Subscribers.create(this::finish, this::processError) //want results on main thread
          );
      }
  
      Observable<Ok> saveConfigObservable(final Config config) {
          return Observable.using(
                  () -> {
                      Timber.d("Acquiring session2");
                      configBuilder.setApiKey(credentials.get().apiKey);
                      configBuilder.setCaCert(credentials.get().caCert);
                      return manager.acquire(configBuilder.build());
                  },
                  s -> {
                      final SyncthingApi api = SynchingApiWrapper.wrap(s.api(), Schedulers.io());
                      return Observable.defer(() -> api.updateConfig(config)
                              //restart
                              .flatMap(c -> api.restart())
                              // Give syncthing a chance to reboot
                              .delay(10, TimeUnit.SECONDS)
                      );
                  },
                  s -> {
                      Timber.d("Releasing session2");
                      manager.release(s);
                  },
                  true // eagerly release
          );//TODO what kind of errors can be recovered from at this point
      }
  
      public void initializeInstance() {
          state = State.GENERATING;
          serviceSettings.setEnabled(true);
          context.startService(new Intent(context, SyncthingInstance.class).setAction(SyncthingInstance.REEVALUATE));
          context.getContentResolver().registerContentObserver(serviceSettings.getInitializedUri(), false, initializedListener);
      }
  
      public void setInitializedSubscriber(Subscriber<Boolean> subscription) {
          initializedSubscriber = subscription;
      }
  
      void cancelGeneration() {
          unsubscribeS();
          exitCanceled();
      }
  
      void finish(Ok ok) {
          state = State.SUCCESS;
          Timber.d("Ready");
          if (page == 1)
              this.page = 5;
          if (initializedSubscriber != null) {
              initializedSubscriber.onNext(true);
          }
          reload();
      }
  
      void processError(Throwable t) {
          Timber.w("processError(%s)", t.getMessage());
          if (!serviceSettings.isInitialised()) {
              serviceSettings.setEnabled(false);
              context.stopService(new Intent(context, SyncthingInstance.class));
              throw new RuntimeException("Daemon failed to start");
          } else {
              notifyError(t);
          }
      }
  
      void notifyError(Throwable t) {
          Timber.d("notifyError(%s)", t.getMessage());
          state = State.ERROR;
  