From f64983b929ea723956ef017a71c0d04d63442de2 Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 18 Jan 2023 11:04:23 +0100 Subject: [PATCH] Cloud sync: account sync --- .../java/eu/faircode/email/CloudSync.java | 298 ++++++++++++++++-- .../java/eu/faircode/email/EntityAccount.java | 96 +++--- .../eu/faircode/email/EntityIdentity.java | 72 +++-- .../faircode/email/FragmentOptionsBackup.java | 18 +- .../res/layout/fragment_options_backup.xml | 11 +- app/src/main/res/values/strings.xml | 3 +- 6 files changed, 367 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/CloudSync.java b/app/src/main/java/eu/faircode/email/CloudSync.java index 46a21d0e4d..d873547ae8 100644 --- a/app/src/main/java/eu/faircode/email/CloudSync.java +++ b/app/src/main/java/eu/faircode/email/CloudSync.java @@ -21,6 +21,7 @@ package eu.faircode.email; import android.content.Context; import android.content.SharedPreferences; +import android.text.TextUtils; import android.util.Base64; import android.util.Pair; @@ -32,7 +33,11 @@ import org.json.JSONObject; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; @@ -70,21 +75,31 @@ public class CloudSync { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String user = prefs.getString("cloud_user", null); String password = prefs.getString("cloud_password", null); + if (TextUtils.isEmpty(user) || TextUtils.isEmpty(password)) + return; + if (!ActivityBilling.isPro(context)) + return; JSONObject jrequest = new JSONObject(); if ("sync".equals(command)) { - DB db = DB.getInstance(context); - - long lrevision = prefs.getLong("sync_status", new Date().getTime()); + long lrevision = prefs.getLong("cloud_revision", new Date().getTime()); Log.i("Cloud local revision=" + lrevision + " (" + new Date(lrevision) + ")"); - JSONObject jsync = new JSONObject(); - jsync.put("key", "sync.status"); - jsync.put("rev", lrevision); + Long lastUpdate = updateSyncdata(context); + Log.i("Cloud last update=" + (lastUpdate == null ? null : new Date(lastUpdate))); + if (lastUpdate != null && lrevision > lastUpdate) { + Log.w("Cloud invalid local revision" + + " lrevision=" + lrevision + " last=" + lastUpdate); + prefs.edit().putLong("cloud_revision", lastUpdate).apply(); + } + + JSONObject jsyncstatus = new JSONObject(); + jsyncstatus.put("key", "sync.status"); + jsyncstatus.put("rev", lrevision); JSONArray jitems = new JSONArray(); - jitems.put(jsync); + jitems.put(jsyncstatus); jrequest.put("items", jitems); @@ -93,32 +108,37 @@ public class CloudSync { if (jitems.length() == 0) { Log.i("Cloud server is empty"); - - JSONObject jstatusdata = new JSONObject(); - jstatusdata.put("sync.version", 1); - jstatusdata.put("app.version", BuildConfig.VERSION_CODE); - - jsync = new JSONObject(); - jsync.put("key", "sync.status"); - jsync.put("val", jstatusdata.toString()); - jsync.put("rev", lrevision); - jitems.put(jsync); - - jrequest = new JSONObject(); - jrequest.put("items", jitems); - call(context, user, password, "write", jrequest); - - prefs.edit().putLong("sync_status", lrevision).apply(); + sendLocalData(context, user, password, lrevision); } else if (jitems.length() == 1) { Log.i("Cloud sync check"); - jsync = jitems.getJSONObject(0); - long rrevision = jsync.getLong("rev"); - JSONObject jstatusdata = new JSONObject(jsync.getString("val")); - - int sync_version = jstatusdata.optInt("sync.version", 0); - int app_version = jstatusdata.optInt("app.version", 0); + jsyncstatus = jitems.getJSONObject(0); + long rrevision = jsyncstatus.getLong("rev"); + JSONObject jstatus = new JSONObject(jsyncstatus.getString("val")); + int sync_version = jstatus.optInt("sync.version", 0); + int app_version = jstatus.optInt("app.version", 0); Log.i("Cloud version sync=" + sync_version + " app=" + app_version + - " local=" + lrevision + " remote=" + rrevision); + " local=" + lrevision + " last=" + lastUpdate + " remote=" + rrevision); + + // last > local (local mods) && remote > local (remote mods) = CONFLICT + // local > last = ignorable ERROR + // remote > local = fetch remote + // last > remote = send local + + if (lastUpdate != null && lastUpdate > rrevision) // local newer than remote + sendLocalData(context, user, password, lastUpdate); + else if (rrevision > lrevision) // remote changes + if (lastUpdate != null && lastUpdate > lrevision) { // local changes + Log.w("Cloud conflict" + + " lrevision=" + lrevision + " last=" + lastUpdate + " rrevision=" + rrevision); + if (manual) + if (lastUpdate >= rrevision) + sendLocalData(context, user, password, lastUpdate); + else + receiveRemoteData(context, user, password, lrevision, jstatus); + } else + receiveRemoteData(context, user, password, lrevision, jstatus); + else if (BuildConfig.DEBUG) + receiveRemoteData(context, user, password, lrevision - 1, jstatus); } else throw new IllegalArgumentException("Expected one status item"); } else { @@ -130,6 +150,224 @@ public class CloudSync { prefs.edit().putLong("cloud_last_sync", new Date().getTime()).apply(); } + private static Long updateSyncdata(Context context) throws IOException, JSONException { + DB db = DB.getInstance(context); + File dir = Helper.ensureExists(new File(context.getFilesDir(), "syncdata")); + + Long last = null; + + List accounts = db.account().getSynchronizingAccounts(null); + if (accounts != null) + for (EntityAccount account : accounts) + if (!TextUtils.isEmpty(account.uuid)) { + EntityAccount aexisting = null; + File afile = new File(dir, "account." + account.uuid + ".json"); + if (afile.exists()) + try (InputStream is = new FileInputStream(afile)) { + aexisting = EntityAccount.fromJSON(new JSONObject(Helper.readStream(is))); + } + + boolean apassword = (account.auth_type == ServiceAuthenticator.AUTH_TYPE_PASSWORD); + if (aexisting == null || + !EntityAccount.areEqual(account, aexisting, apassword, false)) + Helper.writeText(afile, account.toJSON().toString()); + + long atime = afile.lastModified(); + if (last == null || atime > last) + last = atime; + + List identities = db.identity().getIdentities(account.id); + if (identities != null) + for (EntityIdentity identity : identities) + if (!TextUtils.isEmpty(identity.uuid)) { + EntityIdentity iexisting = null; + File ifile = new File(dir, "identity." + identity.uuid + ".json"); + if (ifile.exists()) + try (InputStream is = new FileInputStream(ifile)) { + iexisting = EntityIdentity.fromJSON(new JSONObject(Helper.readStream(is))); + } + + boolean ipassword = (account.auth_type == ServiceAuthenticator.AUTH_TYPE_PASSWORD); + if (iexisting == null || + EntityIdentity.areEqual(identity, iexisting, ipassword, false)) + Helper.writeText(ifile, identity.toJSON().toString()); + + long itime = ifile.lastModified(); + if (last == null || itime > last) + last = itime; + } + } + + return last; + } + + private static void sendLocalData(Context context, String user, String password, long lrevision) + throws JSONException, GeneralSecurityException, IOException { + DB db = DB.getInstance(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + List accounts = db.account().getSynchronizingAccounts(null); + Log.i("Cloud accounts=" + (accounts == null ? null : accounts.size())); + if (accounts == null || accounts.size() == 0) { + Log.i("Cloud no accounts"); + return; + } + + JSONArray jupload = new JSONArray(); + + JSONArray jaccountuuidlist = new JSONArray(); + for (EntityAccount account : accounts) + if (!TextUtils.isEmpty(account.uuid)) { + jaccountuuidlist.put(account.uuid); + + JSONArray jidentitieuuids = new JSONArray(); + List identities = db.identity().getIdentities(account.id); + if (identities != null) + for (EntityIdentity identity : identities) + if (!TextUtils.isEmpty(identity.uuid)) { + jidentitieuuids.put(identity.uuid); + + JSONObject jidentitykv = new JSONObject(); + jidentitykv.put("key", "identity." + identity.uuid); + jidentitykv.put("val", identity.toJSON().toString()); + jidentitykv.put("rev", lrevision); + jupload.put(jidentitykv); + } + + JSONObject jaccount = account.toJSON(); + if (account.swipe_left != null && account.swipe_left > 0) { + EntityFolder f = db.folder().getFolder(account.swipe_left); + if (f != null) + jaccount.put("swipe_left_folder", f.name); + } + if (account.swipe_right != null && account.swipe_right > 0) { + EntityFolder f = db.folder().getFolder(account.swipe_right); + if (f != null) + jaccount.put("swipe_right_folder", f.name); + } + + JSONObject jaccountdata = new JSONObject(); + jaccountdata.put("account", jaccount); + jaccountdata.put("identities", jidentitieuuids); + + JSONObject jaccountkv = new JSONObject(); + jaccountkv.put("key", "account." + account.uuid); + jaccountkv.put("val", jaccountdata.toString()); + jaccountkv.put("rev", lrevision); + jupload.put(jaccountkv); + } + + JSONObject jaccountuuids = new JSONObject(); + jaccountuuids.put("uuids", jaccountuuidlist); + + JSONObject jstatus = new JSONObject(); + jstatus.put("sync.version", 1); + jstatus.put("app.version", BuildConfig.VERSION_CODE); + jstatus.put("accounts", jaccountuuids); + + JSONObject jstatuskv = new JSONObject(); + jstatuskv.put("key", "sync.status"); + jstatuskv.put("val", jstatus.toString()); + jstatuskv.put("rev", lrevision); + jupload.put(jstatuskv); + + JSONObject jrequest = new JSONObject(); + jrequest.put("items", jupload); + call(context, user, password, "write", jrequest); + + prefs.edit().putLong("cloud_revision", lrevision).apply(); + } + + private static void receiveRemoteData(Context context, String user, String password, long lrevision, JSONObject jstatus) + throws JSONException, GeneralSecurityException, IOException { + DB db = DB.getInstance(context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + // New revision + boolean updates = false; + JSONArray jdownload = new JSONArray(); + + // Get accounts + JSONObject jaccountstatus = jstatus.getJSONObject("accounts"); + JSONArray jaccountuuidlist = jaccountstatus.getJSONArray("uuids"); + for (int i = 0; i < jaccountuuidlist.length(); i++) { + String uuid = jaccountuuidlist.getString(i); + JSONObject jaccountkv = new JSONObject(); + jaccountkv.put("key", "account." + uuid); + jaccountkv.put("rev", lrevision); + jdownload.put(jaccountkv); + Log.i("Cloud account uuid=" + uuid); + } + + if (jdownload.length() > 0) { + Log.i("Cloud getting accounts"); + JSONObject jrequest = new JSONObject(); + jrequest.put("items", jdownload); + JSONObject jresponse = call(context, user, password, "sync", jrequest); + + // Process accounts + Log.i("Cloud processing accounts"); + JSONArray jitems = jresponse.getJSONArray("items"); + jdownload = new JSONArray(); + for (int i = 0; i < jitems.length(); i++) { + JSONObject jaccountkv = jitems.getJSONObject(i); + String value = jaccountkv.getString("val"); + long revision = jaccountkv.getLong("rev"); + + JSONObject jaccountdata = new JSONObject(value); + JSONObject jaccount = jaccountdata.getJSONObject("account"); + EntityAccount raccount = EntityAccount.fromJSON(jaccount); + EntityAccount laccount = db.account().getAccountByUUID(raccount.uuid); + + JSONArray jidentities = jaccountdata.getJSONArray("identities"); + Log.i("Cloud account " + raccount.uuid + "=" + + (laccount == null ? "insert" : + (EntityAccount.areEqual(raccount, laccount, laccount.auth_type == ServiceAuthenticator.AUTH_TYPE_PASSWORD, true) + ? "equal" : "update")) + + " rev=" + revision + + " identities=" + jidentities + + " size=" + value.length()); + + for (int j = 0; j < jidentities.length(); j++) { + JSONObject jidentitykv = new JSONObject(); + jidentitykv.put("key", "identity." + jidentities.getString(j)); + jidentitykv.put("rev", lrevision); + jdownload.put(jidentitykv); + } + } + + if (jdownload.length() > 0) { + // Get identities + Log.i("Cloud getting identities"); + jrequest.put("items", jdownload); + jresponse = call(context, user, password, "sync", jrequest); + + // Process identities + Log.i("Cloud processing identities"); + jitems = jresponse.getJSONArray("items"); + for (int i = 0; i < jitems.length(); i++) { + JSONObject jidentitykv = jitems.getJSONObject(i); + long revision = jidentitykv.getLong("rev"); + String value = jidentitykv.getString("val"); + JSONObject jidentity = new JSONObject(value); + EntityIdentity ridentity = EntityIdentity.fromJSON(jidentity); + EntityIdentity lidentity = db.identity().getIdentityByUUID(ridentity.uuid); + + Log.i("Cloud identity " + ridentity.uuid + "=" + + (lidentity == null ? "insert" : + (EntityIdentity.areEqual(ridentity, lidentity, lidentity.auth_type == ServiceAuthenticator.AUTH_TYPE_PASSWORD, true) + ? "equal" : "update")) + + " rev=" + revision + + " size=" + value.length()); + } + } + } + + prefs.edit().putLong("cloud_revision", lrevision).apply(); + + if (updates) + ServiceSynchronize.reload(context, null, true, "sync"); + } // Lower level public static JSONObject call(Context context, String user, String password, String command, JSONObject jrequest) diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index 05b9ab23b4..e2c583d2b2 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -422,55 +422,61 @@ public class EntityAccount extends EntityOrder implements Serializable { public boolean equals(Object obj) { if (obj instanceof EntityAccount) { EntityAccount other = (EntityAccount) obj; - return (Objects.equals(this.uuid, other.uuid) && - Objects.equals(this.order, other.order) && - this.protocol.equals(other.protocol) && - this.host.equals(other.host) && - this.encryption.equals(other.encryption) && - this.insecure == other.insecure && - this.port.equals(other.port) && - this.auth_type.equals(other.auth_type) && - this.user.equals(other.user) && - this.password.equals(other.password) && - Objects.equals(this.realm, other.realm) && - Objects.equals(this.name, other.name) && - Objects.equals(this.category, other.category) && - Objects.equals(this.color, other.color) && - Objects.equals(this.calendar, other.calendar) && - this.synchronize.equals(other.synchronize) && - this.primary.equals(other.primary) && - this.notify.equals(other.notify) && - this.browse.equals(other.browse) && - this.leave_on_server.equals(other.leave_on_server) && - this.leave_on_device.equals(other.leave_on_device) && - Objects.equals(this.max_messages, other.max_messages) && - this.auto_seen.equals(other.auto_seen) && - Objects.equals(this.swipe_left, other.swipe_left) && - Objects.equals(this.swipe_right, other.swipe_right) && - this.poll_interval.equals(other.poll_interval) && - this.partial_fetch == other.partial_fetch && - this.ignore_size == other.ignore_size && - this.use_date == other.use_date && - this.use_received == other.use_received && - this.unicode == other.unicode && - Objects.equals(this.conditions, other.conditions) && - Objects.equals(this.quota_usage, other.quota_usage) && - Objects.equals(this.quota_limit, other.quota_limit) && - Objects.equals(this.created, other.created) && - Objects.equals(this.tbd, other.tbd) && - Objects.equals(this.state, other.state) && - Objects.equals(this.warning, other.warning) && - Objects.equals(this.error, other.error) && - Objects.equals(this.last_connected, other.last_connected) && - Objects.equals(this.backoff_until, other.backoff_until) && - Objects.equals(this.max_size, other.max_size) && - Objects.equals(this.capabilities, other.capabilities) && - Objects.equals(this.capability_idle, other.capability_idle) && - Objects.equals(this.capability_utf8, other.capability_utf8)); + return areEqual(this, other, true, true); } else return false; } + public static boolean areEqual(EntityAccount a1, EntityAccount other, boolean auth, boolean state) { + return (Objects.equals(a1.uuid, other.uuid) && + Objects.equals(a1.order, other.order) && + a1.protocol.equals(other.protocol) && + a1.host.equals(other.host) && + a1.encryption.equals(other.encryption) && + a1.insecure == other.insecure && + a1.port.equals(other.port) && + a1.auth_type.equals(other.auth_type) && + Objects.equals(a1.provider, other.provider) && + a1.user.equals(other.user) && + (!auth || a1.password.equals(other.password)) && + Objects.equals(a1.certificate_alias, other.certificate_alias) && + Objects.equals(a1.realm, other.realm) && + Objects.equals(a1.name, other.name) && + Objects.equals(a1.category, other.category) && + Objects.equals(a1.color, other.color) && + Objects.equals(a1.calendar, other.calendar) && + a1.synchronize.equals(other.synchronize) && + a1.primary.equals(other.primary) && + a1.notify.equals(other.notify) && + a1.browse.equals(other.browse) && + a1.leave_on_server.equals(other.leave_on_server) && + a1.leave_on_device.equals(other.leave_on_device) && + Objects.equals(a1.max_messages, other.max_messages) && + a1.auto_seen.equals(other.auto_seen) && + Objects.equals(a1.swipe_left, other.swipe_left) && + Objects.equals(a1.swipe_right, other.swipe_right) && + a1.poll_interval.equals(other.poll_interval) && + a1.partial_fetch == other.partial_fetch && + a1.ignore_size == other.ignore_size && + a1.use_date == other.use_date && + a1.use_received == other.use_received && + a1.unicode == other.unicode && + Objects.equals(a1.conditions, other.conditions) && + (!state || Objects.equals(a1.quota_usage, other.quota_usage)) && + (!state || Objects.equals(a1.quota_limit, other.quota_limit)) && + (!state || Objects.equals(a1.created, other.created)) && + Objects.equals(a1.tbd, other.tbd) && + (!state || Objects.equals(a1.state, other.state)) && + (!state || Objects.equals(a1.warning, other.warning)) && + (!state || Objects.equals(a1.error, other.error)) && + (!state || Objects.equals(a1.last_connected, other.last_connected)) && + (!state || Objects.equals(a1.backoff_until, other.backoff_until)) && + (!state || Objects.equals(a1.max_size, other.max_size)) && + (!state || Objects.equals(a1.capabilities, other.capabilities)) && + (!state || Objects.equals(a1.capability_idle, other.capability_idle)) && + (!state || Objects.equals(a1.capability_utf8, other.capability_utf8))); + } + @Override Comparator getComparator(final Context context) { final Collator collator = Collator.getInstance(Locale.getDefault()); diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java index 78c1165017..710424ae8a 100644 --- a/app/src/main/java/eu/faircode/email/EntityIdentity.java +++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java @@ -323,43 +323,49 @@ public class EntityIdentity { public boolean equals(Object obj) { if (obj instanceof EntityIdentity) { EntityIdentity other = (EntityIdentity) obj; - return (Objects.equals(this.uuid, other.uuid) && - this.name.equals(other.name) && - this.email.equals(other.email) && - this.account.equals(other.account) && - Objects.equals(this.display, other.display) && - Objects.equals(this.color, other.color) && - Objects.equals(this.signature, other.signature) && - this.host.equals(other.host) && - this.encryption.equals(other.encryption) && - this.insecure.equals(other.insecure) && - this.port.equals(other.port) && - this.auth_type.equals(other.auth_type) && - this.user.equals(other.user) && - this.password.equals(other.password) && - Objects.equals(this.realm, other.realm) && - this.use_ip == other.use_ip && - Objects.equals(this.ehlo, other.ehlo) && - this.synchronize.equals(other.synchronize) && - this.primary.equals(other.primary) && - this.self.equals(other.self) && - this.sender_extra.equals(other.sender_extra) && - this.sender_extra_name.equals(other.sender_extra_name) && - Objects.equals(this.sender_extra_regex, other.sender_extra_regex) && - Objects.equals(this.replyto, other.replyto) && - Objects.equals(this.cc, other.cc) && - Objects.equals(this.bcc, other.bcc) && - Objects.equals(this.internal, other.internal) && - Objects.equals(this.sign_key, other.sign_key) && - Objects.equals(this.sign_key_alias, other.sign_key_alias) && - Objects.equals(this.state, other.state) && - Objects.equals(this.error, other.error) && - Objects.equals(this.last_connected, other.last_connected) && - Objects.equals(this.max_size, other.max_size)); + return areEqual(this, other, true, true); } else return false; } + public static boolean areEqual(EntityIdentity i1, EntityIdentity other, boolean auth, boolean state) { + return (Objects.equals(i1.uuid, other.uuid) && + i1.name.equals(other.name) && + i1.email.equals(other.email) && + Objects.equals(i1.account, other.account) && + Objects.equals(i1.display, other.display) && + Objects.equals(i1.color, other.color) && + Objects.equals(i1.signature, other.signature) && + i1.host.equals(other.host) && + i1.encryption.equals(other.encryption) && + i1.insecure.equals(other.insecure) && + i1.port.equals(other.port) && + i1.auth_type.equals(other.auth_type) && + Objects.equals(i1.provider, other.provider) && + i1.user.equals(other.user) && + (!auth || i1.password.equals(other.password)) && + Objects.equals(i1.certificate_alias, other.certificate_alias) && + Objects.equals(i1.realm, other.realm) && + i1.use_ip == other.use_ip && + Objects.equals(i1.ehlo, other.ehlo) && + i1.synchronize.equals(other.synchronize) && + i1.primary.equals(other.primary) && + i1.self.equals(other.self) && + i1.sender_extra.equals(other.sender_extra) && + i1.sender_extra_name.equals(other.sender_extra_name) && + Objects.equals(i1.sender_extra_regex, other.sender_extra_regex) && + Objects.equals(i1.replyto, other.replyto) && + Objects.equals(i1.cc, other.cc) && + Objects.equals(i1.bcc, other.bcc) && + Objects.equals(i1.internal, other.internal) && + Objects.equals(i1.sign_key, other.sign_key) && + Objects.equals(i1.sign_key_alias, other.sign_key_alias) && + (!state || Objects.equals(i1.state, other.state)) && + (!state || Objects.equals(i1.error, other.error)) && + (!state || Objects.equals(i1.last_connected, other.last_connected)) && + (!state || Objects.equals(i1.max_size, other.max_size))); + } + String getDisplayName() { return (display == null ? name : display); } diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java index 916d703906..67ea3adfab 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsBackup.java @@ -118,7 +118,6 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere private TextInputLayout tilPassword; private Button btnLogin; private TextView tvLogin; - private CheckBox cbBlockedSenders; private ImageButton ibSync; private TextView tvLastSync; private Button btnLogout; @@ -159,7 +158,6 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere tilPassword = view.findViewById(R.id.tilPassword); btnLogin = view.findViewById(R.id.btnLogin); tvLogin = view.findViewById(R.id.tvLogin); - cbBlockedSenders = view.findViewById(R.id.cbBlockedSenders); ibSync = view.findViewById(R.id.ibSync); tvLastSync = view.findViewById(R.id.tvLastSync); btnLogout = view.findViewById(R.id.btnLogout); @@ -206,13 +204,6 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere } }); - cbBlockedSenders.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - prefs.edit().putBoolean("cloud_sync_blocked_senders", isChecked).apply(); - } - }); - ibSync.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -238,7 +229,6 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere Helper.linkPro(tvCloudPro); prefs.registerOnSharedPreferenceChangeListener(this); - cbBlockedSenders.setChecked(prefs.getBoolean("cloud_sync_blocked_senders", true)); onSharedPreferenceChanged(prefs, null); return view; @@ -1455,9 +1445,15 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere } private void onCloudLogin() { + final Context context = getContext(); String username = etUser.getText().toString().trim(); String password = tilPassword.getEditText().getText().toString(); + if (!ActivityBilling.isPro(context)) { + context.startActivity(new Intent(context, ActivityBilling.class)); + return; + } + if (TextUtils.isEmpty(username)) { etUser.requestFocus(); return; @@ -1468,7 +1464,7 @@ public class FragmentOptionsBackup extends FragmentBase implements SharedPrefere return; } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit() .putString("cloud_user", username) .putString("cloud_password", password) diff --git a/app/src/main/res/layout/fragment_options_backup.xml b/app/src/main/res/layout/fragment_options_backup.xml index 4859fa30a2..e9af48bd21 100644 --- a/app/src/main/res/layout/fragment_options_backup.xml +++ b/app/src/main/res/layout/fragment_options_backup.xml @@ -303,22 +303,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvRegister" /> - - Space separated This is an experimental feature! + All data is encrypted end-to-end and the cloud server will never see the username, password and data Login Logging in for the first time will automatically create an account Invalid username or password - Sync blocked senders Last sync: %1$s - Update settings Logout Wipe cloud data on logout