diff options
author | Daniel Gultsch <daniel.gultsch@rwth-aachen.de> | 2014-04-03 23:50:48 +0200 |
---|---|---|
committer | Daniel Gultsch <daniel.gultsch@rwth-aachen.de> | 2014-04-03 23:50:48 +0200 |
commit | bd9dba1a69e2a5523b4d0d7e445d10b6197c3329 (patch) | |
tree | 90bdae99830b539e57c1301d4f5be843a07cd7cf | |
parent | 91aeffae1b94766dd89d061b55a65741ba42be66 (diff) | |
parent | a0fc1c6c77aea1d99078706cf2bee1c5efd246a4 (diff) |
Merge branch 'compression' of https://github.com/rtreffer/Conversations into rtreffer-compression
-rw-r--r-- | res/layout/edit_account_dialog.xml | 10 | ||||
-rw-r--r-- | src/eu/siacs/conversations/entities/Account.java | 1 | ||||
-rw-r--r-- | src/eu/siacs/conversations/persistance/DatabaseBackend.java | 11 | ||||
-rw-r--r-- | src/eu/siacs/conversations/ui/EditAccount.java | 10 | ||||
-rw-r--r-- | src/eu/siacs/conversations/utils/UIHelper.java | 2 | ||||
-rw-r--r-- | src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java | 52 | ||||
-rw-r--r-- | src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java | 89 | ||||
-rw-r--r-- | src/eu/siacs/conversations/xml/TagWriter.java | 8 | ||||
-rw-r--r-- | src/eu/siacs/conversations/xml/XmlReader.java | 6 | ||||
-rw-r--r-- | src/eu/siacs/conversations/xmpp/XmppConnection.java | 60 |
10 files changed, 223 insertions, 26 deletions
diff --git a/res/layout/edit_account_dialog.xml b/res/layout/edit_account_dialog.xml index 2b1f95ab..0a86039d 100644 --- a/res/layout/edit_account_dialog.xml +++ b/res/layout/edit_account_dialog.xml @@ -42,16 +42,6 @@ android:hint="Password" android:fontFamily="sans-serif" /> - - - <CheckBox - android:id="@+id/account_usetls" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Use Transport Layer Security (TLS)" - android:checked="true"/> - - <CheckBox android:id="@+id/edit_account_register_new" android:layout_width="wrap_content" diff --git a/src/eu/siacs/conversations/entities/Account.java b/src/eu/siacs/conversations/entities/Account.java index 7c12c69b..41132b1f 100644 --- a/src/eu/siacs/conversations/entities/Account.java +++ b/src/eu/siacs/conversations/entities/Account.java @@ -30,6 +30,7 @@ public class Account extends AbstractEntity{ public static final int OPTION_USETLS = 0; public static final int OPTION_DISABLED = 1; public static final int OPTION_REGISTER = 2; + public static final int OPTION_USECOMPRESSION = 3; public static final int STATUS_CONNECTING = 0; public static final int STATUS_DISABLED = -2; diff --git a/src/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/eu/siacs/conversations/persistance/DatabaseBackend.java index 434d6779..4c0d6216 100644 --- a/src/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -23,7 +23,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; public DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -66,9 +66,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { } @Override - public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { - // TODO Auto-generated method stub - + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 2 && newVersion >= 2) { + // enable compression by default. + db.execSQL("update " + Account.TABLENAME + + " set " + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); + } } public static synchronized DatabaseBackend getInstance(Context context) { diff --git a/src/eu/siacs/conversations/ui/EditAccount.java b/src/eu/siacs/conversations/ui/EditAccount.java index 080b09ea..8b1c0fa6 100644 --- a/src/eu/siacs/conversations/ui/EditAccount.java +++ b/src/eu/siacs/conversations/ui/EditAccount.java @@ -43,7 +43,6 @@ public class EditAccount extends DialogFragment { final EditText jidText = (EditText) view.findViewById(R.id.account_jid); final TextView confirmPwDesc = (TextView) view .findViewById(R.id.account_confirm_password_desc); - CheckBox useTLS = (CheckBox) view.findViewById(R.id.account_usetls); final EditText password = (EditText) view .findViewById(R.id.account_password); @@ -57,11 +56,6 @@ public class EditAccount extends DialogFragment { if (account != null) { jidText.setText(account.getJid()); password.setText(account.getPassword()); - if (account.isOptionSet(Account.OPTION_USETLS)) { - useTLS.setChecked(true); - } else { - useTLS.setChecked(false); - } Log.d("xmppService","mein debugger. account != null"); if (account.isOptionSet(Account.OPTION_REGISTER)) { registerAccount.setChecked(true); @@ -121,7 +115,6 @@ public class EditAccount extends DialogFragment { EditText passwordEdit = (EditText) d .findViewById(R.id.account_password); String password = passwordEdit.getText().toString(); - CheckBox useTLS = (CheckBox) d.findViewById(R.id.account_usetls); CheckBox register = (CheckBox) d.findViewById(R.id.edit_account_register_new); String username; String server; @@ -139,8 +132,9 @@ public class EditAccount extends DialogFragment { account.setServer(server); } else { account = new Account(username, server, password); + account.setOption(Account.OPTION_USETLS, true); + account.setOption(Account.OPTION_USECOMPRESSION, true); } - account.setOption(Account.OPTION_USETLS, useTLS.isChecked()); account.setOption(Account.OPTION_REGISTER, register.isChecked()); if (listener != null) { listener.onAccountEdited(account); diff --git a/src/eu/siacs/conversations/utils/UIHelper.java b/src/eu/siacs/conversations/utils/UIHelper.java index c0e5eb7c..9b9fd8a9 100644 --- a/src/eu/siacs/conversations/utils/UIHelper.java +++ b/src/eu/siacs/conversations/utils/UIHelper.java @@ -66,7 +66,7 @@ public class UIHelper { } private static Bitmap getUnknownContactPicture(String name, int size) { - String firstLetter = name.substring(0, 1).toUpperCase(Locale.US); + String firstLetter = (name.length() > 0) ? name.substring(0, 1).toUpperCase(Locale.US) : " "; int holoColors[] = { 0xFF1da9da, 0xFFb368d9, 0xFF83b600, 0xFFffa713, 0xFFe92727 }; diff --git a/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java b/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java new file mode 100644 index 00000000..2eebf6f4 --- /dev/null +++ b/src/eu/siacs/conversations/utils/zlib/ZLibInputStream.java @@ -0,0 +1,52 @@ +package eu.siacs.conversations.utils.zlib; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + * ZLibInputStream is a zlib and input stream compatible version of an + * InflaterInputStream. This class solves the incompatibility between + * {@link InputStream#available()} and {@link InflaterInputStream#available()}. + */ +public class ZLibInputStream extends InflaterInputStream { + + /** + * Construct a ZLibInputStream, reading data from the underlying stream. + * + * @param is The {@code InputStream} to read data from. + * @throws IOException If an {@code IOException} occurs. + */ + public ZLibInputStream(InputStream is) throws IOException { + super(is, new Inflater(), 512); + } + + /** + * Provide a more InputStream compatible version of available. + * A return value of 1 means that it is likly to read one byte without + * blocking, 0 means that the system is known to block for more input. + * + * @return 0 if no data is available, 1 otherwise + * @throws IOException + */ + @Override + public int available() throws IOException { + /* This is one of the funny code blocks. + * InflaterInputStream.available violates the contract of + * InputStream.available, which breaks kXML2. + * + * I'm not sure who's to blame, oracle/sun for a broken api or the + * google guys for mixing a sun bug with a xml reader that can't handle + * it.... + * + * Anyway, this simple if breaks suns distorted reality, but helps + * to use the api as intended. + */ + if (inf.needsInput()) { + return 0; + } + return super.available(); + } + +} diff --git a/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java b/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java new file mode 100644 index 00000000..f2bff9fd --- /dev/null +++ b/src/eu/siacs/conversations/utils/zlib/ZLibOutputStream.java @@ -0,0 +1,89 @@ +package eu.siacs.conversations.utils.zlib; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** + * <p>Android 2.2 includes Java7 FLUSH_SYNC option, which will be used by this + * Implementation, preferable via reflection. The @hide was remove in API level + * 19. This class might thus go away in the future.</p> + * <p>Please use {@link ZLibOutputStream#SUPPORTED} to check for flush + * compatibility.</p> + */ +public class ZLibOutputStream extends DeflaterOutputStream { + + /** + * The reflection based flush method. + */ + + private final static Method method; + /** + * SUPPORTED is true if a flush compatible method exists. + */ + public final static boolean SUPPORTED; + + /** + * Static block to initialize {@link #SUPPORTED} and {@link #method}. + */ + static { + Method m = null; + try { + m = Deflater.class.getMethod("deflate", byte[].class, int.class, int.class, int.class); + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } + method = m; + SUPPORTED = (method != null); + } + + /** + * Create a new ZLib compatible output stream wrapping the given low level + * stream. ZLib compatiblity means we will send a zlib header. + * @param os OutputStream The underlying stream. + * @throws IOException In case of a lowlevel transfer problem. + * @throws NoSuchAlgorithmException In case of a {@link Deflater} error. + */ + public ZLibOutputStream(OutputStream os) throws IOException, + NoSuchAlgorithmException { + super(os, new Deflater(Deflater.BEST_COMPRESSION)); + } + + /** + * Flush the given stream, preferring Java7 FLUSH_SYNC if available. + * @throws IOException In case of a lowlevel exception. + */ + @Override + public void flush() throws IOException { + if (!SUPPORTED) { + super.flush(); + return; + } + int count = 0; + if (!def.needsInput()) { + do { + count = def.deflate(buf, 0, buf.length); + out.write(buf, 0, count); + } while (count > 0); + out.flush(); + } + try { + do { + count = (Integer) method.invoke(def, buf, 0, buf.length, 2); + out.write(buf, 0, count); + } while (count > 0); + } catch (IllegalArgumentException e) { + throw new IOException("Can't flush"); + } catch (IllegalAccessException e) { + throw new IOException("Can't flush"); + } catch (InvocationTargetException e) { + throw new IOException("Can't flush"); + } + super.flush(); + } + +} diff --git a/src/eu/siacs/conversations/xml/TagWriter.java b/src/eu/siacs/conversations/xml/TagWriter.java index f06664fd..d945b47b 100644 --- a/src/eu/siacs/conversations/xml/TagWriter.java +++ b/src/eu/siacs/conversations/xml/TagWriter.java @@ -9,6 +9,7 @@ import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; public class TagWriter { + private OutputStream plainOutputStream; private OutputStreamWriter outputStream; private boolean finshed = false; private LinkedBlockingQueue<AbstractStanza> writeQueue = new LinkedBlockingQueue<AbstractStanza>(); @@ -37,9 +38,14 @@ public class TagWriter { } public void setOutputStream(OutputStream out) { + this.plainOutputStream = out; this.outputStream = new OutputStreamWriter(out); } - + + public OutputStream getOutputStream() { + return this.plainOutputStream; + } + public TagWriter beginDocument() throws IOException { outputStream.write("<?xml version='1.0'?>"); outputStream.flush(); diff --git a/src/eu/siacs/conversations/xml/XmlReader.java b/src/eu/siacs/conversations/xml/XmlReader.java index 7ae9a7b3..71e86cf9 100644 --- a/src/eu/siacs/conversations/xml/XmlReader.java +++ b/src/eu/siacs/conversations/xml/XmlReader.java @@ -36,7 +36,11 @@ public class XmlReader { Log.d(LOGTAG,"error setting input stream"); } } - + + public InputStream getInputStream() { + return is; + } + public void reset() { try { parser.setInput(new InputStreamReader(this.is)); diff --git a/src/eu/siacs/conversations/xmpp/XmppConnection.java b/src/eu/siacs/conversations/xmpp/XmppConnection.java index 1fd42bca..3702e66b 100644 --- a/src/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/eu/siacs/conversations/xmpp/XmppConnection.java @@ -38,6 +38,8 @@ import android.util.Log; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.DNSHelper; +import eu.siacs.conversations.utils.zlib.ZLibOutputStream; +import eu.siacs.conversations.utils.zlib.ZLibInputStream; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Tag; import eu.siacs.conversations.xml.TagWriter; @@ -177,6 +179,13 @@ public class XmppConnection implements Runnable { wakeLock.release(); } return; + } catch (NoSuchAlgorithmException e) { + this.changeStatus(Account.STATUS_OFFLINE); + Log.d(LOGTAG, "compression exception " + e.getMessage()); + if (wakeLock.isHeld()) { + wakeLock.release(); + } + return; } catch (XmlPullParserException e) { this.changeStatus(Account.STATUS_OFFLINE); Log.d(LOGTAG, "xml exception " + e.getMessage()); @@ -194,7 +203,7 @@ public class XmppConnection implements Runnable { } private void processStream(Tag currentTag) throws XmlPullParserException, - IOException { + IOException, NoSuchAlgorithmException { Tag nextTag = tagReader.readTag(); while ((nextTag != null) && (!nextTag.isEnd("stream"))) { if (nextTag.isStart("error")) { @@ -208,6 +217,8 @@ public class XmppConnection implements Runnable { } } else if (nextTag.isStart("proceed")) { switchOverToTls(nextTag); + } else if (nextTag.isStart("compressed")) { + switchOverToZLib(nextTag); } else if (nextTag.isStart("success")) { Log.d(LOGTAG, account.getJid() + ": logged in"); @@ -375,6 +386,33 @@ public class XmppConnection implements Runnable { } } + private void sendCompressionZlib() throws IOException { + tagWriter.writeElement(new Element("compress") { + public String toString() { + return + "<compress xmlns='http://jabber.org/protocol/compress'>" + + "<method>zlib</method>" + + "</compress>"; + } + }); + } + + private void switchOverToZLib(Tag currentTag) throws XmlPullParserException, + IOException, NoSuchAlgorithmException { + + Log.d(LOGTAG,account.getJid()+": Starting zlib compressed stream"); + + tagReader.readTag(); // read tag close + + tagWriter.setOutputStream(new ZLibOutputStream(tagWriter.getOutputStream())); + tagReader.setInputStream(new ZLibInputStream(tagReader.getInputStream())); + + sendStartStream(); + processStream(tagReader.readTag()); + + Log.d(LOGTAG,account.getJid()+": zlib compressed stream established"); + } + private void sendStartTLS() throws IOException { Tag startTLS = Tag.empty("starttls"); startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls"); @@ -486,6 +524,8 @@ public class XmppConnection implements Runnable { if (this.streamFeatures.hasChild("starttls") && account.isOptionSet(Account.OPTION_USETLS)) { sendStartTLS(); + } else if (compressionAvailable()) { + sendCompressionZlib(); } else if (this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) { sendRegistryRequest(); } else if (!this.streamFeatures.hasChild("register")&&(account.isOptionSet(Account.OPTION_REGISTER))) { @@ -514,6 +554,24 @@ public class XmppConnection implements Runnable { } } + private boolean compressionAvailable() { + if (!this.streamFeatures.hasChild("compression", "http://jabber.org/features/compress")) return false; + if (!ZLibOutputStream.SUPPORTED) return false; + if (!account.isOptionSet(Account.OPTION_USECOMPRESSION)) return false; + + Element compression = this.streamFeatures.findChild("compression", "http://jabber.org/features/compress"); + for (Element child : compression.getChildren()) { + if (!"method".equals(child.getName())) continue; + + if ("zlib".equalsIgnoreCase(child.getContent())) { + Log.d(LOGTAG, account.getJid() + ": compression available"); + return true; + } + } + + return false; + } + private List<String> extractMechanisms(Element stream) { ArrayList<String> mechanisms = new ArrayList<String>(stream.getChildren().size()); for(Element child : stream.getChildren()) { |