diff --git a/app/src/main/java/org/kontalk/Kontalk.java b/app/src/main/java/org/kontalk/Kontalk.java index b4a8a6f5f..0b0fdb2d3 100644 --- a/app/src/main/java/org/kontalk/Kontalk.java +++ b/app/src/main/java/org/kontalk/Kontalk.java @@ -50,6 +50,7 @@ import org.kontalk.authenticator.Authenticator; import org.kontalk.authenticator.MyAccount; import org.kontalk.client.EndpointServer; +import org.kontalk.client.ServerList; import org.kontalk.crypto.PGP; import org.kontalk.crypto.PersonalKey; import org.kontalk.data.Contact; @@ -57,6 +58,7 @@ import org.kontalk.reporting.ReportingManager; import org.kontalk.service.DownloadService; import org.kontalk.service.NetworkStateReceiver; +import org.kontalk.service.ServerListUpdater; import org.kontalk.service.SystemBootStartup; import org.kontalk.service.UploadService; import org.kontalk.service.msgcenter.IPushService; @@ -241,10 +243,20 @@ public void onAccountsUpdated(Account[] accounts) { // TODO remove after a few release iterations if (Authenticator.getDefaultServiceTermsURL(this) == null) { + // default service terms url am.setUserData(account.getSystemAccount(), Authenticator.DATA_SERVICE_TERMS_URL, getString(R.string.help_default_KPN_service_terms_url)); } + + // TODO remove after a few release iterations + if (Authenticator.getDefaultServerList(this) == null) { + // default server list + ServerList list = ServerListUpdater.getCurrentList(this); + am.setUserData(account.getSystemAccount(), + Authenticator.DATA_SERVER_LIST, + SystemUtils.serializeProperties(list.toProperties())); + } } else { // ensure everything is cleared up diff --git a/app/src/main/java/org/kontalk/authenticator/Authenticator.java b/app/src/main/java/org/kontalk/authenticator/Authenticator.java index fda6446e4..24b55b81a 100644 --- a/app/src/main/java/org/kontalk/authenticator/Authenticator.java +++ b/app/src/main/java/org/kontalk/authenticator/Authenticator.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Map; +import java.util.Properties; import java.util.concurrent.TimeUnit; import org.bouncycastle.openpgp.PGPException; @@ -50,12 +51,14 @@ import org.kontalk.BuildConfig; import org.kontalk.R; import org.kontalk.client.EndpointServer; +import org.kontalk.client.ServerList; import org.kontalk.crypto.PGP; import org.kontalk.crypto.PersonalKey; import org.kontalk.crypto.PersonalKeyExporter; import org.kontalk.provider.Keyring; import org.kontalk.ui.MainActivity; import org.kontalk.ui.NumberValidation; +import org.kontalk.util.SystemUtils; /** @@ -72,6 +75,7 @@ public class Authenticator extends AbstractAccountAuthenticator { public static final String DATA_NAME = "org.kontalk.key.name"; public static final String DATA_USER_PASSPHRASE = "org.kontalk.userPassphrase"; public static final String DATA_SERVER_URI = "org.kontalk.server"; + public static final String DATA_SERVER_LIST = "org.kontalk.serverList"; public static final String DATA_SERVICE_TERMS_URL = "org.kontalk.serviceTermsURL"; @SuppressWarnings("WeakerAccess") @@ -118,12 +122,34 @@ static EndpointServer getServer(AccountManager am, Account account) { public static String getDefaultServiceTermsURL(Context context) { AccountManager am = AccountManager.get(context); Account account = getDefaultSystemAccount(am); - return getServiceTermsURL(am, account); + return account != null ? getServiceTermsURL(am, account) : null; } static String getServiceTermsURL(AccountManager am, Account account) { - return account != null ? - am.getUserData(account, Authenticator.DATA_SERVICE_TERMS_URL) : null; + return am.getUserData(account, DATA_SERVICE_TERMS_URL); + } + + /** @deprecated Still used in {@link org.kontalk.Kontalk#onCreate()}. */ + @Deprecated + public static ServerList getDefaultServerList(Context context) { + AccountManager am = AccountManager.get(context); + Account account = getDefaultSystemAccount(am); + return account != null ? getServerList(am, account) : null; + } + + static ServerList getServerList(AccountManager am, Account account) { + String serverListData = am.getUserData(account, DATA_SERVER_LIST); + if (serverListData != null) { + Properties props = SystemUtils.unserializeProperties(serverListData); + try { + return ServerList.fromProperties(props); + } + catch (IOException e) { + // this isn't right... + return null; + } + } + return null; } static PersonalKey loadPersonalKey(AccountManager am, Account account) diff --git a/app/src/main/java/org/kontalk/authenticator/MyAccount.kt b/app/src/main/java/org/kontalk/authenticator/MyAccount.kt index 6f4f2cbbc..dc4ba2a94 100644 --- a/app/src/main/java/org/kontalk/authenticator/MyAccount.kt +++ b/app/src/main/java/org/kontalk/authenticator/MyAccount.kt @@ -22,6 +22,7 @@ import android.accounts.Account import android.accounts.AccountManager import org.jxmpp.jid.BareJid import org.kontalk.client.EndpointServer +import org.kontalk.client.ServerList import org.kontalk.crypto.PersonalKey import org.kontalk.util.XMPPUtils @@ -37,6 +38,10 @@ class MyAccount (val systemAccount: Account, private val accountManager: Account Authenticator.getServer(accountManager, systemAccount) } + val serverList: ServerList by lazy { + Authenticator.getServerList(accountManager, systemAccount) + } + val serviceTermsURL: String? by lazy { Authenticator.getServiceTermsURL(accountManager, systemAccount) } @@ -59,6 +64,9 @@ class MyAccount (val systemAccount: Account, private val accountManager: Account fun isSelfJID(bareJid: BareJid): Boolean = bareJid.equals(selfJID) + // TODO + fun isNetworkJID(bareJid: BareJid): Boolean = true + /* compatibility interface for android.accounts.Account */ fun getName(): String { diff --git a/app/src/main/java/org/kontalk/client/EndpointServer.java b/app/src/main/java/org/kontalk/client/EndpointServer.java index 29a1f9a8e..ee5563d4f 100644 --- a/app/src/main/java/org/kontalk/client/EndpointServer.java +++ b/app/src/main/java/org/kontalk/client/EndpointServer.java @@ -18,6 +18,8 @@ package org.kontalk.client; +import java.util.Collections; +import java.util.Date; import java.util.regex.Pattern; @@ -110,6 +112,11 @@ public String getNetwork() { * Interface for providing a server. */ public interface EndpointServerProvider { + /** + * Returns a server list. + */ + ServerList list(); + /** * Returns the next server that hasn't been picked yet. */ @@ -137,6 +144,25 @@ public SingleServerProvider(EndpointServer server) { mProvided = server; } + @Override + public ServerList list() { + return new ServerList(new Date(), Collections.singletonList(getProvidedServer())); + } + + private EndpointServer getProvidedServer() { + if (mProvided == null) { + try { + return new EndpointServer(mUri); + } + catch (Exception e) { + // custom is not valid + return null; + } + } + + return mProvided; + } + @Override public EndpointServer next() { if (mCalled) { @@ -144,17 +170,7 @@ public EndpointServer next() { } else { mCalled = true; - if (mProvided == null) { - try { - return new EndpointServer(mUri); - } - catch (Exception e) { - // custom is not valid - return null; - } - } - - return mProvided; + return getProvidedServer(); } } diff --git a/app/src/main/java/org/kontalk/client/ServerList.java b/app/src/main/java/org/kontalk/client/ServerList.java index 8b4aa261b..6dfe49a37 100644 --- a/app/src/main/java/org/kontalk/client/ServerList.java +++ b/app/src/main/java/org/kontalk/client/ServerList.java @@ -18,11 +18,16 @@ package org.kontalk.client; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Locale; +import java.util.Properties; import java.util.Random; @@ -32,6 +37,8 @@ */ public class ServerList extends ArrayList { private static final long serialVersionUID = 1L; + private static DateFormat sTimestampFormat = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US); private final Date mDate; @@ -57,6 +64,36 @@ public EndpointServer random() { get(mSeed.nextInt(size())) : null; } + public Properties toProperties() { + Properties prop = new Properties(); + Date now = new Date(); + prop.setProperty("timestamp", sTimestampFormat.format(now)); + + for (int i = 0; i < size(); i++) { + String item = get(i).toString(); + prop.setProperty("server" + (i + 1), item); + } + return prop; + } + + public static ServerList fromProperties(Properties props) throws IOException { + try { + Date date = sTimestampFormat.parse(props.getProperty("timestamp")); + ServerList list = new ServerList(date); + int i = 1; + String server; + while ((server = props.getProperty("server" + i)) != null) { + list.add(new EndpointServer(server)); + i++; + } + + return list; + } + catch (Exception e) { + throw new IOException("parse error", e); + } + } + /** A simple server provider backed by a server list. */ public static class ServerListProvider implements EndpointServer.EndpointServerProvider { private ServerList mList; @@ -67,6 +104,14 @@ public ServerListProvider(ServerList list) { mUsed = new LinkedList<>(); } + @Override + public ServerList list() { + ServerList list = new ServerList(mList.getDate()); + list.addAll(mList); + list.addAll(mUsed); + return list; + } + @Override public EndpointServer next() { if (mList.size() > 0) { diff --git a/app/src/main/java/org/kontalk/service/ServerListUpdater.java b/app/src/main/java/org/kontalk/service/ServerListUpdater.java index c21927bdd..d5896a424 100644 --- a/app/src/main/java/org/kontalk/service/ServerListUpdater.java +++ b/app/src/main/java/org/kontalk/service/ServerListUpdater.java @@ -24,10 +24,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Locale; import java.util.Properties; import org.greenrobot.eventbus.EventBus; @@ -58,14 +55,12 @@ * * @author Daniele Ricci */ +// TODO downloaded list should be saved in account user data public class ServerListUpdater { private static final String TAG = ServerListUpdater.class.getSimpleName(); private static ServerList sCurrentList; - private static DateFormat sTimestampFormat = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US); - private final Context mContext; private UpdaterListener mListener; @@ -137,17 +132,16 @@ public void onServerList(ServerListEvent event) { unregisterReceiver(); if (event.servers != null && event.servers.length > 0) { - Properties prop = new Properties(); Date now = new Date(); ServerList list = new ServerList(now); - prop.setProperty("timestamp", sTimestampFormat.format(now)); for (int i = 0; i < event.servers.length; i++) { String item = event.servers[i]; - prop.setProperty("server" + (i + 1), item); list.add(new EndpointServer(item)); } + Properties prop = list.toProperties(); + OutputStream out = null; try { out = new FileOutputStream(getCachedListFile(mContext)); @@ -196,22 +190,7 @@ private static File getCachedListFile(Context context) { private static ServerList parseList(InputStream in) throws IOException { Properties prop = new Properties(); prop.load(in); - - try { - Date date = sTimestampFormat.parse(prop.getProperty("timestamp")); - ServerList list = new ServerList(date); - int i = 1; - String server; - while ((server = prop.getProperty("server" + i)) != null) { - list.add(new EndpointServer(server)); - i++; - } - - return list; - } - catch (Exception e) { - throw new IOException("parse error", e); - } + return ServerList.fromProperties(prop); } private static ServerList parseBuiltinList(Context context) throws IOException { diff --git a/app/src/main/java/org/kontalk/service/registration/RegistrationService.java b/app/src/main/java/org/kontalk/service/registration/RegistrationService.java index 2ba4d6df1..26258511d 100644 --- a/app/src/main/java/org/kontalk/service/registration/RegistrationService.java +++ b/app/src/main/java/org/kontalk/service/registration/RegistrationService.java @@ -94,6 +94,7 @@ import org.kontalk.authenticator.MyAccount; import org.kontalk.client.Account; import org.kontalk.client.EndpointServer; +import org.kontalk.client.ServerList; import org.kontalk.client.SmackInitializer; import org.kontalk.crypto.PGP; import org.kontalk.crypto.PGPUidMismatchException; @@ -128,6 +129,7 @@ import org.kontalk.service.registration.event.VerificationRequestedEvent; import org.kontalk.util.EventBusIndex; import org.kontalk.util.Preferences; +import org.kontalk.util.SystemUtils; import org.kontalk.util.XMPPUtils; @@ -260,7 +262,6 @@ public static final class CurrentState { public Map trustedKeys; /** Will be true if the state was restored from preferences. */ - // do not copy public boolean restored; CurrentState() { @@ -471,6 +472,16 @@ private CurrentState restoreState() { } } + String serverProviderData = Preferences.getString("registration_serverprovider", null); + Properties serverProviderProps = SystemUtils.unserializeProperties(serverProviderData); + try { + state.serverProvider = new ServerList + .ServerListProvider(ServerList.fromProperties(serverProviderProps)); + } + catch (IOException e) { + throw new IllegalStateException("invalid server provider", e); + } + String sender = Preferences.getString("registration_sender", null); String brandImage = Preferences.getString("registration_brandimage", null); String brandLink = Preferences.getString("registration_brandlink", null); @@ -530,6 +541,7 @@ private static void saveState(String sender, String brandImage, String brandLink .putString("registration_key", state.key != null ? state.key.toBase64() : null) .putString("registration_passphrase", state.passphrase) .putString("registration_server", state.server.toString()) + .putString("registration_serverprovider", SystemUtils.serializeProperties(state.serverProvider.list().toProperties())) .putString("registration_sender", sender) .putString("registration_challenge", state.challenge) .putString("registration_brandimage", brandImage) @@ -555,6 +567,7 @@ public static void clearSavedState() { .remove("registration_key") .remove("registration_passphrase") .remove("registration_server") + .remove("registration_serverprovider") .remove("registration_sender") .remove("registration_challenge") .remove("registration_brandimage") @@ -1260,7 +1273,8 @@ private void createAccount() { AccountManager.get(this).removeAccount(account, new AccountRemovalCallback(this, account, cstate.passphrase, cstate.privateKey, cstate.publicKey, cstate.key.getBridgeCertificate().getEncoded(), - cstate.displayName, cstate.server.toString(), cstate.termsUrl, cstate.trustedKeys), + cstate.displayName, cstate.server.toString(), cstate.termsUrl, cstate.trustedKeys, + cstate.serverProvider.list()), null); } catch (CertificateEncodingException e) { @@ -1648,11 +1662,12 @@ private static class AccountRemovalCallback implements AccountManagerCallback trustedKeys; + private final ServerList serverList; AccountRemovalCallback(Context context, android.accounts.Account account, String passphrase, byte[] privateKeyData, byte[] publicKeyData, byte[] bridgeCertData, String name, String serverUri, String serviceTermsUrl, - Map trustedKeys) { + Map trustedKeys, ServerList serverList) { this.context = context.getApplicationContext(); this.account = account; this.passphrase = passphrase; @@ -1663,6 +1678,7 @@ private static class AccountRemovalCallback implements AccountManagerCallback result) { data.putString(Authenticator.DATA_NAME, name); data.putString(Authenticator.DATA_SERVER_URI, serverUri); data.putString(Authenticator.DATA_SERVICE_TERMS_URL, serviceTermsUrl); + data.putString(Authenticator.DATA_SERVER_LIST, SystemUtils.serializeProperties(serverList.toProperties())); // this is the password to the private key am.addAccountExplicitly(account, passphrase, data); @@ -1694,6 +1711,7 @@ public void run(AccountManagerFuture result) { am.setUserData(account, Authenticator.DATA_NAME, data.getString(Authenticator.DATA_NAME)); am.setUserData(account, Authenticator.DATA_SERVER_URI, data.getString(Authenticator.DATA_SERVER_URI)); am.setUserData(account, Authenticator.DATA_SERVICE_TERMS_URL, data.getString(Authenticator.DATA_SERVICE_TERMS_URL)); + am.setUserData(account, Authenticator.DATA_SERVER_LIST, data.getString(Authenticator.DATA_SERVER_LIST)); // Set contacts sync for this account. ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); diff --git a/app/src/main/java/org/kontalk/ui/prefs/NetworkFragment.java b/app/src/main/java/org/kontalk/ui/prefs/NetworkFragment.java index 4cc29253f..41f501e7a 100644 --- a/app/src/main/java/org/kontalk/ui/prefs/NetworkFragment.java +++ b/app/src/main/java/org/kontalk/ui/prefs/NetworkFragment.java @@ -71,6 +71,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { }); // server list last update timestamp + // FIXME move to account preferences final Preference updateServerList = findPreference("pref_update_server_list"); updateServerList.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override diff --git a/app/src/main/java/org/kontalk/util/SystemUtils.java b/app/src/main/java/org/kontalk/util/SystemUtils.java index d067606f4..5739c1eaf 100644 --- a/app/src/main/java/org/kontalk/util/SystemUtils.java +++ b/app/src/main/java/org/kontalk/util/SystemUtils.java @@ -22,9 +22,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.StringReader; +import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.List; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -346,6 +349,29 @@ public static void close(Closeable object) { } } + public static String serializeProperties(Properties properties) { + try { + StringWriter writer = new StringWriter(); + properties.store(writer, null); + return writer.toString(); + } + catch (IOException e) { + throw new AssertionError("this can't happen"); + } + } + + public static Properties unserializeProperties(String data) { + try { + StringReader reader = new StringReader(data); + Properties properties = new Properties(); + properties.load(reader); + return properties; + } + catch (IOException e) { + throw new AssertionError("this can't happen"); + } + } + /** * Closes the given stream, ignoring any errors. * This method can be safely deleted once we'll have min SDK set to 19, diff --git a/app/src/main/res/xml/preferences_network.xml b/app/src/main/res/xml/preferences_network.xml index 25cc61545..b97b603c0 100644 --- a/app/src/main/res/xml/preferences_network.xml +++ b/app/src/main/res/xml/preferences_network.xml @@ -18,6 +18,7 @@ +