+package net.jcornell.osmandgeocoder;
+
+import net.jcornell.osmandgeocoder.Util.Result;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import androidx.appcompat.app.AppCompatActivity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.json.JSONException;
+
+public class MainActivity extends AppCompatActivity {
+ protected Application app;
+ protected ExecutorService jobExecutor, resultProcExecutor;
+
+ protected void initResultProcThread() {
+ resultProcExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ final Button goButton = findViewById(R.id.go_button);
+ goButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ handleSubmit();
+ }
+ });
+
+ app = (Application) getApplication();
+
+ // I had problems using plain threads for some reason
+ jobExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ initResultProcThread();
+ resultProcExecutor.execute(new ResultProcessor());
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ resultProcExecutor.shutdownNow();
+ }
+
+ protected static class UiStatus {
+ public final boolean loading;
+ public final String statusMessage;
+
+ public UiStatus(boolean loading, String statusMessage) {
+ this.loading = loading;
+ this.statusMessage = statusMessage;
+ }
+ }
+
+ protected void updateUiStatus(UiStatus status) {
+ ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress);
+ TextView statusText = (TextView) findViewById(R.id.status_text);
+
+ progressBar.setVisibility(status.loading ? View.VISIBLE : View.GONE);
+ if (status.statusMessage == null) {
+ statusText.setVisibility(View.GONE);
+ } else {
+ statusText.setText(status.statusMessage);
+ statusText.setVisibility(View.VISIBLE);
+ }
+ }
+
+ protected void handleSubmit() {
+ final EditText addrBox = findViewById(R.id.addr_entry);
+ final String addr = addrBox.getText().toString();
+ jobExecutor.execute(new Runner(addr));
+ updateUiStatus(new UiStatus(true, null));
+ }
+
+ protected void onJobComplete(Result<JSONArray> result) {
+ if (result instanceof Result.Ok) {
+ JSONArray data = ((Result.Ok<JSONArray>) result).value;
+ if (data.length() == 0) {
+ updateUiStatus(new UiStatus(false, "No results."));
+ }
+ else {
+ updateUiStatus(new UiStatus(false, null));
+
+ final Dialog resultsDialog = new Dialog(this);
+ resultsDialog.setContentView(R.layout.results_dialog);
+ resultsDialog.setTitle("Results");
+
+ class Entry {
+ public final String name, latitude, longitude;
+ public Entry(String name, String latitude, String longitude) {
+ this.name = name;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+ public String toString() { return name; }
+ }
+
+ final List<Entry> entries = new ArrayList<>();
+ for (int i = 0; i < data.length(); i += 1) {
+ try {
+ JSONObject entry = data.getJSONObject(i);
+ entries.add(new Entry(
+ entry.getString("display_name"),
+ entry.getString("lat"),
+ entry.getString("lon")
+ ));
+ } catch (JSONException e) {}
+ }
+ ArrayAdapter<Entry> adapter = new ArrayAdapter<>(
+ this,
+ android.R.layout.simple_list_item_1,
+ entries
+ );
+
+ ListView list = (ListView) resultsDialog.findViewById(R.id.results_list);
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ resultsDialog.dismiss();
+ Entry e = entries.get(position);
+ Uri uri = Uri.parse(String.format("geo:%s,%s", e.latitude, e.longitude));
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ }
+ });
+ list.setAdapter(adapter);
+ resultsDialog.show();
+ }
+ } else {
+ Throwable t = ((Result.Error) result).error;
+ String message = String.format("%s: %s", t.getClass().getSimpleName(), t.getMessage());
+ updateUiStatus(new UiStatus(false, message));
+ }
+ }
+
+ protected class ResultProcessor implements Runnable {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ final Result<JSONArray> result = app.results.take();
+ runOnUiThread(new Runnable() {
+ @Override public void run() {
+ onJobComplete(result);
+ }
+ });
+ }
+ } catch (InterruptedException e) {
+ // terminate thread
+ }
+ }
+ }
+
+ protected class Runner implements Runnable {
+ protected final String API_URL = "https://nominatim.openstreetmap.org/search";
+
+ protected final String address;
+
+ public Runner(String address) {
+ this.address = address;
+ }
+
+ @Override
+ public void run() {
+ Result<JSONArray> result;
+ try {
+ String queryParam = URLEncoder.encode(
+ address.replace('\n', ';'), // Nominatim doesn't like these
+ "UTF-8"
+ );
+ URL url = new URL(API_URL + "?format=json&limit=10&q=" + queryParam);
+ InputStream stream = url.openConnection().getInputStream();
+ String body = new String(Util.readAll(stream), "UTF-8");
+ result = new Result.Ok<>(new JSONArray(body));
+ } catch (IOException | JSONException e) {
+ result = new Result.Error(e);
+ }
+
+ try {
+ app.results.put(result);
+ } catch (InterruptedException e) {}
+ }
+ }
+}