summaryrefslogtreecommitdiff
path: root/library/test/src/android
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2014-03-28 17:35:02 +0100
committerYohann Roussel <yroussel@google.com>2014-04-09 14:56:20 +0200
commit602c6ca8cae4718ba8ff9f65e53305d002479359 (patch)
treeea27d7ec0e1b77ea6471b4955c57ec71b38d5492 /library/test/src/android
parent5516c9ffbddd16c90f17c45caea3f4b59ab360d8 (diff)
downloadmultidex-602c6ca8cae4718ba8ff9f65e53305d002479359.tar.gz
Change update detection to reduce load time.
Reduces load time if extraction was already made. It appeared that new ZipFile was really slow because it's preparing much things as soon as it's instanciated. The new criteria consist of the last modified time of the apk plus the crc of the apk's central directory, last modified time should be enough for nearly all modifications and the crc is here to try to handle an OTA mixing with dates. The transition from old criteria to new should be good: since there will be no stored values they would be detected as a new installation. Change-Id: Id390b77b03d794b8b7feb91eb0daae1126c6d691
Diffstat (limited to 'library/test/src/android')
-rw-r--r--library/test/src/android/support/multidex/ZipEntryReader.java120
-rw-r--r--library/test/src/android/support/multidex/ZipUtilTest.java172
2 files changed, 292 insertions, 0 deletions
diff --git a/library/test/src/android/support/multidex/ZipEntryReader.java b/library/test/src/android/support/multidex/ZipEntryReader.java
new file mode 100644
index 0000000..c10bec5
--- /dev/null
+++ b/library/test/src/android/support/multidex/ZipEntryReader.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and
+ * ZipConstants from android libcore.
+ */
+
+package android.support.multidex;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+
+class ZipEntryReader {
+ static final Charset UTF_8 = Charset.forName("UTF-8");
+ /**
+ * General Purpose Bit Flags, Bit 0.
+ * If set, indicates that the file is encrypted.
+ */
+ private static final int GPBF_ENCRYPTED_FLAG = 1 << 0;
+
+ /**
+ * Supported General Purpose Bit Flags Mask.
+ * Bit mask of bits not supported.
+ * Note: The only bit that we will enforce at this time
+ * is the encrypted bit. Although other bits are not supported,
+ * we must not enforce them as this could break some legitimate
+ * use cases (See http://b/8617715).
+ */
+ private static final int GPBF_UNSUPPORTED_MASK = GPBF_ENCRYPTED_FLAG;
+ private static final long CENSIG = 0x2014b50;
+
+ static ZipEntry readEntry(ByteBuffer in) throws IOException {
+
+ int sig = in.getInt();
+ if (sig != CENSIG) {
+ throw new ZipException("Central Directory Entry not found");
+ }
+
+ in.position(8);
+ int gpbf = in.getShort() & 0xffff;
+
+ if ((gpbf & GPBF_UNSUPPORTED_MASK) != 0) {
+ throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
+ }
+
+ int compressionMethod = in.getShort() & 0xffff;
+ int time = in.getShort() & 0xffff;
+ int modDate = in.getShort() & 0xffff;
+
+ // These are 32-bit values in the file, but 64-bit fields in this object.
+ long crc = ((long) in.getInt()) & 0xffffffffL;
+ long compressedSize = ((long) in.getInt()) & 0xffffffffL;
+ long size = ((long) in.getInt()) & 0xffffffffL;
+
+ int nameLength = in.getShort() & 0xffff;
+ int extraLength = in.getShort() & 0xffff;
+ int commentByteCount = in.getShort() & 0xffff;
+
+ // This is a 32-bit value in the file, but a 64-bit field in this object.
+ in.position(42);
+ long localHeaderRelOffset = ((long) in.getInt()) & 0xffffffffL;
+
+ byte[] nameBytes = new byte[nameLength];
+ in.get(nameBytes, 0, nameBytes.length);
+ String name = new String(nameBytes, 0, nameBytes.length, UTF_8);
+
+ ZipEntry entry = new ZipEntry(name);
+ entry.setMethod(compressionMethod);
+ entry.setTime(getTime(time, modDate));
+
+ entry.setCrc(crc);
+ entry.setCompressedSize(compressedSize);
+ entry.setSize(size);
+
+ // The RI has always assumed UTF-8. (If GPBF_UTF8_FLAG isn't set, the encoding is
+ // actually IBM-437.)
+ if (commentByteCount > 0) {
+ byte[] commentBytes = new byte[commentByteCount];
+ in.get(commentBytes, 0, commentByteCount);
+ entry.setComment(new String(commentBytes, 0, commentBytes.length, UTF_8));
+ }
+
+ if (extraLength > 0) {
+ byte[] extra = new byte[extraLength];
+ in.get(extra, 0, extraLength);
+ entry.setExtra(extra);
+ }
+
+ return entry;
+
+ }
+
+ private static long getTime(int time, int modDate) {
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
+ modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
+ (time & 0x1f) << 1);
+ return cal.getTime().getTime();
+ }
+
+}
diff --git a/library/test/src/android/support/multidex/ZipUtilTest.java b/library/test/src/android/support/multidex/ZipUtilTest.java
new file mode 100644
index 0000000..985d97f
--- /dev/null
+++ b/library/test/src/android/support/multidex/ZipUtilTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.multidex;
+
+import android.support.multidex.ZipUtil.CentralDirectory;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/**
+ * Tests of ZipUtil class.
+ *
+ * The test assumes that ANDROID_BUILD_TOP environment variable is defined and point to the top of a
+ * built android tree. This is the case when the console used for running the tests is setup for
+ * android tree compilation.
+ */
+public class ZipUtilTest {
+ private static final File zipFile = new File(System.getenv("ANDROID_BUILD_TOP"),
+ "out/target/common/obj/JAVA_LIBRARIES/android-support-multidex_intermediates/javalib.jar");
+ @BeforeClass
+ public static void setupClass() throws ZipException, IOException {
+ // just verify the zip is valid
+ new ZipFile(zipFile).close();
+ }
+
+ @Test
+ public void testCrcDoNotCrash() throws IOException {
+
+ long crc =
+ ZipUtil.getZipCrc(zipFile);
+ System.out.println("crc is " + crc);
+
+ }
+
+ @Test
+ public void testCrcRange() throws IOException {
+ RandomAccessFile raf = new RandomAccessFile(zipFile, "r");
+ CentralDirectory dir = ZipUtil.findCentralDirectory(raf);
+ byte[] dirData = new byte[(int) dir.size];
+ int length = dirData.length;
+ int off = 0;
+ raf.seek(dir.offset);
+ while (length > 0) {
+ int read = raf.read(dirData, off, length);
+ if (length == -1) {
+ throw new EOFException();
+ }
+ length -= read;
+ off += read;
+ }
+ raf.close();
+ ByteBuffer buffer = ByteBuffer.wrap(dirData);
+ Map<String, ZipEntry> toCheck = new HashMap<String, ZipEntry>();
+ while (buffer.hasRemaining()) {
+ buffer = buffer.slice();
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ ZipEntry entry = ZipEntryReader.readEntry(buffer);
+ toCheck.put(entry.getName(), entry);
+ }
+
+ ZipFile zip = new ZipFile(zipFile);
+ Assert.assertEquals(zip.size(), toCheck.size());
+ Enumeration<? extends ZipEntry> ref = zip.entries();
+ while (ref.hasMoreElements()) {
+ ZipEntry refEntry = ref.nextElement();
+ ZipEntry checkEntry = toCheck.get(refEntry.getName());
+ Assert.assertNotNull(checkEntry);
+ Assert.assertEquals(refEntry.getName(), checkEntry.getName());
+ Assert.assertEquals(refEntry.getComment(), checkEntry.getComment());
+ Assert.assertEquals(refEntry.getTime(), checkEntry.getTime());
+ Assert.assertEquals(refEntry.getCrc(), checkEntry.getCrc());
+ Assert.assertEquals(refEntry.getCompressedSize(), checkEntry.getCompressedSize());
+ Assert.assertEquals(refEntry.getSize(), checkEntry.getSize());
+ Assert.assertEquals(refEntry.getMethod(), checkEntry.getMethod());
+ Assert.assertArrayEquals(refEntry.getExtra(), checkEntry.getExtra());
+ }
+ zip.close();
+ }
+
+ @Test
+ public void testCrcValue() throws IOException {
+ ZipFile zip = new ZipFile(zipFile);
+ Enumeration<? extends ZipEntry> ref = zip.entries();
+ byte[] buffer = new byte[0x2000];
+ while (ref.hasMoreElements()) {
+ ZipEntry refEntry = ref.nextElement();
+ if (refEntry.getSize() > 0) {
+ File tmp = File.createTempFile("ZipUtilTest", ".fakezip");
+ InputStream in = zip.getInputStream(refEntry);
+ OutputStream out = new FileOutputStream(tmp);
+ int read = in.read(buffer);
+ while (read != -1) {
+ out.write(buffer, 0, read);
+ read = in.read(buffer);
+ }
+ in.close();
+ out.close();
+ RandomAccessFile raf = new RandomAccessFile(tmp, "r");
+ CentralDirectory dir = new CentralDirectory();
+ dir.offset = 0;
+ dir.size = raf.length();
+ long crc = ZipUtil.computeCrcOfCentralDir(raf, dir);
+ Assert.assertEquals(refEntry.getCrc(), crc);
+ raf.close();
+ tmp.delete();
+ }
+ }
+ zip.close();
+ }
+ @Test
+ public void testInvalidCrcValue() throws IOException {
+ ZipFile zip = new ZipFile(zipFile);
+ Enumeration<? extends ZipEntry> ref = zip.entries();
+ byte[] buffer = new byte[0x2000];
+ while (ref.hasMoreElements()) {
+ ZipEntry refEntry = ref.nextElement();
+ if (refEntry.getSize() > 0) {
+ File tmp = File.createTempFile("ZipUtilTest", ".fakezip");
+ InputStream in = zip.getInputStream(refEntry);
+ OutputStream out = new FileOutputStream(tmp);
+ int read = in.read(buffer);
+ while (read != -1) {
+ out.write(buffer, 0, read);
+ read = in.read(buffer);
+ }
+ in.close();
+ out.close();
+ RandomAccessFile raf = new RandomAccessFile(tmp, "r");
+ CentralDirectory dir = new CentralDirectory();
+ dir.offset = 0;
+ dir.size = raf.length() - 1;
+ long crc = ZipUtil.computeCrcOfCentralDir(raf, dir);
+ Assert.assertNotEquals(refEntry.getCrc(), crc);
+ raf.close();
+ tmp.delete();
+ }
+ }
+ zip.close();
+ }
+
+}