package org.gingercal; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import org.gingercal.Model.Cal; /** Account, TLS pin, sync interval and reminder settings. */ public class SettingsActivity extends Activity { EditText url, user, pass, pin, ahead; Button intervalBtn, remindBtn, langBtn; int interval, remind; @Override protected void onCreate(Bundle b) { super.onCreate(b); setTitle(L.t("GingerCal settings")); LinearLayout f = App.vbox(this); int p = App.dp(this, 8); f.setPadding(p, p, p, p); f.addView(App.text(this, L.t("Nextcloud server. Android 2.3 can't speak TLS 1.2, so use either your legacy-TLS bridge (https://cloud.example.com:8443) or http:// to the server on your home Wi-Fi. Use an app password, not your real one."), 12, 0xFFB0BEC5)); url = field(f, L.t("Server address"), "https://cloud.example.com:8443", 1 | 0x10 /* URI */); user = field(f, L.t("User name"), "", 1 | 0x80000 /* no suggestions */); pass = field(f, L.t("App password"), "", 1 | 0x80 /* password */); pin = field(f, L.t("Pinned certificate SHA-256 (filled in for you)"), L.t("optional"), 1 | 0x80000); ahead = field(f, L.t("Days ahead to sync"), "60", 2 /* number */); intervalBtn = new Button(this); f.addView(intervalBtn, new LinearLayout.LayoutParams(-1, -2)); remindBtn = new Button(this); f.addView(remindBtn, new LinearLayout.LayoutParams(-1, -2)); langBtn = new Button(this); f.addView(langBtn, new LinearLayout.LayoutParams(-1, -2)); Button test = new Button(this); test.setText(L.t("Save and test connection")); f.addView(test, new LinearLayout.LayoutParams(-1, -2)); Button resync = new Button(this); resync.setText(L.t("Re-download everything")); f.addView(resync, new LinearLayout.LayoutParams(-1, -2)); ScrollView sv = new ScrollView(this); sv.addView(f); setContentView(sv); url.setText(App.get(this, "url")); user.setText(App.get(this, "user")); pass.setText(App.get(this, "pass")); pin.setText(App.get(this, "pin")); ahead.setText(String.valueOf(App.ahead(this))); interval = App.interval(this); remind = App.remind(this); labels(); intervalBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { final String[] l = new String[App.INTERVALS.length]; for (int i = 0; i < l.length; i++) l[i] = intervalLabel(App.INTERVALS[i]); new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Background sync")) .setItems(l, new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { interval = App.INTERVALS[w]; labels(); } }).show(); } }); remindBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { final int[] opts = {-1, 0, 5, 10, 15, 30, 60}; String[] l = new String[opts.length]; for (int i = 0; i < l.length; i++) l[i] = opts[i] < 0 ? L.t("Off") : App.alarmLabel(opts[i]); new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Phone notification")) .setItems(l, new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { remind = opts[w]; labels(); } }).show(); } }); langBtn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { new AlertDialog.Builder(SettingsActivity.this).setTitle("Lingua / Language") .setItems(new String[] {"Galego", "English"}, new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { boolean toGl = w == 0; if (toGl == L.gl) return; save(); App.prefs(SettingsActivity.this).edit().putString("lang", toGl ? "gl" : "en").commit(); L.gl = toGl; finish(); // reopen so every label is redrawn in the new language startActivity(new Intent(SettingsActivity.this, SettingsActivity.class)); } }).show(); } }); test.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { confirmAndTest(); } }); resync.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { save(); Db.get(SettingsActivity.this).forgetSyncState(); App.requestSync(SettingsActivity.this, true, true); App.toast(SettingsActivity.this, L.t("Re-downloading in the background")); finish(); } }); } EditText field(LinearLayout f, String label, String hint, int type) { TextView l = App.text(this, label, 13, 0xFFFFFFFF); l.setPadding(App.dp(this, 4), App.dp(this, 8), 0, 0); f.addView(l); EditText e = new EditText(this); e.setHint(hint); e.setSingleLine(true); e.setInputType(type); f.addView(e, new LinearLayout.LayoutParams(-1, -2)); return e; } static String intervalLabel(int h) { return h == 0 ? L.t("Manual only") : h == 1 ? L.t("Every hour") : L.f("Every %d hours", h); } void labels() { intervalBtn.setText(L.f("Background sync: %s", intervalLabel(interval))); remindBtn.setText(L.f("Notification: %s", remind < 0 ? L.t("off") : App.alarmLabel(remind))); langBtn.setText(L.f("Language: %s", L.gl ? "Galego" : "English")); } static String txt(TextView t) { return t.getText().toString().trim(); } @Override protected void onPause() { super.onPause(); if (txt(url).length() > 0) save(); } void save() { String oldAccount = App.get(this, "url") + "|" + App.get(this, "user"); int days = 60; try { days = Math.max(7, Math.min(365, Integer.parseInt(txt(ahead)))); } catch (NumberFormatException ignored) { } String u = txt(url); if (u.length() > 0) u = Dav.normalize(u); App.prefs(this).edit() .putString("url", u) .putString("user", txt(user)) .putString("pass", pass.getText().toString()) .putString("pin", Tls.normPin(txt(pin)).length() == 64 ? txt(pin) : "") .putInt("ahead", days) .putInt("interval", interval) .putInt("remind", remind) .commit(); if (!oldAccount.equals(u + "|" + txt(user))) { Db.get(this).clearAll(); App.prefs(this).edit().putLong("lastsync", 0).putLong("win_from", 0).putLong("win_to", 0) .putString("status", "").commit(); } App.scheduleSync(this); Reminders.schedule(this); } void confirmAndTest() { String u = txt(url); if (u.length() == 0 || txt(user).length() == 0) { App.toast(this, L.t("Server address and user name are required.")); return; } if (Dav.normalize(u).startsWith("http://") && !App.isLan(u)) { new AlertDialog.Builder(this).setTitle(L.t("Unencrypted connection")) .setMessage(L.t("http:// sends your password in clear text over the internet. Use the legacy-TLS bridge instead, or only use http:// to a LAN address.")) .setPositiveButton(L.t("Use anyway"), new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { save(); new TestTask().execute(); } }) .setNegativeButton(L.t("Cancel"), null).show(); return; } save(); new TestTask().execute(); } class TestTask extends AsyncTask { ProgressDialog pd; @Override protected void onPreExecute() { pd = ProgressDialog.show(SettingsActivity.this, null, L.t("Connecting\u2026")); } @Override protected Object doInBackground(Void... v) { Dav d = null; try { d = App.dav(SettingsActivity.this); return d.calendars(d.calendarHome()); } catch (Throwable t) { return t; } finally { if (d != null) d.close(); } } @SuppressWarnings("unchecked") @Override protected void onPostExecute(Object r) { try { pd.dismiss(); } catch (RuntimeException ignored) { } if (r instanceof DavException.UntrustedCert) { App.askTrust(SettingsActivity.this, (DavException.UntrustedCert) r, new App.Trusted() { public void trusted(String fp) { pin.setText(fp); save(); new TestTask().execute(); } }); return; } if (r instanceof Throwable) { new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("Connection failed")) .setMessage(App.describe((Throwable) r)).setPositiveButton(L.t("OK"), null).show(); return; } List cals = (List) r; StringBuilder s = new StringBuilder(L.f("Connected. Calendars found: %d\n", cals.size())); for (Cal c : cals) s.append("\n\u2022 ").append(c.name).append(c.writable ? "" : " " + L.t("(read-only)")); new AlertDialog.Builder(SettingsActivity.this).setTitle(L.t("It works")) .setMessage(s.toString()) .setPositiveButton(L.t("Sync now"), new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { App.requestSync(SettingsActivity.this, true, false); finish(); } }).show(); } } }