content: package allen.town.podcast.sync.nextcloud;
  
  import android.content.Context;
  import android.content.Intent;
  import android.net.Uri;
  import allen.town.podcast.sync.HostnameParser;
  import io.reactivex.Observable;
  import io.reactivex.android.schedulers.AndroidSchedulers;
  import io.reactivex.disposables.Disposable;
  import io.reactivex.schedulers.Schedulers;
  import okhttp3.MediaType;
  import okhttp3.OkHttpClient;
  import okhttp3.Request;
  import okhttp3.RequestBody;
  import okhttp3.Response;
  import okhttp3.ResponseBody;
  import org.json.JSONException;
  import org.json.JSONObject;
  import android.util.Log;
  
  import java.io.IOException;
  import java.net.URI;
  import java.net.URL;
  import java.util.concurrent.TimeUnit;
  
  public class NextcloudLoginFlow {
      private static final String TAG = "NextcloudLoginFlow";
  
      private final OkHttpClient httpClient;
      private final HostnameParser hostname;
      private final Context context;
      private final AuthenticationCallback callback;
      private String token;
      private String endpoint;
      private Disposable startDisposable;
      private Disposable pollDisposable;
  
      public NextcloudLoginFlow(OkHttpClient httpClient, String hostUrl, Context context,
                                AuthenticationCallback callback) {
          this.httpClient = httpClient;
          this.hostname = new HostnameParser(hostUrl);
          this.context = context;
          this.callback = callback;
      }
  
      public void start() {
          startDisposable = Observable.fromCallable(() -> {
              URL url = new URI(hostname.scheme, null, hostname.host, hostname.port,
                      hostname.subfolder + "/index.php/login/v2", null, null).toURL();
              JSONObject result = doRequest(url, "");
              String loginUrl = result.getString("login");
              this.token = result.getJSONObject("poll").getString("token");
              this.endpoint = result.getJSONObject("poll").getString("endpoint");
              return loginUrl;
          })
                  .subscribeOn(Schedulers.io())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(
                      result -> {
                          Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(result));
                          context.startActivity(browserIntent);
                          poll();
                      }, error -> {
                          Log.e(TAG, Log.getStackTraceString(error));
                          callback.onNextcloudAuthError(error.getLocalizedMessage());
                      });
      }
  
      private void poll() {
          pollDisposable = Observable.fromCallable(() -> doRequest(URI.create(endpoint).toURL(), "token=" + token))
                  .retryWhen(t -> t.delay(1, TimeUnit.SECONDS))
                  .timeout(5, TimeUnit.MINUTES)
                  .subscribeOn(Schedulers.io())
                  .observeOn(AndroidSchedulers.mainThread())
                  .subscribe(result -> callback.onNextcloudAuthenticated(
                                  result.getString("server"), result.getString("loginName"), result.getString("appPassword")),
                          error -> callback.onNextcloudAuthError(error.getLocalizedMessage()));
      }
  
      public void cancel() {
          if (startDisposable != null) {
              startDisposable.dispose();
          }
          if (pollDisposable != null) {
              pollDisposable.dispose();
          }
      }
  
      private JSONObject doRequest(URL url, String bodyContent) throws IOException, JSONException {
          RequestBody requestBody = RequestBody.create(
                  MediaType.get("application/x-www-form-urlencoded"), bodyContent);
          Request request = new Request.Builder().url(url).method("POST", requestBody).build();
          Response response = httpClient.newCall(request).execute();
          if (response.code() != 200) {
              response.close();
              throw new IOException("Return code " + response.code());
          }
          ResponseBody body = response.body();
          if (body == null) {
              throw new IOException("Empty response");
          }
          return new JSONObject(body.string());
      }
  
      public interface AuthenticationCallback {
          void onNextcloudAuthenticated(String server, String username, String password);
  
          void onNextcloudAuthError(String errorMessage);
      }
  }
  