diff --git a/app/src/main/java/org/fdroid/fdroid/AndroidXMLDecompress.java b/app/src/main/java/org/fdroid/fdroid/AndroidXMLDecompress.java
new file mode 100644
index 000000000..b8569f246
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/AndroidXMLDecompress.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 Hans-Christoph Steiner <hans@eds.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+/*
+ Copyright (c) 2016, Liu Dong
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of apk-parser nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.fdroid.fdroid;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Parse the 'compressed' binary form of Android XML docs such as for
+ * {@code AndroidManifest.xml} in APK files.  This is a very truncated
+ * version of apk-parser since currently, we only need the header from
+ * the binary XML AndroidManifest.xml. apk-parser provides full APK
+ * parsing, which is a lot more than what is needed.
+ *
+ * @see <a href="https://github.com/caoqianli/apk-parser">apk-parser</a>
+ * @see <a href="https://justanapplication.wordpress.com/category/android/android-binary-xml">Android Internals: Binary XML</a>
+ * @see <a href="https://stackoverflow.com/a/4761689">a binary XML parser</a>
+ */
+public class AndroidXMLDecompress {
+    public static int startTag = 0x00100102;
+
+    /**
+     * Just get the XML attributes from the {@code <manifest>} element.
+     *
+     * @return A key value map of the attributes, with values as {@link Object}s
+     */
+    public static Map<String, Object> getManifestHeaderAttributes(String filename) throws IOException {
+        byte[] binaryXml = getManifestFromFilename(filename);
+        int numbStrings = littleEndianWord(binaryXml, 4 * 4);
+        int stringIndexTableOffset = 0x24;
+        int stringTableOffset = stringIndexTableOffset + numbStrings * 4;
+        int xmlTagOffset = littleEndianWord(binaryXml, 3 * 4);
+        for (int i = xmlTagOffset; i < binaryXml.length - 4; i += 4) {
+            if (littleEndianWord(binaryXml, i) == startTag) {
+                xmlTagOffset = i;
+                break;
+            }
+        }
+        int offset = xmlTagOffset;
+
+        while (offset < binaryXml.length) {
+            int tag0 = littleEndianWord(binaryXml, offset);
+            int nameStringIndex = littleEndianWord(binaryXml, offset + 5 * 4);
+
+            if (tag0 == startTag) {
+                int numbAttrs = littleEndianWord(binaryXml, offset + 7 * 4);
+                offset += 9 * 4;
+
+                HashMap<String, Object> attributes = new HashMap<String, Object>(3);
+                for (int i = 0; i < numbAttrs; i++) {
+                    int attributeNameStringIndex = littleEndianWord(binaryXml, offset + 1 * 4);
+                    int attributeValueStringIndex = littleEndianWord(binaryXml, offset + 2 * 4);
+                    int attributeResourceId = littleEndianWord(binaryXml, offset + 4 * 4);
+                    offset += 5 * 4;
+
+                    String attributeName = getString(binaryXml, stringIndexTableOffset, stringTableOffset, attributeNameStringIndex);
+                    Object attributeValue;
+                    if (attributeValueStringIndex != -1) {
+                        attributeValue = getString(binaryXml, stringIndexTableOffset, stringTableOffset, attributeValueStringIndex);
+                    } else {
+                        attributeValue = attributeResourceId;
+                    }
+                    attributes.put(attributeName, attributeValue);
+                }
+                return attributes;
+            } else {
+                // we only need the first <manifest> start tag
+                break;
+            }
+        }
+        return new HashMap<String, Object>(0);
+    }
+
+    public static byte[] getManifestFromFilename(String filename) throws IOException {
+        InputStream is = null;
+        ZipFile zip = null;
+        int size = 0;
+
+        if (filename.endsWith(".apk")) {
+            zip = new ZipFile(filename);
+            ZipEntry ze = zip.getEntry("AndroidManifest.xml");
+            is = zip.getInputStream(ze);
+            size = (int) ze.getSize();
+        } else {
+            throw new RuntimeException("This only works on APK files!");
+        }
+        byte[] buf = new byte[size];
+        is.read(buf);
+
+        is.close();
+        if (zip != null) {
+            zip.close();
+        }
+        return buf;
+    }
+
+    public static String getString(byte[] bytes, int stringIndexTableOffset, int stringTableOffset, int stringIndex) {
+        if (stringIndex < 0) {
+            return null;
+        }
+        int stringOffset = stringTableOffset + littleEndianWord(bytes, stringIndexTableOffset + stringIndex * 4);
+        return getStringAt(bytes, stringOffset);
+    }
+
+    public static String getStringAt(byte[] bytes, int stringOffset) {
+        int length = bytes[stringOffset + 1] << 8 & 0xff00 | bytes[stringOffset] & 0xff;
+        byte[] chars = new byte[length];
+        for (int i = 0; i < length; i++) {
+            chars[i] = bytes[stringOffset + 2 + i * 2];
+        }
+        return new String(chars);
+    }
+
+    /**
+     * Return the little endian 32-bit word from the byte array at offset
+     */
+    public static int littleEndianWord(byte[] bytes, int offset) {
+        return bytes[offset + 3]
+                << 24 & 0xff000000
+                | bytes[offset + 2]
+                << 16 & 0xff0000
+                | bytes[offset + 1]
+                << 8 & 0xff00
+                | bytes[offset] & 0xFF;
+    }
+}
diff --git a/app/src/test/assets/urzip.apk b/app/src/test/assets/urzip.apk
new file mode 100644
index 000000000..ee5e5cba8
Binary files /dev/null and b/app/src/test/assets/urzip.apk differ
diff --git a/app/src/test/java/org/fdroid/fdroid/AndroidXMLDecompressTest.java b/app/src/test/java/org/fdroid/fdroid/AndroidXMLDecompressTest.java
new file mode 100644
index 000000000..ead537730
--- /dev/null
+++ b/app/src/test/java/org/fdroid/fdroid/AndroidXMLDecompressTest.java
@@ -0,0 +1,51 @@
+package org.fdroid.fdroid;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class AndroidXMLDecompressTest {
+
+    String[] testDirNames = {
+            System.getProperty("user.dir") + "/src/test/assets",
+            System.getProperty("user.dir") + "/build/outputs/apk",
+            System.getenv("HOME") + "/fdroid/repo",
+    };
+
+    FilenameFilter apkFilter = new FilenameFilter() {
+        @Override
+        public boolean accept(File dir, String filename) {
+            return filename.endsWith(".apk");
+        }
+    };
+
+    @Test
+    public void testParseVersionCode() throws IOException {
+        for (File f : getFilesToTest()) {
+            System.out.println("\n" + f);
+            Map<String, Object> map = AndroidXMLDecompress.getManifestHeaderAttributes(f.getAbsolutePath());
+            for (String key : map.keySet()) {
+                System.out.println(key + "=\"" + map.get(key) + "\"");
+            }
+        }
+    }
+
+    private List<File> getFilesToTest() {
+        ArrayList<File> apkFiles = new ArrayList<File>(5);
+        for (String dirName : testDirNames) {
+            System.out.println("looking in " + dirName);
+            File dir = new File(dirName);
+            File[] files = dir.listFiles(apkFilter);
+            if (files != null) {
+                apkFiles.addAll(Arrays.asList(files));
+            }
+        }
+        return apkFiles;
+    }
+}