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.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; int interval, remind; @Override protected void onCreate(Bundle b) { super.onCreate(b); setTitle("GingerCal settings"); LinearLayout f = App.vbox(this); int p = App.dp(this, 8); f.setPadding(p, p, p, p); f.addView(App.text(this, "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, "Server address", "https://cloud.example.com:8443", 1 | 0x10 /* URI */); user = field(f, "User name", "", 1 | 0x80000 /* no suggestions */); pass = field(f, "App password", "", 1 | 0x80 /* password */); pin = field(f, "Pinned certificate SHA-256 (filled in for you)", "optional", 1 | 0x80000); ahead = field(f, "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)); Button test = new Button(this); test.setText("Save and test connection"); f.addView(test, new LinearLayout.LayoutParams(-1, -2)); Button resync = new Button(this); resync.setText("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("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 ? "Off" : App.alarmLabel(opts[i]); new AlertDialog.Builder(SettingsActivity.this).setTitle("Phone notification") .setItems(l, new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { remind = opts[w]; labels(); } }).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, "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 ? "Manual only" : h == 1 ? "Every hour" : "Every " + h + " hours"; } void labels() { intervalBtn.setText("Background sync: " + intervalLabel(interval)); remindBtn.setText("Notify: " + (remind < 0 ? "off" : App.alarmLabel(remind))); } 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).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, "Server address and user name are required."); return; } if (Dav.normalize(u).startsWith("http://") && !App.isLan(u)) { new AlertDialog.Builder(this).setTitle("Unencrypted connection") .setMessage("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("Use anyway", new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { save(); new TestTask().execute(); } }) .setNegativeButton("Cancel", null).show(); return; } save(); new TestTask().execute(); } class TestTask extends AsyncTask { ProgressDialog pd; @Override protected void onPreExecute() { pd = ProgressDialog.show(SettingsActivity.this, null, "Connecting…"); } @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("Connection failed") .setMessage(App.describe((Throwable) r)).setPositiveButton("OK", null).show(); return; } List cals = (List) r; StringBuilder s = new StringBuilder("Connected. Found " + cals.size() + " calendar(s):\n"); for (Cal c : cals) s.append("\n• ").append(c.name).append(c.writable ? "" : " (read-only)"); new AlertDialog.Builder(SettingsActivity.this).setTitle("It works") .setMessage(s.toString()) .setPositiveButton("Sync now", new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { App.requestSync(SettingsActivity.this, true, false); finish(); } }).show(); } } }