eclair snapshot
diff --git a/tools/adbs b/tools/adbs
new file mode 100755
index 0000000..8b1fac6
--- /dev/null
+++ b/tools/adbs
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2009 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.
+
+import os
+import re
+import string
+import sys
+
+###############################################################################
+# match "#00 pc 0003f52e /system/lib/libdvm.so" for example
+###############################################################################
+trace_line = re.compile("(.*)(\#[0-9]+) (..) ([0-9a-f]{8}) ([^\r\n \t]*)")
+
+# returns a list containing the function name and the file/lineno
+def CallAddr2Line(lib, addr):
+ global symbols_dir
+ global addr2line_cmd
+ global cppfilt_cmd
+
+ if lib != "":
+ cmd = addr2line_cmd + \
+ " -f -e " + symbols_dir + lib + " 0x" + addr
+ stream = os.popen(cmd)
+ lines = stream.readlines()
+ list = map(string.strip, lines)
+ else:
+ list = []
+ if list != []:
+ # Name like "move_forward_type<JavaVMOption>" causes troubles
+ mangled_name = re.sub('<', '\<', list[0]);
+ mangled_name = re.sub('>', '\>', mangled_name);
+ cmd = cppfilt_cmd + " " + mangled_name
+ stream = os.popen(cmd)
+ list[0] = stream.readline()
+ stream.close()
+ list = map(string.strip, list)
+ else:
+ list = [ "(unknown)", "(unknown)" ]
+ return list
+
+
+###############################################################################
+# similar to CallAddr2Line, but using objdump to find out the name of the
+# containing function of the specified address
+###############################################################################
+def CallObjdump(lib, addr):
+ global objdump_cmd
+ global symbols_dir
+
+ unknown = "(unknown)"
+ uname = os.uname()[0]
+ if uname == "Darwin":
+ proc = os.uname()[-1]
+ if proc == "i386":
+ uname = "darwin-x86"
+ else:
+ uname = "darwin-ppc"
+ elif uname == "Linux":
+ uname = "linux-x86"
+ if lib != "":
+ next_addr = string.atoi(addr, 16) + 1
+ cmd = objdump_cmd \
+ + " -C -d --start-address=0x" + addr + " --stop-address=" \
+ + str(next_addr) \
+ + " " + symbols_dir + lib
+ stream = os.popen(cmd)
+ lines = stream.readlines()
+ map(string.strip, lines)
+ stream.close()
+ else:
+ return unknown
+
+ # output looks like
+ #
+ # file format elf32-littlearm
+ #
+ # Disassembly of section .text:
+ #
+ # 0000833c <func+0x4>:
+ # 833c: 701a strb r2, [r3, #0]
+ #
+ # we want to extract the "func" part
+ num_lines = len(lines)
+ if num_lines < 2:
+ return unknown
+ func_name = lines[num_lines-2]
+ func_regexp = re.compile("(^.*\<)(.*)(\+.*\>:$)")
+ components = func_regexp.match(func_name)
+ if components is None:
+ return unknown
+ return components.group(2)
+
+###############################################################################
+# determine the symbols directory in the local build
+###############################################################################
+def FindSymbolsDir():
+ global symbols_dir
+
+ try:
+ path = os.environ['ANDROID_PRODUCT_OUT'] + "/symbols"
+ except:
+ cmd = "CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core " \
+ + "SRC_TARGET_DIR=build/target make -f build/core/envsetup.mk " \
+ + "dumpvar-abs-TARGET_OUT_UNSTRIPPED"
+ stream = os.popen(cmd)
+ str = stream.read()
+ stream.close()
+ path = str.strip()
+
+ if (not os.path.exists(path)):
+ print path + " not found!"
+ sys.exit(1)
+
+ symbols_dir = path
+
+###############################################################################
+# determine the path of binutils
+###############################################################################
+def SetupToolsPath():
+ global addr2line_cmd
+ global objdump_cmd
+ global cppfilt_cmd
+ global symbols_dir
+
+ uname = os.uname()[0]
+ if uname == "Darwin":
+ proc = os.uname()[-1]
+ if proc == "i386":
+ uname = "darwin-x86"
+ else:
+ uname = "darwin-ppc"
+ elif uname == "Linux":
+ uname = "linux-x86"
+ prefix = "./prebuilt/" + uname + "/toolchain/arm-eabi-4.4.0/bin/"
+ addr2line_cmd = prefix + "arm-eabi-addr2line"
+
+ if (not os.path.exists(addr2line_cmd)):
+ try:
+ prefix = os.environ['ANDROID_BUILD_TOP'] + "/prebuilt/" + uname + \
+ "/toolchain/arm-eabi-4.4.0/bin/"
+ except:
+ prefix = "";
+
+ addr2line_cmd = prefix + "arm-eabi-addr2line"
+ if (not os.path.exists(addr2line_cmd)):
+ print addr2line_cmd + " not found!"
+ sys.exit(1)
+
+ objdump_cmd = prefix + "arm-eabi-objdump"
+ cppfilt_cmd = prefix + "arm-eabi-c++filt"
+
+###############################################################################
+# look up the function and file/line number for a raw stack trace line
+# groups[0]: log tag
+# groups[1]: stack level
+# groups[2]: "pc"
+# groups[3]: code address
+# groups[4]: library name
+###############################################################################
+def SymbolTranslation(groups):
+ lib_name = groups[4]
+ code_addr = groups[3]
+ caller = CallObjdump(lib_name, code_addr)
+ func_line_pair = CallAddr2Line(lib_name, code_addr)
+
+ # If a callee is inlined to the caller, objdump will see the caller's
+ # address but addr2line will report the callee's address. So the printed
+ # format is desgined to be "caller<-callee file:line"
+ if (func_line_pair[0] != caller):
+ print groups[0] + groups[1] + " " + caller + "<-" + \
+ ' '.join(func_line_pair[:]) + " "
+ else:
+ print groups[0] + groups[1] + " " + ' '.join(func_line_pair[:]) + " "
+
+###############################################################################
+
+if __name__ == '__main__':
+ # pass the options to adb
+ adb_cmd = "adb " + ' '.join(sys.argv[1:])
+
+ # setup addr2line_cmd and objdump_cmd
+ SetupToolsPath()
+
+ # setup the symbols directory
+ FindSymbolsDir()
+
+ # invoke the adb command and filter its output
+ stream = os.popen(adb_cmd)
+ while (True):
+ line = stream.readline()
+
+ # EOF reached
+ if (line == ''):
+ break
+
+ # remove the trailing \n
+ line = line.strip()
+
+ # see if this is a stack trace line
+ match = trace_line.match(line)
+ if (match):
+ groups = match.groups()
+ # translate raw address into symbols
+ SymbolTranslation(groups)
+ else:
+ print line
+
+ # adb itself aborts
+ stream.close()
diff --git a/tools/apicheck/src/com/android/apicheck/ApiCheck.java b/tools/apicheck/src/com/android/apicheck/ApiCheck.java
index 20a98ce..c8272dd 100644
--- a/tools/apicheck/src/com/android/apicheck/ApiCheck.java
+++ b/tools/apicheck/src/com/android/apicheck/ApiCheck.java
@@ -127,7 +127,7 @@
}
private static class MakeHandler extends DefaultHandler {
-
+
private ApiInfo mApi;
private PackageInfo mCurrentPackage;
private ClassInfo mCurrentClass;
@@ -139,8 +139,9 @@
super();
mApi = new ApiInfo();
}
-
- public void startElement(String uri, String localName, String qName,
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
Attributes attributes) {
if (qName.equals("package")) {
mCurrentPackage = new PackageInfo(attributes.getValue("name"),
@@ -150,25 +151,25 @@
// push the old outer scope for later recovery, then set
// up the new current class object
mClassScope.push(mCurrentClass);
- mCurrentClass = new ClassInfo(attributes.getValue("name"),
+ mCurrentClass = new ClassInfo(attributes.getValue("name"),
mCurrentPackage,
attributes.getValue("extends") ,
- qName.equals("interface"),
+ qName.equals("interface"),
Boolean.valueOf(
attributes.getValue("abstract")),
Boolean.valueOf(
attributes.getValue("static")),
Boolean.valueOf(
attributes.getValue("final")),
- attributes.getValue("deprecated"),
+ attributes.getValue("deprecated"),
attributes.getValue("visibility"),
SourcePositionInfo.fromXml(attributes.getValue("source")),
mCurrentClass);
} else if (qName.equals("method")) {
- mCurrentMethod = new MethodInfo(attributes.getValue("name"),
+ mCurrentMethod = new MethodInfo(attributes.getValue("name"),
attributes.getValue("return") ,
Boolean.valueOf(
- attributes.getValue("abstract")),
+ attributes.getValue("abstract")),
Boolean.valueOf(
attributes.getValue("native")),
Boolean.valueOf(
@@ -178,11 +179,11 @@
Boolean.valueOf(
attributes.getValue("final")),
attributes.getValue("deprecated"),
- attributes.getValue("visibility"),
+ attributes.getValue("visibility"),
SourcePositionInfo.fromXml(attributes.getValue("source")),
mCurrentClass);
} else if (qName.equals("constructor")) {
- mCurrentMethod = new ConstructorInfo(attributes.getValue("name"),
+ mCurrentMethod = new ConstructorInfo(attributes.getValue("name"),
attributes.getValue("type") ,
Boolean.valueOf(
attributes.getValue("static")),
@@ -193,7 +194,7 @@
SourcePositionInfo.fromXml(attributes.getValue("source")),
mCurrentClass);
} else if (qName.equals("field")) {
- FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
+ FieldInfo fInfo = new FieldInfo(attributes.getValue("name"),
attributes.getValue("type") ,
Boolean.valueOf(
attributes.getValue("transient")),
@@ -218,6 +219,8 @@
mCurrentClass.addInterface(attributes.getValue("name"));
}
}
+
+ @Override
public void endElement(String uri, String localName, String qName) {
if (qName.equals("method")) {
mCurrentClass.addMethod((MethodInfo) mCurrentMethod);
diff --git a/tools/apicheck/src/com/android/apicheck/Errors.java b/tools/apicheck/src/com/android/apicheck/Errors.java
index d7013e3..b0b620e 100644
--- a/tools/apicheck/src/com/android/apicheck/Errors.java
+++ b/tools/apicheck/src/com/android/apicheck/Errors.java
@@ -41,6 +41,7 @@
return this.msg.compareTo(that.msg);
}
+ @Override
public String toString() {
return this.pos.toString() + this.msg;
}
@@ -115,7 +116,7 @@
public static Error CHANGED_CLASS = new Error(23, WARNING);
public static Error CHANGED_DEPRECATED = new Error(24, WARNING);
public static Error CHANGED_SYNCHRONIZED = new Error(25, ERROR);
-
+
public static Error[] ERRORS = {
PARSE_ERROR,
ADDED_PACKAGE,
diff --git a/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java b/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java
index 477c1d3..276771b 100644
--- a/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java
+++ b/tools/apicheck/src/com/android/apicheck/SourcePositionInfo.java
@@ -80,6 +80,7 @@
return new SourcePositionInfo(that.file, line, 0);
}
+ @Override
public String toString()
{
if (this.file == null) {
diff --git a/tools/applypatch/Android.mk b/tools/applypatch/Android.mk
index 5796cef..d20d6c8 100644
--- a/tools/applypatch/Android.mk
+++ b/tools/applypatch/Android.mk
@@ -17,7 +17,7 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := applypatch.c bsdiff.c freecache.c imgpatch.c utils.c
+LOCAL_SRC_FILES := applypatch.c bspatch.c freecache.c imgpatch.c utils.c
LOCAL_MODULE := libapplypatch
LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += external/bzip2 external/zlib bootable/recovery
@@ -47,12 +47,12 @@
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := imgdiff.c utils.c
+LOCAL_SRC_FILES := imgdiff.c utils.c bsdiff.c
LOCAL_MODULE := imgdiff
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_MODULE_TAGS := eng
-LOCAL_C_INCLUDES += external/zlib
-LOCAL_STATIC_LIBRARIES += libz
+LOCAL_C_INCLUDES += external/zlib external/bzip2
+LOCAL_STATIC_LIBRARIES += libz libbz
include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/applypatch/bsdiff.c b/tools/applypatch/bsdiff.c
index d5cd617..b6d342b 100644
--- a/tools/applypatch/bsdiff.c
+++ b/tools/applypatch/bsdiff.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -14,239 +14,397 @@
* limitations under the License.
*/
-// This file is a nearly line-for-line copy of bspatch.c from the
-// bsdiff-4.3 distribution; the primary differences being how the
-// input and output data are read and the error handling. Running
-// applypatch with the -l option will display the bsdiff license
-// notice.
+/*
+ * Most of this code comes from bsdiff.c from the bsdiff-4.3
+ * distribution, which is:
+ */
-#include <stdio.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <unistd.h>
-#include <string.h>
+/*-
+ * Copyright 2003-2005 Colin Percival
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted providing that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#include <sys/types.h>
#include <bzlib.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
-#include "mincrypt/sha.h"
-#include "applypatch.h"
+#define MIN(x,y) (((x)<(y)) ? (x) : (y))
-void ShowBSDiffLicense() {
- puts("The bsdiff library used herein is:\n"
- "\n"
- "Copyright 2003-2005 Colin Percival\n"
- "All rights reserved\n"
- "\n"
- "Redistribution and use in source and binary forms, with or without\n"
- "modification, are permitted providing that the following conditions\n"
- "are met:\n"
- "1. Redistributions of source code must retain the above copyright\n"
- " notice, this list of conditions and the following disclaimer.\n"
- "2. Redistributions in binary form must reproduce the above copyright\n"
- " notice, this list of conditions and the following disclaimer in the\n"
- " documentation and/or other materials provided with the distribution.\n"
- "\n"
- "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
- "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
- "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
- "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n"
- "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n"
- "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n"
- "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
- "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
- "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
- "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
- "POSSIBILITY OF SUCH DAMAGE.\n"
- "\n------------------\n\n"
- "This program uses Julian R Seward's \"libbzip2\" library, available\n"
- "from http://www.bzip.org/.\n"
- );
-}
-
-static off_t offtin(u_char *buf)
+static void split(off_t *I,off_t *V,off_t start,off_t len,off_t h)
{
- off_t y;
+ off_t i,j,k,x,tmp,jj,kk;
- y=buf[7]&0x7F;
- y=y*256;y+=buf[6];
- y=y*256;y+=buf[5];
- y=y*256;y+=buf[4];
- y=y*256;y+=buf[3];
- y=y*256;y+=buf[2];
- y=y*256;y+=buf[1];
- y=y*256;y+=buf[0];
+ if(len<16) {
+ for(k=start;k<start+len;k+=j) {
+ j=1;x=V[I[k]+h];
+ for(i=1;k+i<start+len;i++) {
+ if(V[I[k+i]+h]<x) {
+ x=V[I[k+i]+h];
+ j=0;
+ };
+ if(V[I[k+i]+h]==x) {
+ tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp;
+ j++;
+ };
+ };
+ for(i=0;i<j;i++) V[I[k+i]]=k+j-1;
+ if(j==1) I[k]=-1;
+ };
+ return;
+ };
- if(buf[7]&0x80) y=-y;
+ x=V[I[start+len/2]+h];
+ jj=0;kk=0;
+ for(i=start;i<start+len;i++) {
+ if(V[I[i]+h]<x) jj++;
+ if(V[I[i]+h]==x) kk++;
+ };
+ jj+=start;kk+=jj;
- return y;
+ i=start;j=0;k=0;
+ while(i<jj) {
+ if(V[I[i]+h]<x) {
+ i++;
+ } else if(V[I[i]+h]==x) {
+ tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp;
+ j++;
+ } else {
+ tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp;
+ k++;
+ };
+ };
+
+ while(jj+j<kk) {
+ if(V[I[jj+j]+h]==x) {
+ j++;
+ } else {
+ tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp;
+ k++;
+ };
+ };
+
+ if(jj>start) split(I,V,start,jj-start,h);
+
+ for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1;
+ if(jj==kk-1) I[jj]=-1;
+
+ if(start+len>kk) split(I,V,kk,start+len-kk,h);
}
+static void qsufsort(off_t *I,off_t *V,u_char *old,off_t oldsize)
+{
+ off_t buckets[256];
+ off_t i,h,len;
-int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
- const char* patch_filename, ssize_t patch_offset,
- SinkFn sink, void* token, SHA_CTX* ctx) {
+ for(i=0;i<256;i++) buckets[i]=0;
+ for(i=0;i<oldsize;i++) buckets[old[i]]++;
+ for(i=1;i<256;i++) buckets[i]+=buckets[i-1];
+ for(i=255;i>0;i--) buckets[i]=buckets[i-1];
+ buckets[0]=0;
- unsigned char* new_data;
- ssize_t new_size;
- if (ApplyBSDiffPatchMem(old_data, old_size, patch_filename, patch_offset,
- &new_data, &new_size) != 0) {
- return -1;
- }
+ for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i;
+ I[0]=oldsize;
+ for(i=0;i<oldsize;i++) V[i]=buckets[old[i]];
+ V[oldsize]=0;
+ for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1;
+ I[0]=-1;
- if (sink(new_data, new_size, token) < new_size) {
- fprintf(stderr, "short write of output: %d (%s)\n", errno, strerror(errno));
- return 1;
- }
- if (ctx) {
- SHA_update(ctx, new_data, new_size);
- }
- free(new_data);
+ for(h=1;I[0]!=-(oldsize+1);h+=h) {
+ len=0;
+ for(i=0;i<oldsize+1;) {
+ if(I[i]<0) {
+ len-=I[i];
+ i-=I[i];
+ } else {
+ if(len) I[i-len]=-len;
+ len=V[I[i]]+1-i;
+ split(I,V,i,len,h);
+ i+=len;
+ len=0;
+ };
+ };
+ if(len) I[i-len]=-len;
+ };
- return 0;
+ for(i=0;i<oldsize+1;i++) I[V[i]]=i;
}
-int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
- const char* patch_filename, ssize_t patch_offset,
- unsigned char** new_data, ssize_t* new_size) {
+static off_t matchlen(u_char *old,off_t oldsize,u_char *new,off_t newsize)
+{
+ off_t i;
- FILE* f;
- if ((f = fopen(patch_filename, "rb")) == NULL) {
- fprintf(stderr, "failed to open patch file\n");
- return 1;
- }
+ for(i=0;(i<oldsize)&&(i<newsize);i++)
+ if(old[i]!=new[i]) break;
- // File format:
- // 0 8 "BSDIFF40"
- // 8 8 X
- // 16 8 Y
- // 24 8 sizeof(newfile)
- // 32 X bzip2(control block)
- // 32+X Y bzip2(diff block)
- // 32+X+Y ??? bzip2(extra block)
- // with control block a set of triples (x,y,z) meaning "add x bytes
- // from oldfile to x bytes from the diff block; copy y bytes from the
- // extra block; seek forwards in oldfile by z bytes".
+ return i;
+}
- fseek(f, patch_offset, SEEK_SET);
+static off_t search(off_t *I,u_char *old,off_t oldsize,
+ u_char *new,off_t newsize,off_t st,off_t en,off_t *pos)
+{
+ off_t x,y;
- unsigned char header[32];
- if (fread(header, 1, 32, f) < 32) {
- fprintf(stderr, "failed to read patch file header\n");
- return 1;
- }
+ if(en-st<2) {
+ x=matchlen(old+I[st],oldsize-I[st],new,newsize);
+ y=matchlen(old+I[en],oldsize-I[en],new,newsize);
- if (memcmp(header, "BSDIFF40", 8) != 0) {
- fprintf(stderr, "corrupt bsdiff patch file header (magic number)\n");
- return 1;
- }
+ if(x>y) {
+ *pos=I[st];
+ return x;
+ } else {
+ *pos=I[en];
+ return y;
+ }
+ };
- ssize_t ctrl_len, data_len;
- ctrl_len = offtin(header+8);
- data_len = offtin(header+16);
- *new_size = offtin(header+24);
+ x=st+(en-st)/2;
+ if(memcmp(old+I[x],new,MIN(oldsize-I[x],newsize))<0) {
+ return search(I,old,oldsize,new,newsize,x,en,pos);
+ } else {
+ return search(I,old,oldsize,new,newsize,st,x,pos);
+ };
+}
- if (ctrl_len < 0 || data_len < 0 || *new_size < 0) {
- fprintf(stderr, "corrupt patch file header (data lengths)\n");
- return 1;
- }
+static void offtout(off_t x,u_char *buf)
+{
+ off_t y;
- fclose(f);
+ if(x<0) y=-x; else y=x;
- int bzerr;
+ buf[0]=y%256;y-=buf[0];
+ y=y/256;buf[1]=y%256;y-=buf[1];
+ y=y/256;buf[2]=y%256;y-=buf[2];
+ y=y/256;buf[3]=y%256;y-=buf[3];
+ y=y/256;buf[4]=y%256;y-=buf[4];
+ y=y/256;buf[5]=y%256;y-=buf[5];
+ y=y/256;buf[6]=y%256;y-=buf[6];
+ y=y/256;buf[7]=y%256;
-#define OPEN_AT(f, bzf, offset) \
- FILE* f; \
- BZFILE* bzf; \
- if ((f = fopen(patch_filename, "rb")) == NULL) { \
- fprintf(stderr, "failed to open patch file\n"); \
- return 1; \
- } \
- if (fseeko(f, offset+patch_offset, SEEK_SET)) { \
- fprintf(stderr, "failed to seek in patch file\n"); \
- return 1; \
- } \
- if ((bzf = BZ2_bzReadOpen(&bzerr, f, 0, 0, NULL, 0)) == NULL) { \
- fprintf(stderr, "failed to bzReadOpen in patch file (%d)\n", bzerr); \
- return 1; \
- }
+ if(x<0) buf[7]|=0x80;
+}
- OPEN_AT(cpf, cpfbz2, 32);
- OPEN_AT(dpf, dpfbz2, 32+ctrl_len);
- OPEN_AT(epf, epfbz2, 32+ctrl_len+data_len);
+// This is main() from bsdiff.c, with the following changes:
+//
+// - old, oldsize, new, newsize are arguments; we don't load this
+// data from files. old and new are owned by the caller; we
+// don't free them at the end.
+//
+// - the "I" block of memory is owned by the caller, who passes a
+// pointer to *I, which can be NULL. This way if we call
+// bsdiff() multiple times with the same 'old' data, we only do
+// the qsufsort() step the first time.
+//
+int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize,
+ const char* patch_filename)
+{
+ int fd;
+ off_t *I;
+ off_t scan,pos,len;
+ off_t lastscan,lastpos,lastoffset;
+ off_t oldscore,scsc;
+ off_t s,Sf,lenf,Sb,lenb;
+ off_t overlap,Ss,lens;
+ off_t i;
+ off_t dblen,eblen;
+ u_char *db,*eb;
+ u_char buf[8];
+ u_char header[32];
+ FILE * pf;
+ BZFILE * pfbz2;
+ int bz2err;
-#undef OPEN_AT
+ if (*IP == NULL) {
+ off_t* V;
+ *IP = malloc((oldsize+1) * sizeof(off_t));
+ V = malloc((oldsize+1) * sizeof(off_t));
+ qsufsort(*IP, V, old, oldsize);
+ free(V);
+ }
+ I = *IP;
- *new_data = malloc(*new_size);
- if (*new_data == NULL) {
- fprintf(stderr, "failed to allocate %d bytes of memory for output file\n",
- (int)*new_size);
- return 1;
- }
+ if(((db=malloc(newsize+1))==NULL) ||
+ ((eb=malloc(newsize+1))==NULL)) err(1,NULL);
+ dblen=0;
+ eblen=0;
- off_t oldpos = 0, newpos = 0;
- off_t ctrl[3];
- off_t len_read;
- int i;
- unsigned char buf[8];
- while (newpos < *new_size) {
- // Read control data
- for (i = 0; i < 3; ++i) {
- len_read = BZ2_bzRead(&bzerr, cpfbz2, buf, 8);
- if (len_read < 8 || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
- fprintf(stderr, "corrupt patch (read control)\n");
- return 1;
- }
- ctrl[i] = offtin(buf);
- }
+ /* Create the patch file */
+ if ((pf = fopen(patch_filename, "w")) == NULL)
+ err(1, "%s", patch_filename);
- // Sanity check
- if (newpos + ctrl[0] > *new_size) {
- fprintf(stderr, "corrupt patch (new file overrun)\n");
- return 1;
- }
+ /* Header is
+ 0 8 "BSDIFF40"
+ 8 8 length of bzip2ed ctrl block
+ 16 8 length of bzip2ed diff block
+ 24 8 length of new file */
+ /* File is
+ 0 32 Header
+ 32 ?? Bzip2ed ctrl block
+ ?? ?? Bzip2ed diff block
+ ?? ?? Bzip2ed extra block */
+ memcpy(header,"BSDIFF40",8);
+ offtout(0, header + 8);
+ offtout(0, header + 16);
+ offtout(newsize, header + 24);
+ if (fwrite(header, 32, 1, pf) != 1)
+ err(1, "fwrite(%s)", patch_filename);
- // Read diff string
- len_read = BZ2_bzRead(&bzerr, dpfbz2, *new_data + newpos, ctrl[0]);
- if (len_read < ctrl[0] || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
- fprintf(stderr, "corrupt patch (read diff)\n");
- return 1;
- }
+ /* Compute the differences, writing ctrl as we go */
+ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
+ errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
+ scan=0;len=0;
+ lastscan=0;lastpos=0;lastoffset=0;
+ while(scan<newsize) {
+ oldscore=0;
- // Add old data to diff string
- for (i = 0; i < ctrl[0]; ++i) {
- if ((oldpos+i >= 0) && (oldpos+i < old_size)) {
- (*new_data)[newpos+i] += old_data[oldpos+i];
- }
- }
+ for(scsc=scan+=len;scan<newsize;scan++) {
+ len=search(I,old,oldsize,new+scan,newsize-scan,
+ 0,oldsize,&pos);
- // Adjust pointers
- newpos += ctrl[0];
- oldpos += ctrl[0];
+ for(;scsc<scan+len;scsc++)
+ if((scsc+lastoffset<oldsize) &&
+ (old[scsc+lastoffset] == new[scsc]))
+ oldscore++;
- // Sanity check
- if (newpos + ctrl[1] > *new_size) {
- fprintf(stderr, "corrupt patch (new file overrun)\n");
- return 1;
- }
+ if(((len==oldscore) && (len!=0)) ||
+ (len>oldscore+8)) break;
- // Read extra string
- len_read = BZ2_bzRead(&bzerr, epfbz2, *new_data + newpos, ctrl[1]);
- if (len_read < ctrl[1] || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
- fprintf(stderr, "corrupt patch (read extra)\n");
- return 1;
- }
+ if((scan+lastoffset<oldsize) &&
+ (old[scan+lastoffset] == new[scan]))
+ oldscore--;
+ };
- // Adjust pointers
- newpos += ctrl[1];
- oldpos += ctrl[2];
- }
+ if((len!=oldscore) || (scan==newsize)) {
+ s=0;Sf=0;lenf=0;
+ for(i=0;(lastscan+i<scan)&&(lastpos+i<oldsize);) {
+ if(old[lastpos+i]==new[lastscan+i]) s++;
+ i++;
+ if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
+ };
- BZ2_bzReadClose(&bzerr, cpfbz2);
- BZ2_bzReadClose(&bzerr, dpfbz2);
- BZ2_bzReadClose(&bzerr, epfbz2);
- fclose(cpf);
- fclose(dpf);
- fclose(epf);
+ lenb=0;
+ if(scan<newsize) {
+ s=0;Sb=0;
+ for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
+ if(old[pos-i]==new[scan-i]) s++;
+ if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
+ };
+ };
- return 0;
+ if(lastscan+lenf>scan-lenb) {
+ overlap=(lastscan+lenf)-(scan-lenb);
+ s=0;Ss=0;lens=0;
+ for(i=0;i<overlap;i++) {
+ if(new[lastscan+lenf-overlap+i]==
+ old[lastpos+lenf-overlap+i]) s++;
+ if(new[scan-lenb+i]==
+ old[pos-lenb+i]) s--;
+ if(s>Ss) { Ss=s; lens=i+1; };
+ };
+
+ lenf+=lens-overlap;
+ lenb-=lens;
+ };
+
+ for(i=0;i<lenf;i++)
+ db[dblen+i]=new[lastscan+i]-old[lastpos+i];
+ for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
+ eb[eblen+i]=new[lastscan+lenf+i];
+
+ dblen+=lenf;
+ eblen+=(scan-lenb)-(lastscan+lenf);
+
+ offtout(lenf,buf);
+ BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
+
+ offtout((scan-lenb)-(lastscan+lenf),buf);
+ BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
+
+ offtout((pos-lenb)-(lastpos+lenf),buf);
+ BZ2_bzWrite(&bz2err, pfbz2, buf, 8);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
+
+ lastscan=scan-lenb;
+ lastpos=pos-lenb;
+ lastoffset=pos-scan;
+ };
+ };
+ BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
+
+ /* Compute size of compressed ctrl data */
+ if ((len = ftello(pf)) == -1)
+ err(1, "ftello");
+ offtout(len-32, header + 8);
+
+ /* Write compressed diff data */
+ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
+ errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
+ BZ2_bzWrite(&bz2err, pfbz2, db, dblen);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
+ BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
+
+ /* Compute size of compressed diff data */
+ if ((newsize = ftello(pf)) == -1)
+ err(1, "ftello");
+ offtout(newsize - len, header + 16);
+
+ /* Write compressed extra data */
+ if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
+ errx(1, "BZ2_bzWriteOpen, bz2err = %d", bz2err);
+ BZ2_bzWrite(&bz2err, pfbz2, eb, eblen);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWrite, bz2err = %d", bz2err);
+ BZ2_bzWriteClose(&bz2err, pfbz2, 0, NULL, NULL);
+ if (bz2err != BZ_OK)
+ errx(1, "BZ2_bzWriteClose, bz2err = %d", bz2err);
+
+ /* Seek to the beginning, write the header, and close the file */
+ if (fseeko(pf, 0, SEEK_SET))
+ err(1, "fseeko");
+ if (fwrite(header, 32, 1, pf) != 1)
+ err(1, "fwrite(%s)", patch_filename);
+ if (fclose(pf))
+ err(1, "fclose");
+
+ /* Free the memory we used */
+ free(db);
+ free(eb);
+
+ return 0;
}
diff --git a/tools/applypatch/bspatch.c b/tools/applypatch/bspatch.c
new file mode 100644
index 0000000..d5cd617
--- /dev/null
+++ b/tools/applypatch/bspatch.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+// This file is a nearly line-for-line copy of bspatch.c from the
+// bsdiff-4.3 distribution; the primary differences being how the
+// input and output data are read and the error handling. Running
+// applypatch with the -l option will display the bsdiff license
+// notice.
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <bzlib.h>
+
+#include "mincrypt/sha.h"
+#include "applypatch.h"
+
+void ShowBSDiffLicense() {
+ puts("The bsdiff library used herein is:\n"
+ "\n"
+ "Copyright 2003-2005 Colin Percival\n"
+ "All rights reserved\n"
+ "\n"
+ "Redistribution and use in source and binary forms, with or without\n"
+ "modification, are permitted providing that the following conditions\n"
+ "are met:\n"
+ "1. Redistributions of source code must retain the above copyright\n"
+ " notice, this list of conditions and the following disclaimer.\n"
+ "2. Redistributions in binary form must reproduce the above copyright\n"
+ " notice, this list of conditions and the following disclaimer in the\n"
+ " documentation and/or other materials provided with the distribution.\n"
+ "\n"
+ "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
+ "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
+ "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
+ "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY\n"
+ "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n"
+ "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n"
+ "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
+ "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
+ "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
+ "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
+ "POSSIBILITY OF SUCH DAMAGE.\n"
+ "\n------------------\n\n"
+ "This program uses Julian R Seward's \"libbzip2\" library, available\n"
+ "from http://www.bzip.org/.\n"
+ );
+}
+
+static off_t offtin(u_char *buf)
+{
+ off_t y;
+
+ y=buf[7]&0x7F;
+ y=y*256;y+=buf[6];
+ y=y*256;y+=buf[5];
+ y=y*256;y+=buf[4];
+ y=y*256;y+=buf[3];
+ y=y*256;y+=buf[2];
+ y=y*256;y+=buf[1];
+ y=y*256;y+=buf[0];
+
+ if(buf[7]&0x80) y=-y;
+
+ return y;
+}
+
+
+int ApplyBSDiffPatch(const unsigned char* old_data, ssize_t old_size,
+ const char* patch_filename, ssize_t patch_offset,
+ SinkFn sink, void* token, SHA_CTX* ctx) {
+
+ unsigned char* new_data;
+ ssize_t new_size;
+ if (ApplyBSDiffPatchMem(old_data, old_size, patch_filename, patch_offset,
+ &new_data, &new_size) != 0) {
+ return -1;
+ }
+
+ if (sink(new_data, new_size, token) < new_size) {
+ fprintf(stderr, "short write of output: %d (%s)\n", errno, strerror(errno));
+ return 1;
+ }
+ if (ctx) {
+ SHA_update(ctx, new_data, new_size);
+ }
+ free(new_data);
+
+ return 0;
+}
+
+int ApplyBSDiffPatchMem(const unsigned char* old_data, ssize_t old_size,
+ const char* patch_filename, ssize_t patch_offset,
+ unsigned char** new_data, ssize_t* new_size) {
+
+ FILE* f;
+ if ((f = fopen(patch_filename, "rb")) == NULL) {
+ fprintf(stderr, "failed to open patch file\n");
+ return 1;
+ }
+
+ // File format:
+ // 0 8 "BSDIFF40"
+ // 8 8 X
+ // 16 8 Y
+ // 24 8 sizeof(newfile)
+ // 32 X bzip2(control block)
+ // 32+X Y bzip2(diff block)
+ // 32+X+Y ??? bzip2(extra block)
+ // with control block a set of triples (x,y,z) meaning "add x bytes
+ // from oldfile to x bytes from the diff block; copy y bytes from the
+ // extra block; seek forwards in oldfile by z bytes".
+
+ fseek(f, patch_offset, SEEK_SET);
+
+ unsigned char header[32];
+ if (fread(header, 1, 32, f) < 32) {
+ fprintf(stderr, "failed to read patch file header\n");
+ return 1;
+ }
+
+ if (memcmp(header, "BSDIFF40", 8) != 0) {
+ fprintf(stderr, "corrupt bsdiff patch file header (magic number)\n");
+ return 1;
+ }
+
+ ssize_t ctrl_len, data_len;
+ ctrl_len = offtin(header+8);
+ data_len = offtin(header+16);
+ *new_size = offtin(header+24);
+
+ if (ctrl_len < 0 || data_len < 0 || *new_size < 0) {
+ fprintf(stderr, "corrupt patch file header (data lengths)\n");
+ return 1;
+ }
+
+ fclose(f);
+
+ int bzerr;
+
+#define OPEN_AT(f, bzf, offset) \
+ FILE* f; \
+ BZFILE* bzf; \
+ if ((f = fopen(patch_filename, "rb")) == NULL) { \
+ fprintf(stderr, "failed to open patch file\n"); \
+ return 1; \
+ } \
+ if (fseeko(f, offset+patch_offset, SEEK_SET)) { \
+ fprintf(stderr, "failed to seek in patch file\n"); \
+ return 1; \
+ } \
+ if ((bzf = BZ2_bzReadOpen(&bzerr, f, 0, 0, NULL, 0)) == NULL) { \
+ fprintf(stderr, "failed to bzReadOpen in patch file (%d)\n", bzerr); \
+ return 1; \
+ }
+
+ OPEN_AT(cpf, cpfbz2, 32);
+ OPEN_AT(dpf, dpfbz2, 32+ctrl_len);
+ OPEN_AT(epf, epfbz2, 32+ctrl_len+data_len);
+
+#undef OPEN_AT
+
+ *new_data = malloc(*new_size);
+ if (*new_data == NULL) {
+ fprintf(stderr, "failed to allocate %d bytes of memory for output file\n",
+ (int)*new_size);
+ return 1;
+ }
+
+ off_t oldpos = 0, newpos = 0;
+ off_t ctrl[3];
+ off_t len_read;
+ int i;
+ unsigned char buf[8];
+ while (newpos < *new_size) {
+ // Read control data
+ for (i = 0; i < 3; ++i) {
+ len_read = BZ2_bzRead(&bzerr, cpfbz2, buf, 8);
+ if (len_read < 8 || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
+ fprintf(stderr, "corrupt patch (read control)\n");
+ return 1;
+ }
+ ctrl[i] = offtin(buf);
+ }
+
+ // Sanity check
+ if (newpos + ctrl[0] > *new_size) {
+ fprintf(stderr, "corrupt patch (new file overrun)\n");
+ return 1;
+ }
+
+ // Read diff string
+ len_read = BZ2_bzRead(&bzerr, dpfbz2, *new_data + newpos, ctrl[0]);
+ if (len_read < ctrl[0] || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
+ fprintf(stderr, "corrupt patch (read diff)\n");
+ return 1;
+ }
+
+ // Add old data to diff string
+ for (i = 0; i < ctrl[0]; ++i) {
+ if ((oldpos+i >= 0) && (oldpos+i < old_size)) {
+ (*new_data)[newpos+i] += old_data[oldpos+i];
+ }
+ }
+
+ // Adjust pointers
+ newpos += ctrl[0];
+ oldpos += ctrl[0];
+
+ // Sanity check
+ if (newpos + ctrl[1] > *new_size) {
+ fprintf(stderr, "corrupt patch (new file overrun)\n");
+ return 1;
+ }
+
+ // Read extra string
+ len_read = BZ2_bzRead(&bzerr, epfbz2, *new_data + newpos, ctrl[1]);
+ if (len_read < ctrl[1] || !(bzerr == BZ_OK || bzerr == BZ_STREAM_END)) {
+ fprintf(stderr, "corrupt patch (read extra)\n");
+ return 1;
+ }
+
+ // Adjust pointers
+ newpos += ctrl[1];
+ oldpos += ctrl[2];
+ }
+
+ BZ2_bzReadClose(&bzerr, cpfbz2);
+ BZ2_bzReadClose(&bzerr, dpfbz2);
+ BZ2_bzReadClose(&bzerr, epfbz2);
+ fclose(cpf);
+ fclose(dpf);
+ fclose(epf);
+
+ return 0;
+}
diff --git a/tools/applypatch/imgdiff.c b/tools/applypatch/imgdiff.c
index 51835b4..6ff9b6f 100644
--- a/tools/applypatch/imgdiff.c
+++ b/tools/applypatch/imgdiff.c
@@ -119,6 +119,7 @@
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <sys/types.h>
#include "zlib.h"
#include "imgdiff.h"
@@ -134,6 +135,8 @@
size_t source_start;
size_t source_len;
+ off_t* I; // used by bsdiff
+
// --- for CHUNK_DEFLATE chunks only: ---
// original (compressed) deflate data
@@ -167,6 +170,10 @@
}
}
+// from bsdiff.c
+int bsdiff(u_char* old, off_t oldsize, off_t** IP, u_char* new, off_t newsize,
+ const char* patch_filename);
+
unsigned char* ReadZip(const char* filename,
int* num_chunks, ImageChunk** chunks,
int include_pseudo_chunk) {
@@ -278,6 +285,7 @@
curr->len = st.st_size;
curr->data = img;
curr->filename = NULL;
+ curr->I = NULL;
++curr;
++*num_chunks;
}
@@ -292,6 +300,7 @@
curr->deflate_len = temp_entries[nextentry].deflate_len;
curr->deflate_data = img + pos;
curr->filename = temp_entries[nextentry].filename;
+ curr->I = NULL;
curr->len = temp_entries[nextentry].uncomp_len;
curr->data = malloc(curr->len);
@@ -336,6 +345,7 @@
}
curr->data = img + pos;
curr->filename = NULL;
+ curr->I = NULL;
pos += curr->len;
++*num_chunks;
@@ -400,6 +410,7 @@
curr->type = CHUNK_NORMAL;
curr->len = GZIP_HEADER_LEN;
curr->data = p;
+ curr->I = NULL;
pos += curr->len;
p += curr->len;
@@ -407,6 +418,7 @@
curr->type = CHUNK_DEFLATE;
curr->filename = NULL;
+ curr->I = NULL;
// We must decompress this chunk in order to discover where it
// ends, and so we can put the uncompressed data and its length
@@ -452,6 +464,7 @@
curr->start = pos;
curr->len = GZIP_FOOTER_LEN;
curr->data = img+pos;
+ curr->I = NULL;
pos += curr->len;
p += curr->len;
@@ -475,6 +488,7 @@
*chunks = realloc(*chunks, *num_chunks * sizeof(ImageChunk));
ImageChunk* curr = *chunks + (*num_chunks-1);
curr->start = pos;
+ curr->I = NULL;
// 'pos' is not the offset of the start of a gzip chunk, so scan
// forward until we find a gzip header.
@@ -591,43 +605,12 @@
}
}
- char stemp[] = "/tmp/imgdiff-src-XXXXXX";
- char ttemp[] = "/tmp/imgdiff-tgt-XXXXXX";
char ptemp[] = "/tmp/imgdiff-patch-XXXXXX";
- mkstemp(stemp);
- mkstemp(ttemp);
mkstemp(ptemp);
- FILE* f = fopen(stemp, "wb");
- if (f == NULL) {
- fprintf(stderr, "failed to open src chunk %s: %s\n",
- stemp, strerror(errno));
- return NULL;
- }
- if (fwrite(src->data, 1, src->len, f) != src->len) {
- fprintf(stderr, "failed to write src chunk to %s: %s\n",
- stemp, strerror(errno));
- return NULL;
- }
- fclose(f);
-
- f = fopen(ttemp, "wb");
- if (f == NULL) {
- fprintf(stderr, "failed to open tgt chunk %s: %s\n",
- ttemp, strerror(errno));
- return NULL;
- }
- if (fwrite(tgt->data, 1, tgt->len, f) != tgt->len) {
- fprintf(stderr, "failed to write tgt chunk to %s: %s\n",
- ttemp, strerror(errno));
- return NULL;
- }
- fclose(f);
-
- char cmd[200];
- sprintf(cmd, "bsdiff %s %s %s", stemp, ttemp, ptemp);
- if (system(cmd) != 0) {
- fprintf(stderr, "failed to run bsdiff: %s\n", strerror(errno));
+ int r = bsdiff(src->data, src->len, &(src->I), tgt->data, tgt->len, ptemp);
+ if (r != 0) {
+ fprintf(stderr, "bsdiff() failed: %d\n", r);
return NULL;
}
@@ -641,8 +624,6 @@
unsigned char* data = malloc(st.st_size);
if (tgt->type == CHUNK_NORMAL && tgt->len <= st.st_size) {
- unlink(stemp);
- unlink(ttemp);
unlink(ptemp);
tgt->type = CHUNK_RAW;
@@ -652,7 +633,7 @@
*size = st.st_size;
- f = fopen(ptemp, "rb");
+ FILE* f = fopen(ptemp, "rb");
if (f == NULL) {
fprintf(stderr, "failed to open patch %s: %s\n", ptemp, strerror(errno));
return NULL;
@@ -663,8 +644,6 @@
}
fclose(f);
- unlink(stemp);
- unlink(ttemp);
unlink(ptemp);
tgt->source_start = src->start;
@@ -784,6 +763,14 @@
return NULL;
}
+void DumpChunks(ImageChunk* chunks, int num_chunks) {
+ int i;
+ for (i = 0; i < num_chunks; ++i) {
+ printf("chunk %d: type %d start %d len %d\n",
+ i, chunks[i].type, chunks[i].start, chunks[i].len);
+ }
+}
+
int main(int argc, char** argv) {
if (argc != 4 && argc != 5) {
usage:
@@ -829,14 +816,29 @@
// Verify that the source and target images have the same chunk
// structure (ie, the same sequence of deflate and normal chunks).
+ if (!zip_mode) {
+ // Merge the gzip header and footer in with any adjacent
+ // normal chunks.
+ MergeAdjacentNormalChunks(tgt_chunks, &num_tgt_chunks);
+ MergeAdjacentNormalChunks(src_chunks, &num_src_chunks);
+ }
+
if (num_src_chunks != num_tgt_chunks) {
fprintf(stderr, "source and target don't have same number of chunks!\n");
+ printf("source chunks:\n");
+ DumpChunks(src_chunks, num_src_chunks);
+ printf("target chunks:\n");
+ DumpChunks(tgt_chunks, num_tgt_chunks);
return 1;
}
for (i = 0; i < num_src_chunks; ++i) {
if (src_chunks[i].type != tgt_chunks[i].type) {
fprintf(stderr, "source and target don't have same chunk "
"structure! (chunk %d)\n", i);
+ printf("source chunks:\n");
+ DumpChunks(src_chunks, num_src_chunks);
+ printf("target chunks:\n");
+ DumpChunks(tgt_chunks, num_tgt_chunks);
return 1;
}
}
diff --git a/tools/apriori/prelinkmap.c b/tools/apriori/prelinkmap.c
index 739c181..9fb00e4 100644
--- a/tools/apriori/prelinkmap.c
+++ b/tools/apriori/prelinkmap.c
@@ -7,11 +7,14 @@
typedef struct mapentry mapentry;
+#define MAX_ALIASES 10
+
struct mapentry
{
mapentry *next;
unsigned base;
- char name[0];
+ char *names[MAX_ALIASES];
+ int num_names;
};
static mapentry *maplist = 0;
@@ -22,14 +25,13 @@
*/
#define PRELINK_MIN 0x90000000
-#define PRELINK_MAX 0xB0000000
+#define PRELINK_MAX 0xBFFFFFFF
void pm_init(const char *file)
{
unsigned line = 0;
char buf[256];
char *x;
- unsigned n;
FILE *fp;
mapentry *me;
unsigned last = -1UL;
@@ -65,26 +67,52 @@
continue;
}
- n = strtoul(x, 0, 16);
- /* Note that this is not the only bounds check. If a library's size
- exceeds its slot as defined in the prelink map, the prelinker will
- exit with an error. See pm_report_library_size_in_memory().
- */
- FAILIF((n < PRELINK_MIN) || (n > PRELINK_MAX),
- "%s:%d base 0x%08x out of range.\n",
- file, line, n);
-
- me = malloc(sizeof(mapentry) + strlen(buf) + 1);
- FAILIF(me == NULL, "Out of memory parsing %s\n", file);
+ if (isalpha(*x)) {
+ /* Assume that this is an alias, and look through the list of
+ already-installed libraries.
+ */
+ me = maplist;
+ while(me) {
+ /* The strlen() call ignores the newline at the end of x */
+ if (!strncmp(me->names[0], x, strlen(me->names[0]))) {
+ PRINT("Aliasing library %s to %s at %08x\n",
+ buf, x, me->base);
+ break;
+ }
+ me = me->next;
+ }
+ FAILIF(!me, "Nonexistent alias %s -> %s\n", buf, x);
+ }
+ else {
+ unsigned n = strtoul(x, 0, 16);
+ /* Note that this is not the only bounds check. If a library's
+ size exceeds its slot as defined in the prelink map, the
+ prelinker will exit with an error. See
+ pm_report_library_size_in_memory().
+ */
+ FAILIF((n < PRELINK_MIN) || (n > PRELINK_MAX),
+ "%s:%d base 0x%08x out of range.\n",
+ file, line, n);
- FAILIF(last <= n, "The prelink map is not in descending order "
- "at entry %s (%08x)!\n", buf, n);
- last = n;
-
- me->base = n;
- strcpy(me->name, buf);
- me->next = maplist;
- maplist = me;
+ me = malloc(sizeof(mapentry));
+ FAILIF(me == NULL, "Out of memory parsing %s\n", file);
+
+ FAILIF(last <= n, "The prelink map is not in descending order "
+ "at entry %s (%08x)!\n", buf, n);
+ last = n;
+
+ me->base = n;
+ me->next = maplist;
+ me->num_names = 0;
+ maplist = me;
+ }
+
+ FAILIF(me->num_names >= MAX_ALIASES,
+ "Too many aliases for library %s, maximum is %d.\n",
+ me->names[0],
+ MAX_ALIASES);
+ me->names[me->num_names] = strdup(buf);
+ me->num_names++;
}
fclose(fp);
@@ -99,41 +127,44 @@
{
char *x;
mapentry *me;
+ int n;
x = strrchr(name,'/');
if(x) name = x+1;
for(me = maplist; me; me = me->next){
- if(!strcmp(name, me->name)) {
- off_t slot = me->next ? me->next->base : PRELINK_MAX;
- slot -= me->base;
- FAILIF(fsize > slot,
- "prelink map error: library %s@0x%08x is too big "
- "at %lld bytes, it runs %lld bytes into "
- "library %s@0x%08x!\n",
- me->name, me->base, fsize, fsize - slot,
- me->next->name, me->next->base);
- break;
+ for (n = 0; n < me->num_names; n++) {
+ if(!strcmp(name, me->names[n])) {
+ off_t slot = me->next ? me->next->base : PRELINK_MAX;
+ slot -= me->base;
+ FAILIF(fsize > slot,
+ "prelink map error: library %s@0x%08x is too big "
+ "at %lld bytes, it runs %lld bytes into "
+ "library %s@0x%08x!\n",
+ me->names[0], me->base, fsize, fsize - slot,
+ me->next->names[0], me->next->base);
+ return;
+ }
}
}
- FAILIF(!me,"library '%s' not in prelink map\n", name);
+ FAILIF(1, "library '%s' not in prelink map\n", name);
}
unsigned pm_get_next_link_address(const char *lookup_name)
{
char *x;
mapentry *me;
+ int n;
x = strrchr(lookup_name,'/');
if(x) lookup_name = x+1;
- for(me = maplist; me; me = me->next){
- if(!strcmp(lookup_name, me->name)) {
- return me->base;
- }
- }
-
- FAILIF(1==1,"library '%s' not in prelink map\n", lookup_name);
+ for(me = maplist; me; me = me->next)
+ for (n = 0; n < me->num_names; n++)
+ if(!strcmp(lookup_name, me->names[n]))
+ return me->base;
+
+ FAILIF(1, "library '%s' not in prelink map\n", lookup_name);
return 0;
}
diff --git a/tools/atree/files.cpp b/tools/atree/files.cpp
index c675ab7..d4866d4 100644
--- a/tools/atree/files.cpp
+++ b/tools/atree/files.cpp
@@ -7,6 +7,8 @@
#include <unistd.h>
#include <dirent.h>
#include <fnmatch.h>
+#include <string.h>
+#include <stdlib.h>
static bool
is_comment_line(const char* p)
diff --git a/tools/atree/fs.cpp b/tools/atree/fs.cpp
index 022bd8c..00f44c2 100644
--- a/tools/atree/fs.cpp
+++ b/tools/atree/fs.cpp
@@ -10,6 +10,7 @@
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <string.h>
#include <host/CopyFile.h>
using namespace std;
diff --git a/tools/buildinfo.sh b/tools/buildinfo.sh
index 5c738a2..af5aa47 100755
--- a/tools/buildinfo.sh
+++ b/tools/buildinfo.sh
@@ -24,6 +24,7 @@
echo "ro.product.manufacturer=$PRODUCT_MANUFACTURER"
echo "ro.product.locale.language=$PRODUCT_DEFAULT_LANGUAGE"
echo "ro.product.locale.region=$PRODUCT_DEFAULT_REGION"
+echo "ro.wifi.channels=$PRODUCT_DEFAULT_WIFI_CHANNELS"
echo "ro.board.platform=$TARGET_BOARD_PLATFORM"
echo "# ro.build.product is obsolete; use ro.product.device"
diff --git a/tools/dexpreopt/Config.mk b/tools/dexpreopt/Config.mk
index c6639b2..443b8c9 100644
--- a/tools/dexpreopt/Config.mk
+++ b/tools/dexpreopt/Config.mk
@@ -77,6 +77,13 @@
$(shell echo "$(p) $(PACKAGES.$(p).CERTIFICATE) $(PACKAGES.$(p).PRIVATE_KEY)" >> $(dexpreopt_package_certs_file)))
endif
+# The kernel used for ARMv7 system images is different
+ifeq ($(TARGET_ARCH_VARIANT),armv7-a)
+BUILD_DEXPREOPT_KERNEL := prebuilt/android-arm/kernel/kernel-qemu-armv7
+else
+BUILD_DEXPREOPT_KERNEL := prebuilt/android-arm/kernel/kernel-qemu
+endif
+
# Build an optimized image from the unoptimized image
BUILT_DEXPREOPT_SYSTEMIMAGE := $(intermediates)/system.img
$(BUILT_DEXPREOPT_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE_UNOPT)
@@ -99,7 +106,7 @@
$(hide) \
PATH=$(HOST_OUT_EXECUTABLES):$$PATH \
$(DEXPREOPT) \
- --kernel prebuilt/android-arm/kernel/kernel-qemu \
+ --kernel $(BUILD_DEXPREOPT_KERNEL) \
--ramdisk $(BUILT_DEXPREOPT_RAMDISK) \
--image $(BUILT_SYSTEMIMAGE_UNOPT) \
--system $(PRODUCT_OUT) \
diff --git a/tools/droiddoc/src/AnnotationInstanceInfo.java b/tools/droiddoc/src/AnnotationInstanceInfo.java
index 07d4aa3..c4abc7e 100644
--- a/tools/droiddoc/src/AnnotationInstanceInfo.java
+++ b/tools/droiddoc/src/AnnotationInstanceInfo.java
@@ -35,6 +35,7 @@
return mElementValues;
}
+ @Override
public String toString()
{
StringBuilder str = new StringBuilder();
diff --git a/tools/droiddoc/src/AttrTagInfo.java b/tools/droiddoc/src/AttrTagInfo.java
index abc5452..7f1b4d9 100644
--- a/tools/droiddoc/src/AttrTagInfo.java
+++ b/tools/droiddoc/src/AttrTagInfo.java
@@ -98,7 +98,8 @@
public FieldInfo reference() {
return REF_COMMAND.equals(mCommand) ? mRefField : null;
}
-
+
+ @Override
public String name() {
return NAME_COMMAND.equals(mCommand) ? mAttrName : null;
}
@@ -107,6 +108,7 @@
return DESCRIPTION_COMMAND.equals(mCommand) ? mDescrComment : null;
}
+ @Override
public void makeHDF(HDF data, String base)
{
super.makeHDF(data, base);
diff --git a/tools/droiddoc/src/ClassInfo.java b/tools/droiddoc/src/ClassInfo.java
index 8180436..f3f11de 100644
--- a/tools/droiddoc/src/ClassInfo.java
+++ b/tools/droiddoc/src/ClassInfo.java
@@ -101,7 +101,7 @@
mSelfFields = null;
mSelfAttributes = null;
mDeprecatedKnown = false;
-
+
Arrays.sort(mEnumConstants, FieldInfo.comparator);
Arrays.sort(mInnerClasses, ClassInfo.comparator);
}
@@ -111,16 +111,16 @@
// objects
selfAttributes();
}
-
+
public void init3(TypeInfo[] types, ClassInfo[] realInnerClasses){
mTypeParameters = types;
mRealInnerClasses = realInnerClasses;
}
-
+
public ClassInfo[] getRealInnerClasses(){
return mRealInnerClasses;
}
-
+
public TypeInfo[] getTypeParameters(){
return mTypeParameters;
}
@@ -146,6 +146,7 @@
}
}
+ @Override
public ContainerInfo parent()
{
return this;
@@ -351,7 +352,7 @@
{
return comment().briefTags();
}
-
+
public boolean isDeprecated() {
boolean deprecated = false;
if (!mDeprecatedKnown) {
@@ -378,7 +379,7 @@
public TagInfo[] deprecatedTags()
{
- // should we also do the interfaces?
+ // Should we also do the interfaces?
return comment().deprecatedTags();
}
@@ -551,7 +552,7 @@
public MethodInfo[] allSelfMethods() {
return mAllSelfMethods;
}
-
+
public void addMethod(MethodInfo method) {
MethodInfo[] methods = new MethodInfo[mAllSelfMethods.length + 1];
int i = 0;
@@ -596,7 +597,7 @@
}
}
}
-
+
//constructors too
for (MethodInfo m: constructors()) {
for (AttrTagInfo tag: m.comment().attrTags()) {
@@ -1136,7 +1137,11 @@
if (kind != null) {
data.setValue(base + ".kind", kind);
}
-
+
+ if (cl.mIsIncluded) {
+ data.setValue(base + ".included", "true");
+ }
+
// xml attributes
i=0;
for (AttributeInfo attr: cl.selfAttributes()) {
@@ -1170,6 +1175,7 @@
}
}
+ @Override
public boolean isHidden()
{
int val = mHidden;
@@ -1301,7 +1307,7 @@
return f;
}
}
-
+
// then look at our enum constants (these are really fields, maybe
// they should be mixed into fields(). not sure)
for (FieldInfo f: enumConstants()) {
@@ -1346,11 +1352,11 @@
return false;
}
}
-
+
public void setNonWrittenConstructors(MethodInfo[] nonWritten) {
mNonWrittenConstructors = nonWritten;
}
-
+
public MethodInfo[] getNonWrittenConstructors() {
return mNonWrittenConstructors;
}
@@ -1377,23 +1383,24 @@
}
return null;
}
-
+
public void setHiddenMethods(MethodInfo[] mInfo){
mHiddenMethods = mInfo;
}
public MethodInfo[] getHiddenMethods(){
return mHiddenMethods;
}
+ @Override
public String toString(){
return this.qualifiedName();
}
-
+
public void setReasonIncluded(String reason) {
mReasonIncluded = reason;
}
-
+
public String getReasonIncluded() {
- return mReasonIncluded;
+ return mReasonIncluded;
}
private ClassDoc mClass;
diff --git a/tools/droiddoc/src/Comment.java b/tools/droiddoc/src/Comment.java
index 3f1bf6c..553cdf2 100644
--- a/tools/droiddoc/src/Comment.java
+++ b/tools/droiddoc/src/Comment.java
@@ -157,7 +157,7 @@
else if (name.equals("@literal")) {
mInlineTagsList.add(new LiteralTagInfo(name, name, text, pos));
}
- else if (name.equals("@hide") || name.equals("@doconly")) {
+ else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) {
// nothing
}
else if (name.equals("@attr")) {
@@ -206,7 +206,7 @@
for (int i=0; i<N; i++) {
if (mInlineTagsList.get(i).name().equals("@more")) {
more = i;
- }
+ }
}
if (more >= 0) {
for (int i=0; i<more; i++) {
@@ -225,7 +225,7 @@
}
}
mBriefTagsList.add(t);
-
+
}
}
}
@@ -307,12 +307,12 @@
mHidden = 0;
return false;
}
- boolean b = mText.indexOf("@hide") >= 0;
+ boolean b = mText.indexOf("@hide") >= 0 || mText.indexOf("@pending") >= 0;
mHidden = b ? 1 : 0;
return b;
}
}
-
+
public boolean isDocOnly() {
if (mDocOnly >= 0) {
return mDocOnly != 0;
@@ -391,5 +391,5 @@
ArrayList<TagInfo> mUndeprecateTagsList = new ArrayList<TagInfo>();
ArrayList<AttrTagInfo> mAttrTagsList = new ArrayList<AttrTagInfo>();
-
+
}
diff --git a/tools/droiddoc/src/Converter.java b/tools/droiddoc/src/Converter.java
index 4014f7f..ee911f4 100644
--- a/tools/droiddoc/src/Converter.java
+++ b/tools/droiddoc/src/Converter.java
@@ -238,6 +238,7 @@
}
private static Cache mClasses = new Cache()
{
+ @Override
protected Object make(Object o)
{
ClassDoc c = (ClassDoc)o;
@@ -268,19 +269,21 @@
}
return cl;
}
+ @Override
protected void made(Object o, Object r)
{
if (mClassesNeedingInit == null) {
initClass((ClassDoc)o, (ClassInfo)r);
((ClassInfo)r).init2();
}
- }
+ }
+ @Override
ClassInfo[] all()
{
return (ClassInfo[])mCache.values().toArray(new ClassInfo[mCache.size()]);
}
};
-
+
private static MethodInfo[] getHiddenMethods(MethodDoc[] methods){
if (methods == null) return null;
ArrayList<MethodInfo> out = new ArrayList<MethodInfo>();
@@ -342,7 +345,7 @@
}
return out.toArray(new MethodInfo[out.size()]);
}
-
+
private static MethodInfo[] convertNonWrittenConstructors(ConstructorDoc[] methods)
{
if (methods == null) return null;
@@ -367,6 +370,7 @@
}
private static Cache mMethods = new Cache()
{
+ @Override
protected Object make(Object o)
{
if (o instanceof AnnotationTypeElementDoc) {
@@ -374,7 +378,7 @@
MethodInfo result = new MethodInfo(
m.getRawCommentText(),
Converter.convertTypes(m.typeParameters()),
- m.name(), m.signature(),
+ m.name(), m.signature(),
Converter.obtainClass(m.containingClass()),
Converter.obtainClass(m.containingClass()),
m.isPublic(), m.isProtected(),
@@ -399,7 +403,7 @@
MethodInfo result = new MethodInfo(
m.getRawCommentText(),
Converter.convertTypes(m.typeParameters()),
- m.name(), m.signature(),
+ m.name(), m.signature(),
Converter.obtainClass(m.containingClass()),
Converter.obtainClass(m.containingClass()),
m.isPublic(), m.isProtected(),
@@ -424,7 +428,7 @@
MethodInfo result = new MethodInfo(
m.getRawCommentText(),
Converter.convertTypes(m.typeParameters()),
- m.name(), m.signature(),
+ m.name(), m.signature(),
Converter.obtainClass(m.containingClass()),
Converter.obtainClass(m.containingClass()),
m.isPublic(), m.isProtected(),
@@ -472,6 +476,7 @@
}
private static Cache mFields = new Cache()
{
+ @Override
protected Object make(Object o)
{
FieldDoc f = (FieldDoc)o;
@@ -496,6 +501,7 @@
}
private static Cache mPackagees = new Cache()
{
+ @Override
protected Object make(Object o)
{
PackageDoc p = (PackageDoc)o;
@@ -510,7 +516,8 @@
}
private static Cache mTypes = new Cache()
{
- protected Object make(Object o)
+ @Override
+ protected Object make(Object o)
{
Type t = (Type)o;
String simpleTypeName;
@@ -524,6 +531,7 @@
Converter.obtainClass(t.asClassDoc()));
return ti;
}
+ @Override
protected void made(Object o, Object r)
{
Type t = (Type)o;
@@ -545,8 +553,9 @@
Converter.convertTypes(t.asWildcardType().extendsBounds()));
}
}
+ @Override
protected Object keyFor(Object o)
- {
+ {
Type t = (Type)o;
String keyString = o.getClass().getName() + "/" + o.toString() + "/";
if (t.asParameterizedType() != null){
@@ -584,13 +593,13 @@
}else{
keyString += "NoWildCardType//";
}
-
-
-
+
+
+
return keyString;
}
};
-
+
private static MemberInfo obtainMember(MemberDoc o)
@@ -599,6 +608,7 @@
}
private static Cache mMembers = new Cache()
{
+ @Override
protected Object make(Object o)
{
if (o instanceof MethodDoc) {
@@ -633,6 +643,7 @@
}
private static Cache mAnnotationInstances = new Cache()
{
+ @Override
protected Object make(Object o)
{
AnnotationDesc a = (AnnotationDesc)o;
diff --git a/tools/droiddoc/src/DroidDoc.java b/tools/droiddoc/src/DroidDoc.java
index 4ff26bc..f48b56c 100644
--- a/tools/droiddoc/src/DroidDoc.java
+++ b/tools/droiddoc/src/DroidDoc.java
@@ -40,7 +40,7 @@
private static final int TYPE_WIDGET = 1;
private static final int TYPE_LAYOUT = 2;
private static final int TYPE_LAYOUT_PARAM = 3;
-
+
public static final int SHOW_PUBLIC = 0x00000001;
public static final int SHOW_PROTECTED = 0x00000003;
public static final int SHOW_PACKAGE = 0x00000007;
@@ -84,7 +84,7 @@
}
return false;
}
-
+
public static boolean start(RootDoc r)
{
String keepListFile = null;
@@ -95,6 +95,7 @@
String stubsDir = null;
//Create the dependency graph for the stubs directory
boolean apiXML = false;
+ boolean noDocs = false;
String apiFile = null;
String debugStubsFile = "";
HashSet<String> stubPackages = null;
@@ -187,6 +188,9 @@
apiXML = true;
apiFile = a[1];
}
+ else if (a[0].equals("-nodocs")) {
+ noDocs = true;
+ }
else if (a[0].equals("-since")) {
sinceTagger.addVersion(a[1], a[2]);
}
@@ -200,62 +204,70 @@
// Set up the data structures
Converter.makeInfo(r);
- // Files for proofreading
- if (proofreadFile != null) {
- Proofread.initProofread(proofreadFile);
+ if (!noDocs) {
+ long startTime = System.nanoTime();
+
+ // Apply @since tags from the XML file
+ sinceTagger.tagAll(Converter.rootClasses());
+
+ // Files for proofreading
+ if (proofreadFile != null) {
+ Proofread.initProofread(proofreadFile);
+ }
+ if (todoFile != null) {
+ TodoFile.writeTodoFile(todoFile);
+ }
+
+ // HTML Pages
+ if (ClearPage.htmlDir != null) {
+ writeHTMLPages();
+ }
+
+ // Navigation tree
+ NavTree.writeNavTree(javadocDir);
+
+ // Packages Pages
+ writePackages(javadocDir
+ + (ClearPage.htmlDir!=null
+ ? "packages" + htmlExtension
+ : "index" + htmlExtension));
+
+ // Classes
+ writeClassLists();
+ writeClasses();
+ writeHierarchy();
+ // writeKeywords();
+
+ // Lists for JavaScript
+ writeLists();
+ if (keepListFile != null) {
+ writeKeepList(keepListFile);
+ }
+
+ // Sample Code
+ for (SampleCode sc: sampleCodes) {
+ sc.write();
+ }
+
+ // Index page
+ writeIndex();
+
+ Proofread.finishProofread(proofreadFile);
+
+ if (sdkValuePath != null) {
+ writeSdkValues(sdkValuePath);
+ }
+
+ long time = System.nanoTime() - startTime;
+ System.out.println("DroidDoc took " + (time / 1000000000) + " sec. to write docs to "
+ + ClearPage.outputDir);
}
- if (todoFile != null) {
- TodoFile.writeTodoFile(todoFile);
- }
-
- // Apply @since tags from the XML file
- sinceTagger.tagAll(Converter.rootClasses());
-
- // HTML Pages
- if (ClearPage.htmlDir != null) {
- writeHTMLPages();
- }
-
- // Navigation tree
- NavTree.writeNavTree(javadocDir);
-
- // Packages Pages
- writePackages(javadocDir
- + (ClearPage.htmlDir!=null
- ? "packages" + htmlExtension
- : "index" + htmlExtension));
-
- // Classes
- writeClassLists();
- writeClasses();
- writeHierarchy();
- // writeKeywords();
-
- // Lists for JavaScript
- writeLists();
- if (keepListFile != null) {
- writeKeepList(keepListFile);
- }
-
- // Sample Code
- for (SampleCode sc: sampleCodes) {
- sc.write();
- }
-
- // Index page
- writeIndex();
-
- Proofread.finishProofread(proofreadFile);
// Stubs
if (stubsDir != null) {
Stubs.writeStubs(stubsDir, apiXML, apiFile, stubPackages);
}
- if (sdkValuePath != null) {
- writeSdkValues(sdkValuePath);
- }
-
Errors.printErrors();
return !Errors.hadError;
}
@@ -401,6 +413,9 @@
if (option.equals("-apixml")) {
return 2;
}
+ if (option.equals("-nodocs")) {
+ return 1;
+ }
if (option.equals("-since")) {
return 3;
}
@@ -777,7 +792,7 @@
data.setValue("package.since", pkg.getSince());
data.setValue("package.descr", "...description...");
- makeClassListHDF(data, "package.interfaces",
+ makeClassListHDF(data, "package.interfaces",
ClassInfo.sortByName(pkg.interfaces()));
makeClassListHDF(data, "package.classes",
ClassInfo.sortByName(pkg.ordinaryClasses()));
@@ -871,7 +886,7 @@
HDF data = makeHDF();
Collections.sort(keywords);
-
+
int i=0;
for (KeywordEntry entry: keywords) {
String base = "keywords." + entry.firstChar() + "." + i;
@@ -964,10 +979,11 @@
}
/**
- * Returns true if the given element has an @hide annotation.
+ * Returns true if the given element has an @hide or @pending annotation.
*/
private static boolean hasHideAnnotation(Doc doc) {
- return doc.getRawCommentText().indexOf("@hide") != -1;
+ String comment = doc.getRawCommentText();
+ return comment.indexOf("@hide") != -1 || comment.indexOf("@pending") != -1;
}
/**
@@ -1059,7 +1075,7 @@
if (methodName.equals("getRawCommentText")) {
return filterComment((String) method.invoke(target, args));
}
-
+
// escape "&" in disjunctive types.
if (proxy instanceof Type && methodName.equals("toString")) {
return ((String) method.invoke(target, args))
@@ -1114,7 +1130,7 @@
throw new RuntimeException("invalid scope for object " + scoped);
}
}
-
+
/**
* Collect the values used by the Dev tools and write them in files packaged with the SDK
* @param output the ouput directory for the files.
@@ -1124,16 +1140,16 @@
ArrayList<String> broadcastActions = new ArrayList<String>();
ArrayList<String> serviceActions = new ArrayList<String>();
ArrayList<String> categories = new ArrayList<String>();
-
+
ArrayList<ClassInfo> layouts = new ArrayList<ClassInfo>();
ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
-
+
ClassInfo[] classes = Converter.allClasses();
// Go through all the fields of all the classes, looking SDK stuff.
for (ClassInfo clazz : classes) {
-
+
// first check constant fields for the SdkConstant annotation.
FieldInfo[] fields = clazz.allSelfFields();
for (FieldInfo field : fields) {
@@ -1162,7 +1178,7 @@
}
}
}
-
+
// Now check the class for @Widget or if its in the android.widget package
// (unless the class is hidden or abstract, or non public)
if (clazz.isHidden() == false && clazz.isPublic() && clazz.isAbstract() == false) {
@@ -1181,7 +1197,7 @@
}
}
}
-
+
if (annotated == false) {
// lets check if this is inside android.widget
PackageInfo pckg = clazz.containingPackage();
@@ -1221,7 +1237,7 @@
Collections.sort(categories);
writeValues(output + "/categories.txt", categories);
-
+
// before writing the list of classes, we do some checks, to make sure the layout params
// are enclosed by a layout class (and not one that has been declared as a widget)
for (int i = 0 ; i < layoutParams.size();) {
@@ -1233,10 +1249,10 @@
i++;
}
}
-
+
writeClasses(output + "/widgets.txt", widgets, layouts, layoutParams);
}
-
+
/**
* Writes a list of values into a text files.
* @param pathname the absolute os path of the output file.
@@ -1248,7 +1264,7 @@
try {
fw = new FileWriter(pathname, false);
bw = new BufferedWriter(fw);
-
+
for (String value : values) {
bw.append(value).append('\n');
}
@@ -1282,7 +1298,7 @@
try {
fw = new FileWriter(pathname, false);
bw = new BufferedWriter(fw);
-
+
// write the 3 types of classes.
for (ClassInfo clazz : widgets) {
writeClass(bw, clazz, 'W');
@@ -1325,7 +1341,7 @@
}
writer.append('\n');
}
-
+
/**
* Checks the inheritance of {@link ClassInfo} objects. This method return
* <ul>
@@ -1333,7 +1349,7 @@
* <li>{@link #TYPE_WIDGET}: if the class extends <code>android.view.View</code></li>
* <li>{@link #TYPE_LAYOUT_PARAM}: if the class extends <code>android.view.ViewGroup$LayoutParams</code></li>
* <li>{@link #TYPE_NONE}: in all other cases</li>
- * </ul>
+ * </ul>
* @param clazz the {@link ClassInfo} to check.
*/
private static int checkInheritance(ClassInfo clazz) {
@@ -1344,12 +1360,12 @@
} else if ("android.view.ViewGroup.LayoutParams".equals(clazz.qualifiedName())) {
return TYPE_LAYOUT_PARAM;
}
-
+
ClassInfo parent = clazz.superclass();
if (parent != null) {
return checkInheritance(parent);
}
-
+
return TYPE_NONE;
}
}
diff --git a/tools/droiddoc/src/Errors.java b/tools/droiddoc/src/Errors.java
index 95439f1..77852f8 100644
--- a/tools/droiddoc/src/Errors.java
+++ b/tools/droiddoc/src/Errors.java
@@ -41,6 +41,7 @@
return this.msg.compareTo(that.msg);
}
+ @Override
public String toString() {
String whereText = this.pos == null ? "unknown: " : this.pos.toString() + ':';
return whereText + this.msg;
diff --git a/tools/droiddoc/src/FieldInfo.java b/tools/droiddoc/src/FieldInfo.java
index 1c975e4..d9371e8 100644
--- a/tools/droiddoc/src/FieldInfo.java
+++ b/tools/droiddoc/src/FieldInfo.java
@@ -26,7 +26,7 @@
return a.name().compareTo(b.name());
}
};
-
+
public FieldInfo(String name, ClassInfo containingClass, ClassInfo realContainingClass,
boolean isPublic, boolean isProtected,
boolean isPackagePrivate, boolean isPrivate,
@@ -92,7 +92,7 @@
{
return constantLiteralValue(mConstantValue);
}
-
+
public boolean isDeprecated() {
boolean deprecated = false;
if (!mDeprecatedKnown) {
@@ -124,7 +124,7 @@
if (val instanceof Boolean
|| val instanceof Byte
|| val instanceof Short
- || val instanceof Integer)
+ || val instanceof Integer)
{
str = val.toString();
}
@@ -291,6 +291,7 @@
}
}
+ @Override
public boolean isExecutable()
{
return false;
diff --git a/tools/droiddoc/src/MemberInfo.java b/tools/droiddoc/src/MemberInfo.java
index 2a2572a..05da583 100644
--- a/tools/droiddoc/src/MemberInfo.java
+++ b/tools/droiddoc/src/MemberInfo.java
@@ -115,6 +115,7 @@
return mIsSynthetic;
}
+ @Override
public ContainerInfo parent()
{
return mContainingClass;
@@ -130,7 +131,7 @@
{
return mKind;
}
-
+
public AnnotationInstanceInfo[] annotations()
{
return mAnnotations;
diff --git a/tools/droiddoc/src/MethodInfo.java b/tools/droiddoc/src/MethodInfo.java
index bded88b..3211038 100644
--- a/tools/droiddoc/src/MethodInfo.java
+++ b/tools/droiddoc/src/MethodInfo.java
@@ -25,9 +25,9 @@
return a.name().compareTo(b.name());
}
};
-
+
private class InlineTags implements InheritedTags
- {
+ {
public TagInfo[] tags()
{
return comment().tags();
@@ -42,7 +42,7 @@
}
}
}
-
+
private static void addInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
{
for (ClassInfo i: ifaces) {
@@ -79,7 +79,7 @@
}
return null;
}
-
+
private static void addRealInterfaces(ClassInfo[] ifaces, ArrayList<ClassInfo> queue)
{
for (ClassInfo i: ifaces) {
@@ -92,7 +92,7 @@
addInterfaces(i.realInterfaces(), queue);
}
}
-
+
public MethodInfo findRealOverriddenMethod(String name, String signature, HashSet notStrippable) {
if (mReturnType == null) {
// ctor
@@ -103,7 +103,7 @@
}
ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
- if (containingClass().realSuperclass() != null &&
+ if (containingClass().realSuperclass() != null &&
containingClass().realSuperclass().isAbstract()) {
queue.add(containingClass());
}
@@ -121,7 +121,7 @@
}
return null;
}
-
+
public MethodInfo findSuperclassImplementation(HashSet notStrippable) {
if (mReturnType == null) {
// ctor
@@ -138,7 +138,7 @@
}
ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
- if (containingClass().realSuperclass() != null &&
+ if (containingClass().realSuperclass() != null &&
containingClass().realSuperclass().isAbstract()) {
queue.add(containingClass());
}
@@ -154,7 +154,7 @@
}
return null;
}
-
+
public ClassInfo findRealOverriddenClass(String name, String signature) {
if (mReturnType == null) {
// ctor
@@ -165,7 +165,7 @@
}
ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
- if (containingClass().realSuperclass() != null &&
+ if (containingClass().realSuperclass() != null &&
containingClass().realSuperclass().isAbstract()) {
queue.add(containingClass());
}
@@ -199,7 +199,7 @@
}
}
}
-
+
private class ReturnTags implements InheritedTags {
public TagInfo[] tags() {
return comment().returnTags();
@@ -213,7 +213,7 @@
}
}
}
-
+
public boolean isDeprecated() {
boolean deprecated = false;
if (!mDeprecatedKnown) {
@@ -237,7 +237,7 @@
}
return mIsDeprecated;
}
-
+
public TypeInfo[] getTypeParameters(){
return mTypeParameters;
}
@@ -274,7 +274,7 @@
// The underlying MethodDoc for an interface's declared methods winds up being marked
// non-abstract. Correct that here by looking at the immediate-parent class, and marking
- // this method abstract if it is an unimplemented interface method.
+ // this method abstract if it is an unimplemented interface method.
if (containingClass.isInterface()) {
isAbstract = true;
}
@@ -448,7 +448,7 @@
+ tag.parameterName() + "'");
}
}
-
+
// get our parent's tags to fill in the blanks
MethodInfo overridden = this.findOverriddenMethod(name(), signature());
if (overridden != null) {
@@ -508,7 +508,7 @@
{
return mParameters;
}
-
+
public boolean matchesParams(String[] params, String[] dimensions)
{
@@ -589,6 +589,7 @@
return result;
}
+ @Override
public boolean isExecutable()
{
return true;
@@ -617,21 +618,23 @@
{
return mDefaultAnnotationElementValue;
}
-
+
public void setVarargs(boolean set){
mIsVarargs = set;
}
public boolean isVarArgs(){
return mIsVarargs;
}
+
+ @Override
public String toString(){
return this.name();
}
-
+
public void setReason(String reason) {
mReasonOpened = reason;
}
-
+
public String getReason() {
return mReasonOpened;
}
diff --git a/tools/droiddoc/src/PackageInfo.java b/tools/droiddoc/src/PackageInfo.java
index 18c636e..17ad1b7 100644
--- a/tools/droiddoc/src/PackageInfo.java
+++ b/tools/droiddoc/src/PackageInfo.java
@@ -57,11 +57,13 @@
return s;
}
+ @Override
public ContainerInfo parent()
{
return null;
}
+ @Override
public boolean isHidden()
{
return comment().isHidden();
diff --git a/tools/droiddoc/src/ParamTagInfo.java b/tools/droiddoc/src/ParamTagInfo.java
index c21ecd5..d6f2b6b 100644
--- a/tools/droiddoc/src/ParamTagInfo.java
+++ b/tools/droiddoc/src/ParamTagInfo.java
@@ -76,6 +76,7 @@
return mParameterName;
}
+ @Override
public void makeHDF(HDF data, String base)
{
data.setValue(base + ".name", parameterName());
diff --git a/tools/droiddoc/src/SampleTagInfo.java b/tools/droiddoc/src/SampleTagInfo.java
index c80083b..c7ad1cc 100644
--- a/tools/droiddoc/src/SampleTagInfo.java
+++ b/tools/droiddoc/src/SampleTagInfo.java
@@ -36,7 +36,7 @@
* Both tags accept either a filename and an id or just a filename. If no id
* is provided, the entire file is copied. If an id is provided, the lines
* in the given file between the first two lines containing BEGIN_INCLUDE(id)
- * and END_INCLUDE(id), for the given id, are copied. The id may be only
+ * and END_INCLUDE(id), for the given id, are copied. The id may be only
* letters, numbers and underscore (_).
*
* Four examples:
@@ -274,6 +274,7 @@
return result.substring(0);
}
+ @Override
public void makeHDF(HDF data, String base)
{
data.setValue(base + ".name", name());
diff --git a/tools/droiddoc/src/SeeTagInfo.java b/tools/droiddoc/src/SeeTagInfo.java
index 94863b5..8420ed3 100644
--- a/tools/droiddoc/src/SeeTagInfo.java
+++ b/tools/droiddoc/src/SeeTagInfo.java
@@ -45,6 +45,7 @@
return linkReference().label;
}
+ @Override
public void makeHDF(HDF data, String base)
{
LinkReference linkRef = linkReference();
diff --git a/tools/droiddoc/src/SourcePositionInfo.java b/tools/droiddoc/src/SourcePositionInfo.java
index 6244803..ac605ec 100644
--- a/tools/droiddoc/src/SourcePositionInfo.java
+++ b/tools/droiddoc/src/SourcePositionInfo.java
@@ -76,6 +76,7 @@
return new SourcePositionInfo(that.file, line, 0);
}
+ @Override
public String toString()
{
return file + ':' + line;
diff --git a/tools/droiddoc/src/TypeInfo.java b/tools/droiddoc/src/TypeInfo.java
index 5196c13..45e9db9 100644
--- a/tools/droiddoc/src/TypeInfo.java
+++ b/tools/droiddoc/src/TypeInfo.java
@@ -249,6 +249,7 @@
}
}
+ @Override
public String toString(){
String returnString = "";
returnString += "Primitive?: " + mIsPrimitive + " TypeVariable?: " +
diff --git a/tools/droiddoc/templates-sdk/customization.cs b/tools/droiddoc/templates-sdk/customization.cs
index 6bdb992..6ae8446 100644
--- a/tools/droiddoc/templates-sdk/customization.cs
+++ b/tools/droiddoc/templates-sdk/customization.cs
@@ -73,9 +73,10 @@
call:default_search_box() ?><?cs
if:reference ?>
<div id="api-level-toggle">
- <label for="apiLevelControl"><a href="<?cs var:toroot ?>guide/appendix/api-levels.html">Filter by API Level</a>: </label>
- <select id="apiLevelControl">
- <!-- option elements added by buildApiLevelToggle() -->
+ <input type="checkbox" id="apiLevelCheckbox" onclick="toggleApiLevelSelector(this)" />
+ <label for="apiLevelCheckbox" class="disabled">Filter by API Level: </label>
+ <select id="apiLevelSelector">
+ <!-- option elements added by buildApiLevelSelector() -->
</select>
</div>
<script>
@@ -85,7 +86,7 @@
if:!last(since) ?>, <?cs /if ?><?cs
/each
?> ];
- buildApiLevelToggle();
+ buildApiLevelSelector();
</script><?cs
/if ?>
</div><!-- headerRight -->
diff --git a/tools/droiddoc/templates-sdk/devdoc-nav.cs b/tools/droiddoc/templates-sdk/devdoc-nav.cs
deleted file mode 100644
index a69c175..0000000
--- a/tools/droiddoc/templates-sdk/devdoc-nav.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-<ul>
- <li><div><a href="<?cs var:toroot ?>index.html">Home</a></div></li>
- <li><div><a href="<?cs var:toroot ?>what-is-android.html">What is Android?</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/index.html">Getting Started</a></div>
- <ul>
- <li><div><a href="<?cs var:toroot ?>intro/installing.html">Installing the SDK</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/upgrading.html">Upgrading the SDK</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/develop-and-debug.html">Developing/Debugging</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/hello-android.html">Hello Android</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/anatomy.html">Anatomy of an App</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/tutorial.html">Notepad Tutorial</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/tools.html">Development Tools</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/appmodel.html">Application Model</a></div></li>
- <li><div><a href="<?cs var:toroot ?>intro/lifecycle.html">Application Life Cycle</a></div></li>
- </ul>
- </li>
- <li><div><div><a href="<?cs var:toroot ?>devel/index.html">Developing Applications</a></div>
- <ul>
- <li><div><a href="<?cs var:toroot ?>devel/implementing-ui.html">Implementing a UI</a></div></li>
- <li><div><a href="<?cs var:toroot ?>devel/building-blocks.html">Building Blocks</a></div></li>
- <li><div><a href="<?cs var:toroot ?>devel/data.html">Data Storage and Retrieval</a></div></li>
- <li><div><a href="<?cs var:toroot ?>devel/security.html">Security Model</a></div></li>
- <li><div><a href="<?cs var:toroot ?>devel/resources-i18n.html">Resources and i18n</a></div></li>
- </ul>
- </li>
- <li><div><a href="<?cs var:toroot ?>toolbox/index.html">Developer Toolbox</a></div>
- <ul>
- <li><div><a href="<?cs var:toroot ?>toolbox/philosophy.html">Design Philosophy</a></div></li>
- <li><div><a href="<?cs var:toroot ?>toolbox/custom-components.html">Building Custom Components</a></div></li>
- <li><div><a href="<?cs var:toroot ?>toolbox/optional-apis.html">Optional APIs</a></div></li>
- </ul>
- </li>
- <li><div><a href="<?cs var:toroot ?>samples/index.html">Sample Code</a></div>
- <ul>
- <li><div><a href="<?cs var:toroot ?>samples/ApiDemos/index.html">API Demos</a></div></li>
- <li><div><a href="<?cs var:toroot ?>samples/LunarLander/index.html">Lunar Lander</a></div></li>
- <li><div><a href="<?cs var:toroot ?>samples/NotePad/index.html">Note Pad</a></div></li>
- </ul>
- </li>
- <li> <a href="<?cs var:toroot ?>reference/index.html"><strong>Reference Information</strong></a>
- <ul>
- <li><a href="<?cs var:toroot ?>reference/packages.html">Package Index</a></li>
- <li><a href="<?cs var:toroot ?>reference/classes.html">Class Index</a></li>
- <li><a href="<?cs var:toroot ?>reference/hierarchy.html">Class Hierarchy</a></li>
- <li><a href="<?cs var:toroot ?>reference/view-gallery.html">List of Views</a></li>
- <li><a href="<?cs var:toroot ?>reference/available-intents.html">List of Intents</a></li>
- <li><a href="<?cs var:toroot ?>reference/android/Manifest.permission.html">List of Permissions</a></li>
- <li><a href="<?cs var:toroot ?>reference/available-resources.html">List of Resource Types</a></li>
- <li><a href="<?cs var:toroot ?>reference/aidl.html">Android IDL</a></li>
- <li><a href="<?cs var:toroot ?>reference/glossary.html">Glossary</a></li>
- <li><a href="<?cs var:toroot ?>reference/keywords.html">Index</a></li>
- </ul>
- </li>
- <li><div><a href="<?cs var:toroot ?>kb/index.html">FAQs</a></div>
- <ul>
- <li><div><a href="<?cs var:toroot ?>kb/general.html">General</a></div></li>
- <li><div><a href="<?cs var:toroot ?>kb/commontasks.html">Common Tasks</a></div></li>
- <li><div><a href="<?cs var:toroot ?>kb/troubleshooting.html">Troubleshooting</a></div></li>
- <li><div><a href="<?cs var:toroot ?>kb/licensingandoss.html">Open Source Licensing</a></div></li>
- <li><div><a href="<?cs var:toroot ?>kb/framework.html">Application Framework</a></div></li>
- <li><div><a href="<?cs var:toroot ?>kb/security.html">Security</a></div></li>
- </ul>
- </li>
- <li><div><a href="<?cs var:toroot ?>roadmap.html">Roadmap</a></div></li>
- <li><div><a href="<?cs var:toroot ?>goodies/index.html">Goodies</a></div></li>
-</ul>
\ No newline at end of file
diff --git a/tools/droiddoc/templates-sdk/header_tabs.cs b/tools/droiddoc/templates-sdk/header_tabs.cs
index 2a897cb..97d9048 100644
--- a/tools/droiddoc/templates-sdk/header_tabs.cs
+++ b/tools/droiddoc/templates-sdk/header_tabs.cs
@@ -5,80 +5,82 @@
elif:home ?>home<?cs
elif:community ?>community<?cs
elif:videos ?>videos<?cs /if ?>">
-
- <li id="home-link"><a href="<?cs var:toroot ?><?cs if:android.whichdoc != "online" ?>offline.html<?cs else ?>index.html<?cs /if ?>">
+
+ <li id="home-link"><a href="<?cs var:toroot ?><?cs
+ if:android.whichdoc != "online" ?>offline.html<?cs
+ else ?>index.html<?cs /if ?>">
<?cs if:!sdk.redirect ?>
<span class="en">Home</span>
- <span class="de">Startseite</span>
- <span class="es"></span>
- <span class="fr"></span>
- <span class="it"></span>
- <span class="ja">ホーム</span>
- <span class="zh-CN">主页</span>
- <span class="zh-TW">首頁</span>
+ <span style="display:none" class="de">Startseite</span>
+ <span style="display:none" class="es"></span>
+ <span style="display:none" class="fr"></span>
+ <span style="display:none" class="it"></span>
+ <span style="display:none" class="ja">ホーム</span>
+ <span style="display:none" class="zh-CN">主页</span>
+ <span style="display:none" class="zh-TW">首頁</span>
<?cs /if ?>
</a></li>
- <li id="sdk-link"><a href="<?cs var:toroot ?>sdk/<?cs var:sdk.current ?>/index.html">
+ <li id="sdk-link"><a href="<?cs var:toroot ?>sdk/index.html">
<span class="en">SDK</span>
</a></li>
<li id="guide-link"><a href="<?cs var:toroot ?>guide/index.html" onClick="return loadLast('guide')">
<?cs if:!sdk.redirect ?>
<span class="en">Dev Guide</span>
- <span class="de">Handbuch</span>
- <span class="es">Guía</span>
- <span class="fr">Guide</span>
- <span class="it">Guida</span>
- <span class="ja">開発ガイド</span>
- <span class="zh-CN">开发人员指南</span>
- <span class="zh-TW">開發指南</span>
+ <span style="display:none" class="de">Handbuch</span>
+ <span style="display:none" class="es">Guía</span>
+ <span style="display:none" class="fr">Guide</span>
+ <span style="display:none" class="it">Guida</span>
+ <span style="display:none" class="ja">開発ガイド</span>
+ <span style="display:none" class="zh-CN">开发人员指南</span>
+ <span style="display:none" class="zh-TW">開發指南</span>
<?cs /if ?>
</a></li>
<li id="reference-link"><a href="<?cs var:toroot ?>reference/packages.html" onClick="return loadLast('reference')">
<?cs if:!sdk.redirect ?>
<span class="en">Reference</span>
- <span class="de">Referenz</span>
- <span class="es">Referencia</span>
- <span class="fr">Référence</span>
- <span class="it">Riferimento</span>
- <span class="ja">リファレンス</span>
- <span class="zh-CN">参考</span>
- <span class="zh-TW">參考資料</span>
+ <span style="display:none" class="de">Referenz</span>
+ <span style="display:none" class="es">Referencia</span>
+ <span style="display:none" class="fr">Référence</span>
+ <span style="display:none" class="it">Riferimento</span>
+ <span style="display:none" class="ja">リファレンス</span>
+ <span style="display:none" class="zh-CN">参考</span>
+ <span style="display:none" class="zh-TW">參考資料</span>
<?cs /if ?>
</a></li>
<li><a href="http://android-developers.blogspot.com" onClick="return requestAppendHL(this.href)">
<?cs if:!sdk.redirect ?>
<span class="en">Blog</span>
- <span class="de"></span>
- <span class="es"></span>
- <span class="fr"></span>
- <span class="it"></span>
- <span class="ja">ブログ</span>
- <span class="zh-CN">博客</span>
- <span class="zh-TW">網誌</span>
+ <span style="display:none" class="de"></span>
+ <span style="display:none" class="es"></span>
+ <span style="display:none" class="fr"></span>
+ <span style="display:none" class="it"></span>
+ <span style="display:none" class="ja">ブログ</span>
+ <span style="display:none" class="zh-CN">博客</span>
+ <span style="display:none" class="zh-TW">網誌</span>
<?cs /if ?>
</a></li>
<li id="videos-link"><a href="<?cs var:toroot ?>videos/index.html" onClick="return loadLast('videos')">
<?cs if:!sdk.redirect ?>
<span class="en">Videos</span>
- <span class="de"></span>
- <span class="es"></span>
- <span class="fr"></span>
- <span class="it"></span>
- <span class="ja">ビデオ</span>
- <span class="zh-CN"></span>
- <span class="zh-TW"></span>
+ <span style="display:none" class="de"></span>
+ <span style="display:none" class="es"></span>
+ <span style="display:none" class="fr"></span>
+ <span style="display:none" class="it"></span>
+ <span style="display:none" class="ja">ビデオ</span>
+ <span style="display:none" class="zh-CN"></span>
+ <span style="display:none" class="zh-TW"></span>
<?cs /if ?>
</a></li>
<li id="community-link"><a href="<?cs var:toroot ?>community/index.html">
<?cs if:!sdk.redirect ?>
<span class="en">Community</span>
- <span class="de"></span>
- <span class="es">Comunidad</span>
- <span class="fr">Communauté</span>
- <span class="it"></span>
- <span class="ja">コミュニティ</span>
- <span class="zh-CN">社区</span>
- <span class="zh-TW">社群</span>
+ <span style="display:none" class="de"></span>
+ <span style="display:none" class="es">Comunidad</span>
+ <span style="display:none" class="fr">Communauté</span>
+ <span style="display:none" class="it"></span>
+ <span style="display:none" class="ja">コミュニティ</span>
+ <span style="display:none" class="zh-CN">社区</span>
+ <span style="display:none" class="zh-TW">社群</span>
<?cs /if ?>
</a></li>
diff --git a/tools/droiddoc/templates-sdk/sdkpage.cs b/tools/droiddoc/templates-sdk/sdkpage.cs
index fdc1df5..ebaee07 100644
--- a/tools/droiddoc/templates-sdk/sdkpage.cs
+++ b/tools/droiddoc/templates-sdk/sdkpage.cs
@@ -33,28 +33,29 @@
<div class="g-unit" id="doc-content" >
<div id="jd-header" class="guide-header" >
<span class="crumb"> </span>
- <h1><?cs if:android.whichdoc == "online" ?>Download <?cs /if ?><?cs var:page.title ?></h1>
+ <h1><?cs if:android.whichdoc == "online" ?>Download the <?cs /if ?><?cs var:page.title ?></h1>
</div>
<div id="jd-content">
- <p><em><?cs
- if:ndk ?><?cs
- var:ndk.date ?><?cs
+ <?cs
+ if:ndk ?><p><em><?cs
+ var:ndk.date ?></em></p><?cs
else ?><?cs
- var:sdk.date ?><?cs
- /if ?></em>
- </p>
+ if:android.whichdoc == "online" ?><p><em><?cs
+ var:sdk.date ?></em></p><?cs
+ /if ?><?cs
+ /if ?>
<?cs if:sdk.not_latest_version ?>
<div class="special">
<p><strong>This is NOT the current Android SDK release.</strong></p>
- <p><a href="/sdk/<?cs var:sdk.current ?>/index.html">Download the current Android SDK</a></p>
+ <p><a href="/sdk/index.html">Download the current Android SDK</a></p>
</div>
<?cs /if ?>
<?cs if:android.whichdoc != "online" && !android.preview ?>
-<p>The sections below provide an overview of the SDK package. </p>
+<!-- <p>The sections below provide an overview of how to install the SDK package. </p> -->
<?cs else ?>
<?cs if:ndk ?>
@@ -62,10 +63,10 @@
<p>The Android NDK is a companion tool to the Android SDK that lets Android
application developers build performance-critical portions of their apps in
native code. It is designed for use <em>only</em> in conjunction with the
-Android SDK, so if you have not already installed the Android 1.5 SDK, please do
-so before downloading the NDK. Also, please read <a href="#overview">What is the
-Android NDK?</a> to get an understanding of what the NDK offers and whether it
-will be useful to you.</p>
+Android SDK, so if you have not already installed the latest Android SDK, please
+do so before downloading the NDK. Also, please read <a href="#overview">What is
+the Android NDK?</a> to get an understanding of what the NDK offers and whether
+it will be useful to you.</p>
<p>Select the download package that is appropriate for your development
computer. </p>
@@ -110,16 +111,49 @@
Android 1.6 and we are pleased to announce the availability of an early look
SDK to give you a head-start on developing applications for it. </p>
- <p>The Android 1.6 platform includes a variety of improvements and new
- features for users and developers. Additionally, the SDK itself introduces
- several new capabilities that enable you to develop applications more
- efficiently. See the <a href="features.html">Android 1.6 Highlights</a>
- document for a list of highlights.</p>
- <?cs /if ?>
+ <p>The Android <?cs var:sdk.preview.version ?> platform includes a variety of
+ improvements and new features for users and developers. Additionally, the SDK
+ itself introduces several new capabilities that enable you to develop
+ applications more efficiently. See the <a href="features.html">Android <?cs
+ var:sdk.preview.version ?> Platform Highlights</a> document for a list of
+ highlights.</p>
+<?cs /if ?>
+<?cs # end if NDK ... the following is for the SDK ?>
-<p>Before downloading, please read the <a href="requirements.html">
-System Requirements</a> document. As you start the download, you will also need to review and agree to
-the Terms and Conditions that govern the use of the Android SDK. </p>
+ <div class="toggle-content special">
+ <p>The Android SDK has changed! If you've worked with the Android SDK before,
+ you will notice several important differences:</p>
+
+ <div class="toggle-content-toggleme" style="display:none">
+ <ul style="padding-bottom:.0;">
+ <li style="margin-top:.5em">The SDK downloadable package includes <em>only</em>
+ the latest version of the Android SDK Tools.</li>
+ <li>Once you've installed the SDK, you now use the Android SDK and AVD Manager
+ to download all of the SDK components that you need, such as Android platforms,
+ SDK add-ons, tools, and documentation. </li>
+ <li>The new approach is modular — you can install only the components you
+ need and update any or all components without affecting other parts of your
+ development environment.</li>
+ <li>In short, once you've installed the new SDK, you will not need to download
+ an SDK package again. Instead, you will use the Android SDK and AVD Manager to
+ keep your development environment up-to-date. </li>
+ </ul>
+ <p style="margin-top:0">If you are currently using the Android 1.6 SDK, you
+ do not need to install the new SDK, because your existing SDK already
+ includes the Android SDK and AVD Manager tool. To develop against Android
+ 2.0, for example, you can just download the Android 2.0 platform (and
+ updated SDK Tools) into your existing SDK. Refer to <a
+ href="adding-components.html">Adding SDK Components</a>.</p>
+ </div>
+
+ <a href='#' class='toggle-content-button show' onclick="toggleContent(this);return false;">
+ <span>show more</span><span style='display:none'>show less</span>
+ </a>
+ </div>
+
+ <p>If you are new to the Android SDK, please read the <a href="#quickstart">Quick Start</a>,
+ below, for an overview of how to install and set up the SDK.</p>
+
<table class="download">
<tr>
@@ -156,7 +190,7 @@
<tr class="alt-color">
<td>ADT Plugin for Eclipse <?cs var:adt.zip_version ?></td>
<td>
- <a href="<?cs var:toroot ?>sdk/download.html?v=<?cs var:adt.zip_download ?>"><?cs var:adt.zip_download ?></a>
+ <a href="http://dl.google.com/android/<?cs var:adt.zip_download ?>"><?cs var:adt.zip_download ?></a>
</td>
<td><?cs var:adt.zip_bytes ?> bytes</td>
<td><?cs var:adt.zip_checksum ?></td>
@@ -170,14 +204,17 @@
<?cs if:android.whichdoc != "online" && sdk.preview ?>
<p>Welcome developers! The next release of the Android platform will be
- Android 1.6 and we are pleased to announce the availability of an early look SDK
- to give you a head-start on developing applications for it. </p>
+Android <?cs var:sdk.preview.version ?> and we are pleased to announce the
+availability of an early look SDK to give you a head-start on developing
+applications for it. </p>
- <p>The Android 1.6 platform includes a variety of improvements and new features
- for users and developers. Additionally, the SDK itself introduces several new
- capabilities that enable you to develop applications more efficiently.
- See the <a href="http://developer.android.com/sdk/preview/features.html">
- Android 1.6 Highlights</a> document for a list of highlights.</p>
+ <p>The Android <?cs var:sdk.preview.version ?> platform includes a variety of
+improvements and new features for users and developers. Additionally, the SDK
+itself introduces several new capabilities that enable you to develop
+applications more efficiently. See the <a
+href="http://developer.android.com/sdk/preview/features.html">Android
+<?cs var:sdk.preview.version ?> Highlights</a> document for a list of
+highlights.</p>
<?cs /if ?>
<?cs call:tag_list(root.descr) ?>
diff --git a/tools/droiddoc/templates/assets/android-developer-core.css b/tools/droiddoc/templates/assets/android-developer-core.css
index acb873d..2b30c05 100644
--- a/tools/droiddoc/templates/assets/android-developer-core.css
+++ b/tools/droiddoc/templates/assets/android-developer-core.css
@@ -48,11 +48,12 @@
}
input, select,
-textarea, option {
+textarea, option, label {
font-family:inherit;
font-size:inherit;
padding:0;
margin:0;
+ vertical-align:middle;
}
option {
@@ -81,6 +82,7 @@
padding:10px;
margin:0 0 1em 1em;
overflow:auto;
+ line-height:inherit; /* fixes vertical scrolling in webkit */
}
h1,h2,h3,h4,h5 {
@@ -194,7 +196,7 @@
height: 114px;
position:relative;
z-index:100;
- min-width:576px;
+ min-width:675px; /* min width for the tabs, before they wrap */
padding:0 10px;
border-bottom:3px solid #94b922;
}
diff --git a/tools/droiddoc/templates/assets/android-developer-docs.css b/tools/droiddoc/templates/assets/android-developer-docs.css
index d72098f..c52222c 100644
--- a/tools/droiddoc/templates/assets/android-developer-docs.css
+++ b/tools/droiddoc/templates/assets/android-developer-docs.css
@@ -339,9 +339,12 @@
}
#api-level-toggle {
- float:right;
padding:0 10px;
font-size:11px;
+ float:right;
+}
+
+#api-level-toggle label.disabled {
color:#999;
}
@@ -660,7 +663,27 @@
div.special {
padding: .5em 1em 1em 1em;
margin: 0 0 1em;
- background-color: #ddf0f2;
+ background-color: #DAF3FC;
+ border:1px solid #d3ecf5;
+ border-radius:5px;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+}
+
+.toggle-content-toggleme {
+ display:none;
+}
+
+.toggle-content-button {
+ font-size:.9em;
+ line-height:.9em;
+ text-decoration:none;
+ position:relative;
+ top:5px;
+}
+
+.toggle-content-button:hover {
+ text-decoration:underline;
}
div.special p {
@@ -751,6 +774,8 @@
font-weight: bold;
color: red;
text-decoration: none;
+ vertical-align:top;
+ line-height:.9em;
}
pre.classic {
diff --git a/tools/droiddoc/templates/assets/android-developer-docs.js b/tools/droiddoc/templates/assets/android-developer-docs.js
index b16ed0d..6431163 100644
--- a/tools/droiddoc/templates/assets/android-developer-docs.js
+++ b/tools/droiddoc/templates/assets/android-developer-docs.js
@@ -10,6 +10,7 @@
var nav_pref;
var toRoot;
var isMobile = false; // true if mobile, so we can adjust some layout
+var isIE6 = false; // true if IE6
function addLoadEvent(newfun) {
var current = window.onload;
@@ -24,17 +25,24 @@
}
var agent = navigator['userAgent'];
+// If a mobile phone, set flag and do mobile setup
if ((agent.indexOf("Mobile") != -1) ||
(agent.indexOf("BlackBerry") != -1) ||
(agent.indexOf("Mini") != -1)) {
isMobile = true;
addLoadEvent(mobileSetup);
+// If not a mobile browser, set the onresize event for IE6, and others
+} else if (agent.indexOf("MSIE 6.0") != -1) {
+ isIE6 = true;
+ addLoadEvent(function() {
+ window.onresize = resizeAll;
+ });
+} else {
+ addLoadEvent(function() {
+ window.onresize = resizeHeight;
+ });
}
-addLoadEvent(function() {
-window.onresize = resizeAll;
-});
-
function mobileSetup() {
$("body").css({'overflow':'auto'});
$("html").css({'overflow':'auto'});
@@ -50,7 +58,7 @@
var lists = document.createElement("script");
lists.setAttribute("type","text/javascript");
lists.setAttribute("src", toRoot+"reference/lists.js");
- $("head").append($(lists));
+ document.getElementsByTagName("head")[0].appendChild(lists);
} );
function setToRoot(root) {
@@ -60,8 +68,12 @@
function restoreWidth(navWidth) {
var windowWidth = $(window).width() + "px";
- content.css({marginLeft:parseInt(navWidth) + 6 + "px", //account for 6px-wide handle-bar
- width:parseInt(windowWidth) - parseInt(navWidth) - 6 + "px"});
+ content.css({marginLeft:parseInt(navWidth) + 6 + "px"}); //account for 6px-wide handle-bar
+
+ if (isIE6) {
+ content.css({width:parseInt(windowWidth) - parseInt(navWidth) - 6 + "px"}); // necessary in order for scrollbars to be visible
+ }
+
sidenav.css({width:navWidth});
resizePackagesNav.css({width:navWidth});
classesNav.css({width:navWidth});
@@ -99,14 +111,14 @@
}
function writeCookie(cookie, val, section, expiration) {
- if (!val) return;
+ if (val==undefined) return;
section = section == null ? "_" : "_"+section+"_";
if (expiration == null) {
var date = new Date();
date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
expiration = date.toGMTString();
}
- document.cookie = cookie_namespace+section+cookie+"="+val+"; expires="+expiration+"; path=/";
+ document.cookie = cookie_namespace + section + cookie + "=" + val + "; expires=" + expiration+"; path=/";
}
function init() {
@@ -124,7 +136,7 @@
}
if (!isMobile) {
- $("#resize-packages-nav").resizable({handles: "s", resize: function(e, ui) { resizeHeight(); } });
+ $("#resize-packages-nav").resizable({handles: "s", resize: function(e, ui) { resizePackagesHeight(); } });
$(".side-nav-resizable").resizable({handles: "e", resize: function(e, ui) { resizeWidth(); } });
var cookieWidth = readCookie(cookiePath+'width');
var cookieHeight = readCookie(cookiePath+'height');
@@ -156,8 +168,8 @@
var htmlPos = fullPageName.lastIndexOf(".html", fullPageName.length);
var pathPageName = fullPageName.slice(firstSlashPos, htmlPos + 5);
var link = $("#devdoc-nav a[href$='"+ pathPageName+"']");
- if ((link.length == 0) && ((fullPageName.indexOf("/guide/") != -1) || (fullPageName.indexOf("/sdk/") != -1))) {
-// if there's no match, then let's backstep through the directory until we find an index.html page that matches our ancestor directories (only for dev guide and sdk)
+ if ((link.length == 0) && (fullPageName.indexOf("/guide/") != -1)) {
+// if there's no match, then let's backstep through the directory until we find an index.html page that matches our ancestor directories (only for dev guide)
lastBackstep = pathPageName.lastIndexOf("/");
while (link.length == 0) {
backstepDirectory = pathPageName.lastIndexOf("/", lastBackstep);
@@ -174,23 +186,46 @@
}
}
-function resizeHeight() {
+/* Resize the height of the nav panels in the reference,
+ * and save the new size to a cookie */
+function resizePackagesHeight() {
var windowHeight = ($(window).height() - HEADER_HEIGHT);
- var swapperHeight = windowHeight - 13;
- $("#swapper").css({height:swapperHeight + "px"});
- sidenav.css({height:windowHeight + "px"});
- content.css({height:windowHeight + "px"});
+ var swapperHeight = windowHeight - 13; // move 13px for swapper link at the bottom
resizePackagesNav.css({maxHeight:swapperHeight + "px"});
classesNav.css({height:swapperHeight - parseInt(resizePackagesNav.css("height")) + "px"});
+
+ $("#swapper").css({height:swapperHeight + "px"});
$("#packages-nav").css({height:parseInt(resizePackagesNav.css("height")) - 6 + "px"}); //move 6px for handle
- devdocNav.css({height:sidenav.css("height")});
- $("#nav-tree").css({height:swapperHeight + "px"});
-
+
var basePath = getBaseUri(location.pathname);
var section = basePath.substring(1,basePath.indexOf("/",1));
writeCookie("height", resizePackagesNav.css("height"), section, null);
}
+/* Resize the height of the side-nav and doc-content divs,
+ * which creates the frame effect */
+function resizeHeight() {
+ // Get the window height and always resize the doc-content and side-nav divs
+ var windowHeight = ($(window).height() - HEADER_HEIGHT);
+ content.css({height:windowHeight + "px"});
+ sidenav.css({height:windowHeight + "px"});
+
+ var href = location.href;
+ // If in the reference docs, also resize the "swapper", "classes-nav", and "nav-tree" divs
+ if (href.indexOf("/reference/") != -1) {
+ var swapperHeight = windowHeight - 13;
+ $("#swapper").css({height:swapperHeight + "px"});
+ $("#classes-nav").css({height:swapperHeight - parseInt(resizePackagesNav.css("height")) + "px"});
+ $("#nav-tree").css({height:swapperHeight + "px"});
+
+ // If in the dev guide docs, also resize the "devdoc-nav" div
+ } else if (href.indexOf("/guide/") != -1) {
+ $("#devdoc-nav").css({height:sidenav.css("height")});
+ }
+}
+
+/* Resize the width of the "side-nav" and the left margin of the "doc-content" div,
+ * which creates the resizable side bar */
function resizeWidth() {
var windowWidth = $(window).width() + "px";
if (sidenav.length) {
@@ -198,24 +233,27 @@
} else {
var sidenavWidth = 0;
}
- content.css({marginLeft:parseInt(sidenavWidth) + 6 + "px", //account for 6px-wide handle-bar
- width:parseInt(windowWidth) - parseInt(sidenavWidth) - 6 + "px"});
+ content.css({marginLeft:parseInt(sidenavWidth) + 6 + "px"}); //account for 6px-wide handle-bar
+
+ if (isIE6) {
+ content.css({width:parseInt(windowWidth) - parseInt(sidenavWidth) - 6 + "px"}); // necessary in order to for scrollbars to be visible
+ }
+
resizePackagesNav.css({width:sidenavWidth});
classesNav.css({width:sidenavWidth});
$("#packages-nav").css({width:sidenavWidth});
-
+
var basePath = getBaseUri(location.pathname);
var section = basePath.substring(1,basePath.indexOf("/",1));
writeCookie("width", sidenavWidth, section, null);
}
+/* For IE6 only,
+ * because it can't properly perform auto width for "doc-content" div,
+ * avoiding this for all browsers provides better performance */
function resizeAll() {
- if (!isMobile) {
- resizeHeight();
- if ($(".side-nav-resizable").length) {
- resizeWidth();
- }
- }
+ resizeHeight();
+ resizeWidth();
}
function getBaseUri(uri) {
@@ -448,3 +486,18 @@
}
return (lang != 0) ? lang : 'en';
}
+
+
+function toggleContent(obj) {
+ var button = $(obj);
+ var div = $(obj.parentNode);
+ var toggleMe = $(".toggle-content-toggleme",div);
+ if (button.hasClass("show")) {
+ toggleMe.slideDown();
+ button.removeClass("show").addClass("hide");
+ } else {
+ toggleMe.slideUp();
+ button.removeClass("hide").addClass("show");
+ }
+ $("span", button).toggle();
+}
diff --git a/tools/droiddoc/templates/assets/android-developer-reference.js b/tools/droiddoc/templates/assets/android-developer-reference.js
index 3080760..6299596 100644
--- a/tools/droiddoc/templates/assets/android-developer-reference.js
+++ b/tools/droiddoc/templates/assets/android-developer-reference.js
@@ -1,48 +1,78 @@
/* API LEVEL TOGGLE */
addLoadEvent(changeApiLevel);
+
+var API_LEVEL_ENABLED_COOKIE = "api_level_enabled";
var API_LEVEL_COOKIE = "api_level";
var minLevel = 1;
-function buildApiLevelToggle() {
- var maxLevel = SINCE_DATA.length;
- var userApiLevel = readCookie(API_LEVEL_COOKIE);
-
- if (userApiLevel != 0) {
- selectedLevel = userApiLevel;
- } else {
- selectedLevel = maxLevel;
- }
+function toggleApiLevelSelector(checkbox) {
+ var date = new Date();
+ date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
+ var expiration = date.toGMTString();
+ if (checkbox.checked) {
+ $("#apiLevelSelector").removeAttr("disabled");
+ $("#api-level-toggle label").removeClass("disabled");
+ writeCookie(API_LEVEL_ENABLED_COOKIE, 1, null, expiration);
+ } else {
+ $("#apiLevelSelector").attr("disabled","disabled");
+ $("#api-level-toggle label").addClass("disabled");
+ writeCookie(API_LEVEL_ENABLED_COOKIE, 0, null, expiration);
+ }
+ changeApiLevel();
+}
+
+function buildApiLevelSelector() {
+ var maxLevel = SINCE_DATA.length;
+ var userApiLevelEnabled = readCookie(API_LEVEL_ENABLED_COOKIE);
+ var userApiLevel = readCookie(API_LEVEL_COOKIE);
+ userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
+
+ if (userApiLevelEnabled == 0) {
+ $("#apiLevelSelector").attr("disabled","disabled");
+ } else {
+ $("#apiLevelCheckbox").attr("checked","checked");
+ $("#api-level-toggle label").removeClass("disabled");
+ }
minLevel = $("body").attr("class");
- var select = $("#apiLevelControl").html("").change(changeApiLevel);
- for (var i = maxLevel-1; i >= 0; i--) {
- var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
-// if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
- select.append(option);
- }
+ var select = $("#apiLevelSelector").html("").change(changeApiLevel);
+ for (var i = maxLevel-1; i >= 0; i--) {
+ var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
+ // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
+ select.append(option);
+ }
// get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
- var selectedLevelItem = $("#apiLevelControl option[value='"+selectedLevel+"']").get(0);
+ var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
selectedLevelItem.setAttribute('selected',true);
}
function changeApiLevel() {
- var selectedLevel = $("#apiLevelControl option:selected").val();
- toggleVisisbleApis(selectedLevel, "body");
+ var maxLevel = SINCE_DATA.length;
+ var userApiLevelEnabled = readCookie(API_LEVEL_ENABLED_COOKIE);
+ var selectedLevel = maxLevel;
+
+ if (userApiLevelEnabled == 0) {
+ toggleVisisbleApis(selectedLevel, "body");
+ } else {
+ selectedLevel = $("#apiLevelSelector option:selected").val();
+ toggleVisisbleApis(selectedLevel, "body");
+
+ var date = new Date();
+ date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
+ var expiration = date.toGMTString();
+ writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
+ }
- var date = new Date();
- date.setTime(date.getTime()+(50*365*24*60*60*1000)); // keep this for 50 years
- writeCookie(API_LEVEL_COOKIE, selectedLevel, null, date);
-
- if (selectedLevel < minLevel) {
- var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
- $("#naMessage").show().html("<div><p><strong>This " + thing + " is not available with API Level " + selectedLevel + ".</strong></p>"
+ if (selectedLevel < minLevel) {
+ var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
+ $("#naMessage").show().html("<div><p><strong>This " + thing + " is not available with API Level " + selectedLevel + ".</strong></p>"
+ "<p>To use this " + thing + ", your application must specify API Level " + minLevel + " or higher in its manifest "
+ "and be compiled against a version of the Android library that supports an equal or higher API Level. To reveal this "
+ "document, change the value of the API Level filter above.</p>"
+ "<p><a href='" +toRoot+ "guide/appendix/api-levels.html'>What is the API Level?</a></p></div>");
- } else {
+ } else {
$("#naMessage").hide();
}
}
@@ -156,7 +186,7 @@
node.expanded = true;
// perform api level toggling because new nodes are new to the DOM
- var selectedLevel = $("#apiLevelControl option:selected").val();
+ var selectedLevel = $("#apiLevelSelector option:selected").val();
toggleVisisbleApis(selectedLevel, "#side-nav");
}
}
@@ -228,7 +258,7 @@
init_navtree("nav-tree", toroot, NAVTREE_DATA);
// perform api level toggling because because the whole tree is new to the DOM
- var selectedLevel = $("#apiLevelControl option:selected").val();
+ var selectedLevel = $("#apiLevelSelector option:selected").val();
toggleVisisbleApis(selectedLevel, "#side-nav");
}
diff --git a/tools/droiddoc/templates/assets/images/home/donut-android.png b/tools/droiddoc/templates/assets/images/home/donut-android.png
old mode 100644
new mode 100755
index 85bc952..6aba06b
--- a/tools/droiddoc/templates/assets/images/home/donut-android.png
+++ b/tools/droiddoc/templates/assets/images/home/donut-android.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/home/eclair-android.png b/tools/droiddoc/templates/assets/images/home/eclair-android.png
new file mode 100644
index 0000000..d476ce9
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/home/eclair-android.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/search_autocomplete.js b/tools/droiddoc/templates/assets/search_autocomplete.js
index 929751f..086674a 100644
--- a/tools/droiddoc/templates/assets/search_autocomplete.js
+++ b/tools/droiddoc/templates/assets/search_autocomplete.js
@@ -168,6 +168,6 @@
function submit_search() {
var query = document.getElementById('search_autocomplete').value;
- document.location = toRoot + 'search.html#q=' + query; // toRoot is initialized in android-developer-docs.js
+ document.location = toRoot + 'search.html#q=' + query + '&t=0';
return false;
}
diff --git a/tools/droiddoc/templates/class.cs b/tools/droiddoc/templates/class.cs
index 9bc2ba0..89eb927 100644
--- a/tools/droiddoc/templates/class.cs
+++ b/tools/droiddoc/templates/class.cs
@@ -188,7 +188,7 @@
<?cs # summary macros ?>
-<?cs def:write_method_summary(methods) ?>
+<?cs def:write_method_summary(methods, included) ?>
<?cs set:count = #1 ?>
<?cs each:method = methods ?>
<?cs # The apilevel-N class MUST BE LAST in the sequence of class names ?>
@@ -202,8 +202,7 @@
<?cs call:type_link(method.returnType) ?></nobr>
</td>
<td class="jd-linkcol" width="100%"><nobr>
- <span class="sympad"><a href="<?cs var:toroot ?><?cs var:method.href ?>">
- <?cs var:method.name ?></a></span>(<?cs call:parameter_list(method.params) ?>)</nobr>
+ <span class="sympad"><?cs call:cond_link(method.name, toroot, method.href, included) ?></span>(<?cs call:parameter_list(method.params) ?>)</nobr>
<?cs if:subcount(method.shortDescr) || subcount(method.deprecated) ?>
<div class="jd-descrdiv"><?cs call:short_descr(method) ?></div>
<?cs /if ?>
@@ -212,7 +211,7 @@
<?cs /each ?>
<?cs /def ?>
-<?cs def:write_field_summary(fields) ?>
+<?cs def:write_field_summary(fields, included) ?>
<?cs set:count = #1 ?>
<?cs each:field=fields ?>
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:field.since ?>" >
@@ -221,26 +220,26 @@
<?cs var:field.static ?>
<?cs var:field.final ?>
<?cs call:type_link(field.type) ?></nobr></td>
- <td class="jd-linkcol"><a href="<?cs var:toroot ?><?cs var:field.href ?>"><?cs var:field.name ?></a></td>
+ <td class="jd-linkcol"><?cs call:cond_link(field.name, toroot, field.href, included) ?></td>
<td class="jd-descrcol" width="100%"><?cs call:short_descr(field) ?></td>
</tr>
<?cs set:count = count + #1 ?>
<?cs /each ?>
<?cs /def ?>
-<?cs def:write_constant_summary(fields) ?>
+<?cs def:write_constant_summary(fields, included) ?>
<?cs set:count = #1 ?>
<?cs each:field=fields ?>
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:field.since ?>" >
<td class="jd-typecol"><?cs call:type_link(field.type) ?></td>
- <td class="jd-linkcol"><a href="<?cs var:toroot ?><?cs var:field.href ?>"><?cs var:field.name ?></a></td>
+ <td class="jd-linkcol"><?cs call:cond_link(field.name, toroot, field.href, included) ?></td>
<td class="jd-descrcol" width="100%"><?cs call:short_descr(field) ?></td>
</tr>
<?cs set:count = count + #1 ?>
<?cs /each ?>
<?cs /def ?>
-<?cs def:write_attr_summary(attrs) ?>
+<?cs def:write_attr_summary(attrs, included) ?>
<?cs set:count = #1 ?>
<tr>
<td><nobr><em>Attribute Name</em></nobr></td>
@@ -249,9 +248,9 @@
</tr>
<?cs each:attr=attrs ?>
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:attr.since ?>" >
- <td class="jd-linkcol"><a href="<?cs var:toroot ?><?cs var:attr.href ?>"><?cs var:attr.name ?></a></td>
+ <td class="jd-linkcol"><?cs if:included ?><a href="<?cs var:toroot ?><?cs var:attr.href ?>"><?cs /if ?><?cs var:attr.name ?><?cs if:included ?></a><?cs /if ?></td>
<td class="jd-linkcol"><?cs each:m=attr.methods ?>
- <a href="<?cs var:toroot ?><?cs var:m.href ?>"><?cs var:m.name ?></a>
+ <?cs call:cond_link(m.name, toroot, m.href, included) ?>
<?cs /each ?>
</td>
<td class="jd-descrcol" width="100%"><?cs call:short_descr(attr) ?> </td>
@@ -293,7 +292,7 @@
<?cs if:subcount(class.attrs) ?>
<!-- =========== FIELD SUMMARY =========== -->
<table id="lattrs" class="jd-sumtable"><tr><th colspan="12">XML Attributes</th></tr>
-<?cs call:write_attr_summary(class.attrs) ?>
+<?cs call:write_attr_summary(class.attrs, 1) ?>
<?cs /if ?>
<?cs # if there are inherited attrs, write the table ?>
@@ -308,14 +307,14 @@
<tr class="api apilevel-<?cs var:cl.since ?>" >
<td colspan="12">
<?cs call:expando_trigger("inherited-attrs-"+cl.qualified, "closed") ?>From <?cs var:cl.kind ?>
-<a href="<?cs var:toroot ?><?cs var:cl.link ?>"><?cs var:cl.qualified ?></a>
+<?cs call:cond_link(cl.qualified, toroot, cl.link, cl.included) ?>
<div id="inherited-attrs-<?cs var:cl.qualified ?>">
<div id="inherited-attrs-<?cs var:cl.qualified ?>-list"
class="jd-inheritedlinks">
</div>
<div id="inherited-attrs-<?cs var:cl.qualified ?>-summary" style="display: none;">
<table class="jd-sumtable-expando">
- <?cs call:write_attr_summary(cl.attrs) ?></table>
+ <?cs call:write_attr_summary(cl.attrs, cl.included) ?></table>
</div>
</div>
</td></tr>
@@ -332,7 +331,7 @@
<?cs each:field=class.enumConstants ?>
<tr class="<?cs if:count % #2 ?>alt-color<?cs /if ?> api apilevel-<?cs var:field.since ?>" >
<td class="jd-descrcol"><?cs call:type_link(field.type) ?> </td>
- <td class="jd-linkcol"><a href="<?cs var:toroot ?><?cs var:field.href ?>"><?cs var:field.name ?></a> </td>
+ <td class="jd-linkcol"><?cs call:cond_link(field.name, toroot, field.href, cl.included) ?> </td>
<td class="jd-descrcol" width="100%"><?cs call:short_descr(field) ?> </td>
</tr>
<?cs set:count = count + #1 ?>
@@ -343,7 +342,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- =========== ENUM CONSTANT SUMMARY =========== -->
<table id="constants" class="jd-sumtable"><tr><th colspan="12">Constants</th></tr>
-<?cs call:write_constant_summary(class.constants) ?>
+<?cs call:write_constant_summary(class.constants, 1) ?>
</table>
<?cs /if ?>
@@ -359,14 +358,14 @@
<tr class="api apilevel-<?cs var:cl.since ?>" >
<td colspan="12">
<?cs call:expando_trigger("inherited-constants-"+cl.qualified, "closed") ?>From <?cs var:cl.kind ?>
-<a href="<?cs var:toroot ?><?cs var:cl.link ?>"><?cs var:cl.qualified ?></a>
+<?cs call:cond_link(cl.qualified, toroot, cl.link, cl.included) ?>
<div id="inherited-constants-<?cs var:cl.qualified ?>">
<div id="inherited-constants-<?cs var:cl.qualified ?>-list"
class="jd-inheritedlinks">
</div>
<div id="inherited-constants-<?cs var:cl.qualified ?>-summary" style="display: none;">
<table class="jd-sumtable-expando">
- <?cs call:write_constant_summary(cl.constants) ?></table>
+ <?cs call:write_constant_summary(cl.constants, cl.included) ?></table>
</div>
</div>
</td></tr>
@@ -379,7 +378,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- =========== FIELD SUMMARY =========== -->
<table id="lfields" class="jd-sumtable"><tr><th colspan="12">Fields</th></tr>
-<?cs call:write_field_summary(class.fields) ?>
+<?cs call:write_field_summary(class.fields, 1) ?>
</table>
<?cs /if ?>
@@ -395,14 +394,14 @@
<tr class="api apilevel-<?cs var:cl.since ?>" >
<td colspan="12">
<?cs call:expando_trigger("inherited-fields-"+cl.qualified, "closed") ?>From <?cs var:cl.kind ?>
-<a href="<?cs var:toroot ?><?cs var:cl.link ?>"><?cs var:cl.qualified ?></a>
+<?cs call:cond_link(cl.qualified, toroot, cl.link, cl.included) ?>
<div id="inherited-fields-<?cs var:cl.qualified ?>">
<div id="inherited-fields-<?cs var:cl.qualified ?>-list"
class="jd-inheritedlinks">
</div>
<div id="inherited-fields-<?cs var:cl.qualified ?>-summary" style="display: none;">
<table class="jd-sumtable-expando">
- <?cs call:write_field_summary(cl.fields) ?></table>
+ <?cs call:write_field_summary(cl.fields, cl.included) ?></table>
</div>
</div>
</td></tr>
@@ -415,7 +414,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- ======== CONSTRUCTOR SUMMARY ======== -->
<table id="pubctors" class="jd-sumtable"><tr><th colspan="12">Public Constructors</th></tr>
-<?cs call:write_method_summary(class.ctors.public) ?>
+<?cs call:write_method_summary(class.ctors.public, 1) ?>
</table>
<?cs /if ?>
@@ -423,7 +422,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- ======== CONSTRUCTOR SUMMARY ======== -->
<table id="proctors" class="jd-sumtable"><tr><th colspan="12">Protected Constructors</th></tr>
-<?cs call:write_method_summary(class.ctors.protected) ?>
+<?cs call:write_method_summary(class.ctors.protected, 1) ?>
</table>
<?cs /if ?>
@@ -431,7 +430,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- ========== METHOD SUMMARY =========== -->
<table id="pubmethods" class="jd-sumtable"><tr><th colspan="12">Public Methods</th></tr>
-<?cs call:write_method_summary(class.methods.public) ?>
+<?cs call:write_method_summary(class.methods.public, 1) ?>
</table>
<?cs /if ?>
@@ -439,7 +438,7 @@
<?cs # this next line must be exactly like this to be parsed by eclipse ?>
<!-- ========== METHOD SUMMARY =========== -->
<table id="promethods" class="jd-sumtable"><tr><th colspan="12">Protected Methods</th></tr>
-<?cs call:write_method_summary(class.methods.protected) ?>
+<?cs call:write_method_summary(class.methods.protected, 1) ?>
</table>
<?cs /if ?>
@@ -454,14 +453,14 @@
<?cs if:subcount(cl.methods) ?>
<tr class="api apilevel-<?cs var:cl.since ?>" >
<td colspan="12"><?cs call:expando_trigger("inherited-methods-"+cl.qualified, "closed") ?>
-From <?cs var:cl.kind ?> <a href="<?cs var:toroot ?><?cs var:cl.link ?>"><?cs var:cl.qualified ?></a>
+From <?cs var:cl.kind ?> <?cs call:cond_link(cl.qualified, toroot, cl.link, cl.included) ?>
<div id="inherited-methods-<?cs var:cl.qualified ?>">
<div id="inherited-methods-<?cs var:cl.qualified ?>-list"
class="jd-inheritedlinks">
</div>
<div id="inherited-methods-<?cs var:cl.qualified ?>-summary" style="display: none;">
<table class="jd-sumtable-expando">
- <?cs call:write_method_summary(cl.methods) ?></table>
+ <?cs call:write_method_summary(cl.methods, cl.included) ?></table>
</div>
</div>
</td></tr>
diff --git a/tools/droiddoc/templates/macros.cs b/tools/droiddoc/templates/macros.cs
index 14dc90e..0c59f32 100644
--- a/tools/droiddoc/templates/macros.cs
+++ b/tools/droiddoc/templates/macros.cs
@@ -39,6 +39,15 @@
<?cs def:class_name(type) ?><?cs call:type_link_impl(type, "false") ?><?cs /def ?>
<?cs def:type_link(type) ?><?cs call:type_link_impl(type, "true") ?><?cs /def ?>
+<?cs # a conditional link.
+ if the "condition" parameter evals to true then the link is displayed
+ otherwise only the text is displayed
+?><?cs
+def:cond_link(text, root, path, condition) ?><?cs
+ if:condition ?><a href="<?cs var:root ?><?cs var:path ?>"><?cs /if ?><?cs var:text ?><?cs if:condition ?></a><?cs /if ?><?cs
+/def ?>
+
+
<?cs # A comma separated parameter list ?><?cs
def:parameter_list(params) ?><?cs
each:param = params ?><?cs
@@ -64,6 +73,15 @@
elif:tag.kind == "@sdkCurrent" ?><?cs var:sdk.current ?><?cs
elif:tag.kind == "@sdkCurrentVersion" ?><?cs var:sdk.version ?><?cs
elif:tag.kind == "@sdkCurrentRelId" ?><?cs var:sdk.rel.id ?><?cs
+ elif:tag.kind == "@sdkPlatformVersion" ?><?cs var:sdk.platform.version ?><?cs
+ elif:tag.kind == "@sdkPlatformApiLevel" ?><?cs var:sdk.platform.apiLevel ?><?cs
+ elif:tag.kind == "@sdkPlatformMajorMinor" ?><?cs var:sdk.platform.majorMinor ?><?cs
+ elif:tag.kind == "@sdkPlatformReleaseDate" ?><?cs var:sdk.platform.releaseDate ?><?cs
+ elif:tag.kind == "@sdkPlatformDeployableDate" ?><?cs var:sdk.platform.deployableDate ?><?cs
+ elif:tag.kind == "@adtZipVersion" ?><?cs var:adt.zip.version ?><?cs
+ elif:tag.kind == "@adtZipDownload" ?><?cs var:adt.zip.download ?><?cs
+ elif:tag.kind == "@adtZipBytes" ?><?cs var:adt.zip.bytes ?><?cs
+ elif:tag.kind == "@adtZipChecksum" ?><?cs var:adt.zip.checksum ?><?cs
elif:tag.kind == "@inheritDoc" ?><?cs # This is the case when @inheritDoc is in something
that doesn't inherit from anything?><?cs
elif:tag.kind == "@attr" ?><?cs
@@ -89,8 +107,8 @@
<?cs # Show the red box with the deprecated warning ?><?cs
def:deprecated_warning(obj) ?><?cs
if:subcount(obj.deprecated) ?><p>
- <p class="warning jd-deprecated-warning">
- <strong><?cs call:deprecated_text(obj.kind) ?></strong><?cs
+ <p class="caution">
+ <strong><?cs call:deprecated_text(obj.kind) ?></strong><br/> <?cs
call:tag_list(obj.deprecated) ?>
</p><?cs
/if ?><?cs
diff --git a/tools/dump-package-stats b/tools/dump-package-stats
index 589bab5..d11e727 100755
--- a/tools/dump-package-stats
+++ b/tools/dump-package-stats
@@ -102,7 +102,7 @@
$1 != "Length" ||
$2 != "Method" ||
$3 != "Size" ||
- $4 != "Ratio" ||
+ ($4 != "Ratio" && $4 != "Cmpr") ||
$5 != "Date" ||
$6 != "Time" ||
$7 != "CRC-32" ||
diff --git a/tools/findleaves.py b/tools/findleaves.py
new file mode 100755
index 0000000..0adf188
--- /dev/null
+++ b/tools/findleaves.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2009 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.
+#
+
+#
+# Finds files with the specified name under a particular directory, stopping
+# the search in a given subdirectory when the file is found.
+#
+
+import os
+import sys
+
+def perform_find(mindepth, prune, dirlist, filename):
+ result = []
+ pruneleaves = set(map(lambda x: os.path.split(x)[1], prune))
+ for rootdir in dirlist:
+ rootdepth = rootdir.count("/")
+ for root, dirs, files in os.walk(rootdir):
+ # prune
+ check_prune = False
+ for d in dirs:
+ if d in pruneleaves:
+ check_prune = True
+ break
+ if check_prune:
+ i = 0
+ while i < len(dirs):
+ if dirs[i] in prune:
+ del dirs[i]
+ else:
+ i += 1
+ # mindepth
+ if mindepth > 0:
+ depth = 1 + root.count("/") - rootdepth
+ if depth < mindepth:
+ continue
+ # match
+ if filename in files:
+ result.append(os.path.join(root, filename))
+ del dirs[:]
+ return result
+
+def usage():
+ sys.stderr.write("""Usage: %(progName)s [<options>] <dirlist> <filename>
+Options:
+ --mindepth=<mindepth>
+ Both behave in the same way as their find(1) equivalents.
+ --prune=<dirname>
+ Avoids returning results from inside any directory called <dirname>
+ (e.g., "*/out/*"). May be used multiple times.
+""" % {
+ "progName": os.path.split(sys.argv[0])[1],
+ })
+ sys.exit(1)
+
+def main(argv):
+ mindepth = -1
+ prune = []
+ i=1
+ while i<len(argv) and len(argv[i])>2 and argv[i][0:2] == "--":
+ arg = argv[i]
+ if arg.startswith("--mindepth="):
+ try:
+ mindepth = int(arg[len("--mindepth="):])
+ except ValueError:
+ usage()
+ elif arg.startswith("--prune="):
+ p = arg[len("--prune="):]
+ if len(p) == 0:
+ usage()
+ prune.append(p)
+ else:
+ usage()
+ i += 1
+ if len(argv)-i < 2: # need both <dirlist> and <filename>
+ usage()
+ dirlist = argv[i:-1]
+ filename = argv[-1]
+ results = perform_find(mindepth, prune, dirlist, filename)
+ results.sort()
+ for r in set(results):
+ print r
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tools/findleaves.sh b/tools/findleaves.sh
deleted file mode 100755
index 6e3aed0..0000000
--- a/tools/findleaves.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-#
-
-#
-# Finds files with the specified name under a particular directory, stopping
-# the search in a given subdirectory when the file is found.
-#
-
-set -o nounset # fail when dereferencing unset variables
-set -o errexit # fail if any subcommand fails
-
-progName=`basename $0`
-
-function warn() {
- echo "$progName: $@" >&2
-}
-
-function trace() {
- echo "$progName: $@"
-}
-
-function usage() {
- if [[ $# > 0 ]]
- then
- warn $@
- fi
- cat <<-EOF
-Usage: $progName [<options>] <dirlist> <filename>
-Options:
- --mindepth=<mindepth>
- --maxdepth=<maxdepth>
- Both behave in the same way as their find(1) equivalents.
- --prune=<glob>
- Avoids returning results from any path matching the given glob-style
- pattern (e.g., "*/out/*"). May be used multiple times.
-EOF
- exit 1
-}
-
-function fail() {
- warn $@
- exit 1
-}
-
-if [ $# -lt 2 ]
-then
- usage
-fi
-
-findargs=""
-while [[ "${1:0:2}" == "--" ]]
-do
- arg=${1:2}
- name=${arg%%=*}
- value=${arg##*=}
- if [[ "$name" == "mindepth" || "$name" == "maxdepth" ]]
- then
- # Add to beginning of findargs; these must come before the expression.
- findargs="-$name $value $findargs"
- elif [[ "$name" == "prune" ]]
- then
- # Add to end of findargs; these are part of the expression.
- findargs="$findargs -path $value -prune -or"
- fi
- shift
-done
-
-nargs=$#
-# The filename is the last argument
-filename="${!nargs}"
-
-# Print out all files that match, as long as the path isn't explicitly
-# pruned. This will print out extraneous results from directories whose
-# parents have a match. These are filtered out by the awk script below.
-find "${@:1:$nargs-1}" $findargs -type f -name "$filename" -print |
-
-# Only pass along the directory of each match.
-sed -e 's/\/[^\/]*$/\//' |
-
-# Sort the output, so directories appear immediately before their contents.
-# If there are any duplicates, the awk script will implicitly ignore them.
-# The LC_ALL=C forces sort(1) to use bytewise ordering instead of listening
-# to the locale, which may do case-insensitive and/or alphanumeric-only
-# sorting.
-LC_ALL=C sort |
-
-# Always print the first line, which can't possibly be covered by a
-# parent directory match. After that, only print lines where the last
-# line printed isn't a prefix.
-awk -v "filename=$filename" '
- (NR == 1) || (index($0, last) != 1) {
- last = $0;
- printf("%s%s\n", $0, filename);
- }
-'
diff --git a/tools/releasetools/amend_generator.py b/tools/releasetools/amend_generator.py
index 70e71d4..f8f4344 100644
--- a/tools/releasetools/amend_generator.py
+++ b/tools/releasetools/amend_generator.py
@@ -87,6 +87,10 @@
'dur' seconds."""
self.script.append("show_progress %f %d" % (frac, int(dur)))
+ def SetProgress(self, frac):
+ """Not implemented in amend."""
+ pass
+
def PatchCheck(self, filename, *sha1):
"""Check that the given file (or MTD reference) has one of the
given *sha1 hashes."""
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 2e3138c..27264dd 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -15,6 +15,7 @@
import errno
import getopt
import getpass
+import imp
import os
import re
import shutil
@@ -33,7 +34,7 @@
OPTIONS.max_image_size = {}
OPTIONS.verbose = False
OPTIONS.tempfiles = []
-
+OPTIONS.device_specific = None
class ExternalError(RuntimeError): pass
@@ -184,14 +185,20 @@
return key_passwords
-def SignFile(input_name, output_name, key, password, align=None):
+def SignFile(input_name, output_name, key, password, align=None,
+ whole_file=False):
"""Sign the input_name zip/jar/apk, producing output_name. Use the
given key and password (the latter may be None if the key does not
have a password.
If align is an integer > 1, zipalign is run to align stored files in
the output zip on 'align'-byte boundaries.
+
+ If whole_file is true, use the "-w" option to SignApk to embed a
+ signature that covers the whole file in the archive comment of the
+ zip file.
"""
+
if align == 0 or align == 1:
align = None
@@ -201,13 +208,14 @@
else:
sign_name = output_name
- p = Run(["java", "-jar",
- os.path.join(OPTIONS.search_path, "framework", "signapk.jar"),
- key + ".x509.pem",
- key + ".pk8",
- input_name, sign_name],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE)
+ cmd = ["java", "-Xmx512m", "-jar",
+ os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
+ if whole_file:
+ cmd.append("-w")
+ cmd.extend([key + ".x509.pem", key + ".pk8",
+ input_name, sign_name])
+
+ p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
if password is not None:
password += "\n"
p.communicate(password)
@@ -247,6 +255,10 @@
Prepend <dir>/bin to the list of places to search for binaries
run by this script, and expect to find jars in <dir>/framework.
+ -s (--device_specific) <file>
+ Path to the python module containing device-specific
+ releasetools code.
+
-v (--verbose)
Show command lines being executed.
@@ -271,8 +283,9 @@
try:
opts, args = getopt.getopt(
- argv, "hvp:" + extra_opts,
- ["help", "verbose", "path="] + list(extra_long_opts))
+ argv, "hvp:s:" + extra_opts,
+ ["help", "verbose", "path=", "device_specific="] +
+ list(extra_long_opts))
except getopt.GetoptError, err:
Usage(docstring)
print "**", str(err), "**"
@@ -288,6 +301,8 @@
OPTIONS.verbose = True
elif o in ("-p", "--path"):
OPTIONS.search_path = a
+ elif o in ("-s", "--device_specific"):
+ OPTIONS.device_specific = a
else:
if extra_option_handler is None or not extra_option_handler(o, a):
assert False, "unknown option \"%s\"" % (o,)
@@ -412,3 +427,68 @@
zinfo.compress_type = zip.compression
zinfo.external_attr = perms << 16
zip.writestr(zinfo, data)
+
+
+class DeviceSpecificParams(object):
+ module = None
+ def __init__(self, **kwargs):
+ """Keyword arguments to the constructor become attributes of this
+ object, which is passed to all functions in the device-specific
+ module."""
+ for k, v in kwargs.iteritems():
+ setattr(self, k, v)
+
+ if self.module is None:
+ path = OPTIONS.device_specific
+ if not path: return
+ try:
+ if os.path.isdir(path):
+ info = imp.find_module("releasetools", [path])
+ else:
+ d, f = os.path.split(path)
+ b, x = os.path.splitext(f)
+ if x == ".py":
+ f = b
+ info = imp.find_module(f, [d])
+ self.module = imp.load_module("device_specific", *info)
+ except ImportError:
+ print "unable to load device-specific module; assuming none"
+
+ def _DoCall(self, function_name, *args, **kwargs):
+ """Call the named function in the device-specific module, passing
+ the given args and kwargs. The first argument to the call will be
+ the DeviceSpecific object itself. If there is no module, or the
+ module does not define the function, return the value of the
+ 'default' kwarg (which itself defaults to None)."""
+ if self.module is None or not hasattr(self.module, function_name):
+ return kwargs.get("default", None)
+ return getattr(self.module, function_name)(*((self,) + args), **kwargs)
+
+ def FullOTA_Assertions(self):
+ """Called after emitting the block of assertions at the top of a
+ full OTA package. Implementations can add whatever additional
+ assertions they like."""
+ return self._DoCall("FullOTA_Assertions")
+
+ def FullOTA_InstallEnd(self):
+ """Called at the end of full OTA installation; typically this is
+ used to install the image for the device's baseband processor."""
+ return self._DoCall("FullOTA_InstallEnd")
+
+ def IncrementalOTA_Assertions(self):
+ """Called after emitting the block of assertions at the top of an
+ incremental OTA package. Implementations can add whatever
+ additional assertions they like."""
+ return self._DoCall("IncrementalOTA_Assertions")
+
+ def IncrementalOTA_VerifyEnd(self):
+ """Called at the end of the verification phase of incremental OTA
+ installation; additional checks can be placed here to abort the
+ script before any changes are made."""
+ return self._DoCall("IncrementalOTA_VerifyEnd")
+
+ def IncrementalOTA_InstallEnd(self):
+ """Called at the end of incremental OTA installation; typically
+ this is used to install the image for the device's baseband
+ processor."""
+ return self._DoCall("IncrementalOTA_InstallEnd")
diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py
index e7a15cd..d1902e1 100644
--- a/tools/releasetools/edify_generator.py
+++ b/tools/releasetools/edify_generator.py
@@ -100,9 +100,16 @@
def ShowProgress(self, frac, dur):
"""Update the progress bar, advancing it over 'frac' over the next
- 'dur' seconds."""
+ 'dur' seconds. 'dur' may be zero to advance it via SetProgress
+ commands instead of by time."""
self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
+ def SetProgress(self, frac):
+ """Set the position of the progress bar within the chunk defined
+ by the most recent ShowProgress call. 'frac' should be in
+ [0,1]."""
+ self.script.append("set_progress(%f);" % (frac,))
+
def PatchCheck(self, filename, *sha1):
"""Check that the given file (or MTD reference) has one of the
given *sha1 hashes."""
diff --git a/tools/releasetools/img_from_target_files b/tools/releasetools/img_from_target_files
index 00abde4..d157dca 100755
--- a/tools/releasetools/img_from_target_files
+++ b/tools/releasetools/img_from_target_files
@@ -31,6 +31,7 @@
print >> sys.stderr, "Python 2.4 or newer is required."
sys.exit(1)
+import errno
import os
import re
import shutil
@@ -82,8 +83,16 @@
# The name of the directory it is making an image out of matters to
# mkyaffs2image. It wants "system" but we have a directory named
# "SYSTEM", so create a symlink.
- os.symlink(os.path.join(OPTIONS.input_tmp, "SYSTEM"),
- os.path.join(OPTIONS.input_tmp, "system"))
+ try:
+ os.symlink(os.path.join(OPTIONS.input_tmp, "SYSTEM"),
+ os.path.join(OPTIONS.input_tmp, "system"))
+ except OSError, e:
+ # bogus error on my mac version?
+ # File "./build/tools/releasetools/img_from_target_files", line 86, in AddSystem
+ # os.path.join(OPTIONS.input_tmp, "system"))
+ # OSError: [Errno 17] File exists
+ if (e.errno == errno.EEXIST):
+ pass
p = common.Run(["mkyaffs2image", "-f",
os.path.join(OPTIONS.input_tmp, "system"), img.name])
diff --git a/tools/releasetools/ota_from_target_files b/tools/releasetools/ota_from_target_files
index 4aaad37..f129da1 100755
--- a/tools/releasetools/ota_from_target_files
+++ b/tools/releasetools/ota_from_target_files
@@ -57,11 +57,13 @@
sys.exit(1)
import copy
+import errno
import os
import re
import sha
import subprocess
import tempfile
+import threading
import time
import zipfile
@@ -80,6 +82,7 @@
OPTIONS.omit_prereq = False
OPTIONS.extra_script = None
OPTIONS.script_mode = 'auto'
+OPTIONS.worker_threads = 3
def MostPopularKey(d, default):
"""Given a dict, return the key corresponding to the largest
@@ -273,19 +276,14 @@
key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
pw = key_passwords[OPTIONS.package_key]
- common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
+ common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
+ whole_file=True)
def AppendAssertions(script, input_zip):
device = GetBuildProp("ro.product.device", input_zip)
script.AssertDevice(device)
- info = input_zip.read("OTA/android-info.txt")
- m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
- if m:
- bootloaders = m.group(1).split("|")
- script.AssertSomeBootloader(*bootloaders)
-
def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
"""Generate a binary patch that creates the recovery image starting
@@ -302,8 +300,9 @@
executable.
"""
- patch = Difference(recovery_img, boot_img, "imgdiff")
- common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
+ d = Difference(recovery_img, boot_img)
+ _, _, patch = d.ComputePatch()
+ common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
Item.Get("system/recovery-from-boot.p", dir=False)
# Images with different content will have a different first page, so
@@ -324,7 +323,7 @@
'header_sha1': header_sha1,
'recovery_size': recovery_img.size,
'recovery_sha1': recovery_img.sha1 }
- common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
+ common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
return Item.Get("system/etc/install-recovery.sh", dir=False)
@@ -339,19 +338,18 @@
# change very often.
script = edify_generator.EdifyGenerator(2)
+ device_specific = common.DeviceSpecificParams(
+ input_zip=input_zip,
+ output_zip=output_zip,
+ script=script,
+ input_tmp=OPTIONS.input_tmp)
+
if not OPTIONS.omit_prereq:
ts = GetBuildProp("ro.build.date.utc", input_zip)
script.AssertOlderBuild(ts)
AppendAssertions(script, input_zip)
-
- script.ShowProgress(0.1, 0)
-
- try:
- common.ZipWriteStr(output_zip, "radio.img", input_zip.read("RADIO/image"))
- script.WriteFirmwareImage("radio", "radio.img")
- except KeyError:
- print "warning: no radio image in input target_files; not flashing radio"
+ device_specific.FullOTA_Assertions()
script.ShowProgress(0.5, 0)
@@ -360,6 +358,7 @@
script.FormatPartition("system")
script.Mount("MTD", "system", "/system")
+ script.UnpackPackageDir("recovery", "/system")
script.UnpackPackageDir("system", "/system")
symlinks = CopySystemFiles(input_zip, output_zip)
@@ -385,8 +384,11 @@
common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
script.ShowProgress(0.2, 0)
- script.WriteRawImage("boot", "boot.img")
script.ShowProgress(0.2, 10)
+ script.WriteRawImage("boot", "boot.img")
+
+ script.ShowProgress(0.1, 0)
+ device_specific.FullOTA_InstallEnd()
if OPTIONS.extra_script is not None:
script.AppendExtra(OPTIONS.extra_script)
@@ -423,38 +425,111 @@
return out
-def Difference(tf, sf, diff_program):
- """Return the patch (as a string of data) needed to turn sf into tf.
- diff_program is the name of an external program (or list, if
- additional arguments are desired) to run to generate the diff.
- """
+DIFF_PROGRAM_BY_EXT = {
+ ".gz" : "imgdiff",
+ ".zip" : ["imgdiff", "-z"],
+ ".jar" : ["imgdiff", "-z"],
+ ".apk" : ["imgdiff", "-z"],
+ ".img" : "imgdiff",
+ }
- ttemp = tf.WriteToTemp()
- stemp = sf.WriteToTemp()
- ext = os.path.splitext(tf.name)[1]
+class Difference(object):
+ def __init__(self, tf, sf):
+ self.tf = tf
+ self.sf = sf
+ self.patch = None
- try:
- ptemp = tempfile.NamedTemporaryFile()
- if isinstance(diff_program, list):
- cmd = copy.copy(diff_program)
- else:
- cmd = [diff_program]
- cmd.append(stemp.name)
- cmd.append(ttemp.name)
- cmd.append(ptemp.name)
- p = common.Run(cmd)
- _, err = p.communicate()
- if err or p.returncode != 0:
- print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
- return None
- diff = ptemp.read()
- finally:
- ptemp.close()
- stemp.close()
- ttemp.close()
+ def ComputePatch(self):
+ """Compute the patch (as a string of data) needed to turn sf into
+ tf. Returns the same tuple as GetPatch()."""
- return diff
+ tf = self.tf
+ sf = self.sf
+
+ ext = os.path.splitext(tf.name)[1]
+ diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
+
+ ttemp = tf.WriteToTemp()
+ stemp = sf.WriteToTemp()
+
+ ext = os.path.splitext(tf.name)[1]
+
+ try:
+ ptemp = tempfile.NamedTemporaryFile()
+ if isinstance(diff_program, list):
+ cmd = copy.copy(diff_program)
+ else:
+ cmd = [diff_program]
+ cmd.append(stemp.name)
+ cmd.append(ttemp.name)
+ cmd.append(ptemp.name)
+ p = common.Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ _, err = p.communicate()
+ if err or p.returncode != 0:
+ print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
+ return None
+ diff = ptemp.read()
+ finally:
+ ptemp.close()
+ stemp.close()
+ ttemp.close()
+
+ self.patch = diff
+ return self.tf, self.sf, self.patch
+
+
+ def GetPatch(self):
+ """Return a tuple (target_file, source_file, patch_data).
+ patch_data may be None if ComputePatch hasn't been called, or if
+ computing the patch failed."""
+ return self.tf, self.sf, self.patch
+
+
+def ComputeDifferences(diffs):
+ """Call ComputePatch on all the Difference objects in 'diffs'."""
+ print len(diffs), "diffs to compute"
+
+ # Do the largest files first, to try and reduce the long-pole effect.
+ by_size = [(i.tf.size, i) for i in diffs]
+ by_size.sort(reverse=True)
+ by_size = [i[1] for i in by_size]
+
+ lock = threading.Lock()
+ diff_iter = iter(by_size) # accessed under lock
+
+ def worker():
+ try:
+ lock.acquire()
+ for d in diff_iter:
+ lock.release()
+ start = time.time()
+ d.ComputePatch()
+ dur = time.time() - start
+ lock.acquire()
+
+ tf, sf, patch = d.GetPatch()
+ if sf.name == tf.name:
+ name = tf.name
+ else:
+ name = "%s (%s)" % (tf.name, sf.name)
+ if patch is None:
+ print "patching failed! %s" % (name,)
+ else:
+ print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
+ dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
+ lock.release()
+ except Exception, e:
+ print e
+ raise
+
+ # start worker threads; wait for them all to finish.
+ threads = [threading.Thread(target=worker)
+ for i in range(OPTIONS.worker_threads)]
+ for th in threads:
+ th.start()
+ while threads:
+ threads.pop().join()
def GetBuildProp(property, z):
@@ -484,6 +559,7 @@
except KeyError:
return 0
+
def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
source_version = GetRecoveryAPIVersion(source_zip)
@@ -502,6 +578,12 @@
else:
raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
+ device_specific = common.DeviceSpecificParams(
+ source_zip=source_zip,
+ target_zip=target_zip,
+ output_zip=output_zip,
+ script=script)
+
print "Loading target..."
target_data = LoadSystemFiles(target_zip)
print "Loading source..."
@@ -509,9 +591,11 @@
verbatim_targets = []
patch_list = []
+ diffs = []
largest_source_size = 0
for fn in sorted(target_data.keys()):
tf = target_data[fn]
+ assert fn == tf.name
sf = source_data.get(fn, None)
if sf is None or fn in OPTIONS.require_verbatim:
@@ -523,26 +607,23 @@
verbatim_targets.append((fn, tf.size))
elif tf.sha1 != sf.sha1:
# File is different; consider sending as a patch
- diff_method = "bsdiff"
- if tf.name.endswith(".gz"):
- diff_method = "imgdiff"
- d = Difference(tf, sf, diff_method)
- if d is not None:
- print fn, tf.size, len(d), (float(len(d)) / tf.size)
- if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
- # patch is almost as big as the file; don't bother patching
- tf.AddToZip(output_zip)
- verbatim_targets.append((fn, tf.size))
- else:
- common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
- patch_list.append((fn, tf, sf, tf.size))
- largest_source_size = max(largest_source_size, sf.size)
+ diffs.append(Difference(tf, sf))
else:
# Target file identical to source.
pass
- total_verbatim_size = sum([i[1] for i in verbatim_targets])
- total_patched_size = sum([i[3] for i in patch_list])
+ ComputeDifferences(diffs)
+
+ for diff in diffs:
+ tf, sf, d = diff.GetPatch()
+ if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
+ # patch is almost as big as the file; don't bother patching
+ tf.AddToZip(output_zip)
+ verbatim_targets.append((tf.name, tf.size))
+ else:
+ common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
+ patch_list.append((tf.name, tf, sf, tf.size))
+ largest_source_size = max(largest_source_size, sf.size)
source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
@@ -566,35 +647,31 @@
os.path.join(OPTIONS.target_tmp, "RECOVERY")))
updating_recovery = (source_recovery.data != target_recovery.data)
- source_radio = source_zip.read("RADIO/image")
- target_radio = target_zip.read("RADIO/image")
- updating_radio = (source_radio != target_radio)
-
- # The last 0.1 is reserved for creating symlinks, fixing
- # permissions, and writing the boot image (if necessary).
- progress_bar_total = 1.0
- if updating_boot:
- progress_bar_total -= 0.1
- if updating_radio:
- progress_bar_total -= 0.3
+ # Here's how we divide up the progress bar:
+ # 0.1 for verifying the start state (PatchCheck calls)
+ # 0.8 for applying patches (ApplyPatch calls)
+ # 0.1 for unpacking verbatim files, symlinking, and doing the
+ # device-specific commands.
AppendAssertions(script, target_zip)
+ device_specific.IncrementalOTA_Assertions()
script.Print("Verifying current system...")
- pb_verify = progress_bar_total * 0.3 * \
- (total_patched_size /
- float(total_patched_size+total_verbatim_size+1))
+ script.ShowProgress(0.1, 0)
+ total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
+ if updating_boot:
+ total_verify_size += source_boot.size
+ so_far = 0
- for i, (fn, tf, sf, size) in enumerate(patch_list):
- if i % 5 == 0:
- next_sizes = sum([i[3] for i in patch_list[i:i+5]])
- script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
-
+ for fn, tf, sf, size in patch_list:
script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
+ so_far += sf.size
+ script.SetProgress(so_far / total_verify_size)
if updating_boot:
- d = Difference(target_boot, source_boot, "imgdiff")
+ d = Difference(target_boot, source_boot)
+ _, _, d = d.ComputePatch()
print "boot target: %d source: %d diff: %d" % (
target_boot.size, source_boot.size, len(d))
@@ -603,12 +680,16 @@
script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
(source_boot.size, source_boot.sha1,
target_boot.size, target_boot.sha1))
+ so_far += source_boot.size
+ script.SetProgress(so_far / total_verify_size)
if patch_list or updating_recovery or updating_boot:
script.CacheFreeSpaceCheck(largest_source_size)
script.Print("Unpacking patches...")
script.UnpackPackageDir("patch", "/tmp/patchtmp")
+ device_specific.IncrementalOTA_VerifyEnd()
+
script.Comment("---- start making changes here ----")
if OPTIONS.wipe_user_data:
@@ -621,6 +702,19 @@
if i not in target_data] +
["/system/recovery.img"])
+ script.ShowProgress(0.8, 0)
+ total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
+ if updating_boot:
+ total_patch_size += target_boot.size
+ so_far = 0
+
+ script.Print("Patching system files...")
+ for fn, tf, sf, size in patch_list:
+ script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
+ sf.sha1, "/tmp/patchtmp/"+fn+".p")
+ so_far += tf.size
+ script.SetProgress(so_far / total_patch_size)
+
if updating_boot:
# Produce the boot image by applying a patch to the current
# contents of the boot partition, and write it back to the
@@ -632,6 +726,8 @@
"-",
target_boot.size, target_boot.sha1,
source_boot.sha1, "/tmp/patchtmp/boot.img.p")
+ so_far += target_boot.size
+ script.SetProgress(so_far / total_patch_size)
print "boot image changed; including."
else:
print "boot image unchanged; skipping."
@@ -650,29 +746,12 @@
# as fodder for constructing the recovery image.
recovery_sh_item = MakeRecoveryPatch(output_zip,
target_recovery, target_boot)
+ script.UnpackPackageDir("recovery", "/system")
print "recovery image changed; including as patch from boot."
else:
print "recovery image unchanged; skipping."
- if updating_radio:
- script.ShowProgress(0.3, 10)
- script.Print("Writing radio image...")
- script.WriteFirmwareImage("radio", "radio.img")
- common.ZipWriteStr(output_zip, "radio.img", target_radio)
- print "radio image changed; including."
- else:
- print "radio image unchanged; skipping."
-
- script.Print("Patching system files...")
- pb_apply = progress_bar_total * 0.7 * \
- (total_patched_size /
- float(total_patched_size+total_verbatim_size+1))
- for i, (fn, tf, sf, size) in enumerate(patch_list):
- if i % 5 == 0:
- next_sizes = sum([i[3] for i in patch_list[i:i+5]])
- script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
- script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
- sf.sha1, "/tmp/patchtmp/"+fn+".p")
+ script.ShowProgress(0.1, 10)
target_symlinks = CopySystemFiles(target_zip, None)
@@ -700,14 +779,10 @@
script.DeleteFiles(to_delete)
if verbatim_targets:
- pb_verbatim = progress_bar_total * \
- (total_verbatim_size /
- float(total_patched_size+total_verbatim_size+1))
- script.ShowProgress(pb_verbatim, 5)
script.Print("Unpacking new files...")
script.UnpackPackageDir("system", "/system")
- script.Print("Finishing up...")
+ script.Print("Symlinks and permissions...")
# Create all the symlinks that don't already exist, or point to
# somewhere different than what we want. Delete each symlink before
@@ -726,6 +801,9 @@
# permissions.
script.AppendScript(temp_script)
+ # Do device-specific installation (eg, write radio image).
+ device_specific.IncrementalOTA_InstallEnd()
+
if OPTIONS.extra_script is not None:
scirpt.AppendExtra(OPTIONS.extra_script)
@@ -749,6 +827,8 @@
OPTIONS.extra_script = a
elif o in ("-m", "--script_mode"):
OPTIONS.script_mode = a
+ elif o in ("--worker_threads"):
+ OPTIONS.worker_threads = int(a)
else:
return False
return True
@@ -761,7 +841,8 @@
"wipe_user_data",
"no_prereq",
"extra_script=",
- "script_mode="],
+ "script_mode=",
+ "worker_threads="],
extra_option_handler=option_handler)
if len(args) != 2:
@@ -777,6 +858,23 @@
print "unzipping target target-files..."
OPTIONS.input_tmp = common.UnzipTemp(args[0])
+ if OPTIONS.device_specific is None:
+ # look for the device-specific tools extension location in the input
+ try:
+ f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
+ ds = f.read().strip()
+ f.close()
+ if ds:
+ ds = os.path.normpath(ds)
+ print "using device-specific extensions in", ds
+ OPTIONS.device_specific = ds
+ except IOError, e:
+ if e.errno == errno.ENOENT:
+ # nothing specified in the file
+ pass
+ else:
+ raise
+
common.LoadMaxSizes()
if not OPTIONS.max_image_size:
print
diff --git a/tools/releasetools/sign_target_files_apks b/tools/releasetools/sign_target_files_apks
index 158845c..961e643 100755
--- a/tools/releasetools/sign_target_files_apks
+++ b/tools/releasetools/sign_target_files_apks
@@ -20,10 +20,6 @@
Usage: sign_target_files_apks [flags] input_target_files output_target_files
- -s (--signapk_jar) <path>
- Path of the signapks.jar file used to sign an individual APK
- file.
-
-e (--extra_apks) <name,name,...=key>
Add extra APK name/key pairs as though they appeared in
apkcerts.txt (so mappings specified by -k and -d are applied).
@@ -307,9 +303,7 @@
def main(argv):
def option_handler(o, a):
- if o in ("-s", "--signapk_jar"):
- OPTIONS.signapk_jar = a
- elif o in ("-e", "--extra_apks"):
+ if o in ("-e", "--extra_apks"):
names, key = a.split("=")
names = names.split(",")
for n in names:
@@ -339,9 +333,8 @@
return True
args = common.ParseOptions(argv, __doc__,
- extra_opts="s:e:d:k:ot:",
- extra_long_opts=["signapk_jar=",
- "extra_apks=",
+ extra_opts="e:d:k:ot:",
+ extra_long_opts=["extra_apks=",
"default_key_mappings=",
"key_mapping=",
"replace_ota_keys",
diff --git a/tools/signapk/SignApk.java b/tools/signapk/SignApk.java
index caf7935..3244a49 100644
--- a/tools/signapk/SignApk.java
+++ b/tools/signapk/SignApk.java
@@ -247,7 +247,7 @@
}
}
- /** Write a .SF file with a digest the specified manifest. */
+ /** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
@@ -304,6 +304,75 @@
pkcs7.encodeSignedData(out);
}
+ private static void signWholeOutputFile(byte[] zipData,
+ OutputStream outputStream,
+ X509Certificate publicKey,
+ PrivateKey privateKey)
+ throws IOException, GeneralSecurityException {
+
+ // For a zip with no archive comment, the
+ // end-of-central-directory record will be 22 bytes long, so
+ // we expect to find the EOCD marker 22 bytes from the end.
+ if (zipData[zipData.length-22] != 0x50 ||
+ zipData[zipData.length-21] != 0x4b ||
+ zipData[zipData.length-20] != 0x05 ||
+ zipData[zipData.length-19] != 0x06) {
+ throw new IllegalArgumentException("zip data already has an archive comment");
+ }
+
+ Signature signature = Signature.getInstance("SHA1withRSA");
+ signature.initSign(privateKey);
+ signature.update(zipData, 0, zipData.length-2);
+
+ ByteArrayOutputStream temp = new ByteArrayOutputStream();
+
+ // put a readable message and a null char at the start of the
+ // archive comment, so that tools that display the comment
+ // (hopefully) show something sensible.
+ // TODO: anything more useful we can put in this message?
+ byte[] message = "signed by SignApk".getBytes("UTF-8");
+ temp.write(message);
+ temp.write(0);
+ writeSignatureBlock(signature, publicKey, temp);
+ int total_size = temp.size() + 6;
+ if (total_size > 0xffff) {
+ throw new IllegalArgumentException("signature is too big for ZIP file comment");
+ }
+ // signature starts this many bytes from the end of the file
+ int signature_start = total_size - message.length - 1;
+ temp.write(signature_start & 0xff);
+ temp.write((signature_start >> 8) & 0xff);
+ // Why the 0xff bytes? In a zip file with no archive comment,
+ // bytes [-6:-2] of the file are the little-endian offset from
+ // the start of the file to the central directory. So for the
+ // two high bytes to be 0xff 0xff, the archive would have to
+ // be nearly 4GB in side. So it's unlikely that a real
+ // commentless archive would have 0xffs here, and lets us tell
+ // an old signed archive from a new one.
+ temp.write(0xff);
+ temp.write(0xff);
+ temp.write(total_size & 0xff);
+ temp.write((total_size >> 8) & 0xff);
+ temp.flush();
+
+ // Signature verification checks that the EOCD header is the
+ // last such sequence in the file (to avoid minzip finding a
+ // fake EOCD appended after the signature in its scan). The
+ // odds of producing this sequence by chance are very low, but
+ // let's catch it here if it does.
+ byte[] b = temp.toByteArray();
+ for (int i = 0; i < b.length-3; ++i) {
+ if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
+ throw new IllegalArgumentException("found spurious EOCD header at " + i);
+ }
+ }
+
+ outputStream.write(zipData, 0, zipData.length-2);
+ outputStream.write(total_size & 0xff);
+ outputStream.write((total_size >> 8) & 0xff);
+ temp.writeTo(outputStream);
+ }
+
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
@@ -340,25 +409,40 @@
}
public static void main(String[] args) {
- if (args.length != 4) {
- System.err.println("Usage: signapk " +
+ if (args.length != 4 && args.length != 5) {
+ System.err.println("Usage: signapk [-w] " +
"publickey.x509[.pem] privatekey.pk8 " +
"input.jar output.jar");
System.exit(2);
}
+ boolean signWholeFile = false;
+ int argstart = 0;
+ if (args[0].equals("-w")) {
+ signWholeFile = true;
+ argstart = 1;
+ }
+
JarFile inputJar = null;
JarOutputStream outputJar = null;
+ FileOutputStream outputFile = null;
try {
- X509Certificate publicKey = readPublicKey(new File(args[0]));
+ X509Certificate publicKey = readPublicKey(new File(args[argstart+0]));
// Assume the certificate is valid for at least an hour.
long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;
- PrivateKey privateKey = readPrivateKey(new File(args[1]));
- inputJar = new JarFile(new File(args[2]), false); // Don't verify.
- outputJar = new JarOutputStream(new FileOutputStream(args[3]));
+ PrivateKey privateKey = readPrivateKey(new File(args[argstart+1]));
+ inputJar = new JarFile(new File(args[argstart+2]), false); // Don't verify.
+
+ OutputStream outputStream = null;
+ if (signWholeFile) {
+ outputStream = new ByteArrayOutputStream();
+ } else {
+ outputStream = outputFile = new FileOutputStream(args[argstart+3]);
+ }
+ outputJar = new JarOutputStream(outputStream);
outputJar.setLevel(9);
JarEntry je;
@@ -387,13 +471,23 @@
// Everything else
copyFiles(manifest, inputJar, outputJar, timestamp);
+
+ outputJar.close();
+ outputJar = null;
+ outputStream.flush();
+
+ if (signWholeFile) {
+ outputFile = new FileOutputStream(args[argstart+3]);
+ signWholeOutputFile(((ByteArrayOutputStream)outputStream).toByteArray(),
+ outputFile, publicKey, privateKey);
+ }
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
} finally {
try {
if (inputJar != null) inputJar.close();
- if (outputJar != null) outputJar.close();
+ if (outputFile != null) outputFile.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
diff --git a/tools/warn.py b/tools/warn.py
new file mode 100755
index 0000000..6fea20a
--- /dev/null
+++ b/tools/warn.py
@@ -0,0 +1,523 @@
+#!/usr/bin/env python
+
+import sys
+import re
+
+if len(sys.argv) == 1:
+ print 'usage: ' + sys.argv[0] + ' <build.log>'
+ sys.exit()
+
+# if you add another level, don't forget to give it a color below
+class severity:
+ UNKNOWN=0
+ SKIP=100
+ FIXMENOW=1
+ HIGH=2
+ MEDIUM=3
+ LOW=4
+ HARMLESS=5
+
+def colorforseverity(sev):
+ if sev == severity.FIXMENOW:
+ return 'fuchsia'
+ if sev == severity.HIGH:
+ return 'red'
+ if sev == severity.MEDIUM:
+ return 'orange'
+ if sev == severity.LOW:
+ return 'yellow'
+ if sev == severity.HARMLESS:
+ return 'limegreen'
+ if sev == severity.UNKNOWN:
+ return 'blue'
+ return 'grey'
+
+warnpatterns = [
+ { 'category':'make', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'make: overriding commands/ignoring old commands',
+ 'patterns':[r".*: warning: overriding commands for target .+",
+ r".*: warning: ignoring old commands for target .+"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-Wimplicit-function-declaration',
+ 'description':'Implicit function declaration',
+ 'patterns':[r".*: warning: implicit declaration of function .+"] },
+ { 'category':'C/C++', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: conflicting types for '.+'"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-Wtype-limits',
+ 'description':'Expression always evaluates to true or false',
+ 'patterns':[r".*: warning: comparison is always false due to limited range of data type",
+ r".*: warning: comparison of unsigned expression >= 0 is always true",
+ r".*: warning: comparison of unsigned expression < 0 is always false"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Incompatible pointer types',
+ 'patterns':[r".*: warning: assignment from incompatible pointer type",
+ r".*: warning: passing argument [0-9]+ of '.*' from incompatible pointer type",
+ r".*: warning: initialization from incompatible pointer type"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-fno-builtin',
+ 'description':'Incompatible declaration of built in function',
+ 'patterns':[r".*: warning: incompatible implicit declaration of built-in function .+"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wunused-parameter',
+ 'description':'Unused parameter',
+ 'patterns':[r".*: warning: unused parameter '.*'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wunused',
+ 'description':'Unused function, variable or label',
+ 'patterns':[r".*: warning: '.+' defined but not used"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wunused-value',
+ 'description':'Statement with no effect',
+ 'patterns':[r".*: warning: statement with no effect"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmissing-field-initializers',
+ 'description':'Missing initializer',
+ 'patterns':[r".*: warning: missing initializer"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: \(near initialization for '.+'\)"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wformat',
+ 'description':'Format string does not match arguments',
+ 'patterns':[r".*: warning: format '.+' expects type '.+', but argument [0-9]+ has type '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wformat-extra-args',
+ 'description':'Too many arguments for format string',
+ 'patterns':[r".*: warning: too many arguments for format"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wsign-compare',
+ 'description':'Comparison between signed and unsigned',
+ 'patterns':[r".*: warning: comparison between signed and unsigned",
+ r".*: warning: comparison of promoted \~unsigned with unsigned",
+ r".*: warning: signed and unsigned type in conditional expression"] },
+ { 'category':'libpng', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'libpng: zero area',
+ 'patterns':[r".*libpng warning: Ignoring attempt to set cHRM RGB triangle with zero area"] },
+ { 'category':'aapt', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'aapt: no comment for public symbol',
+ 'patterns':[r".*: warning: No comment for public symbol .+"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmissing-braces',
+ 'description':'Missing braces around initializer',
+ 'patterns':[r".*: warning: missing braces around initializer.*"] },
+ { 'category':'C/C++', 'severity':severity.HARMLESS, 'members':[], 'option':'',
+ 'description':'No newline at end of file',
+ 'patterns':[r".*: warning: no newline at end of file"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wcast-qual',
+ 'description':'Qualifier discarded',
+ 'patterns':[r".*: warning: passing argument [0-9]+ of '.+' discards qualifiers from pointer target type",
+ r".*: warning: assignment discards qualifiers from pointer target type",
+ r".*: warning: return discards qualifiers from pointer target type"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wattributes',
+ 'description':'Attribute ignored',
+ 'patterns':[r".*: warning: '_*packed_*' attribute ignored"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wattributes',
+ 'description':'Visibility mismatch',
+ 'patterns':[r".*: warning: '.+' declared with greater visibility than the type of its field '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Shift count greater than width of type',
+ 'patterns':[r".*: warning: (left|right) shift count >= width of type"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'extern <foo> is initialized',
+ 'patterns':[r".*: warning: '.+' initialized and declared 'extern'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wold-style-declaration',
+ 'description':'Old style declaration',
+ 'patterns':[r".*: warning: 'static' is not at beginning of declaration"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wuninitialized',
+ 'description':'Variable may be used uninitialized',
+ 'patterns':[r".*: warning: '.+' may be used uninitialized in this function"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-Wuninitialized',
+ 'description':'Variable is used uninitialized',
+ 'patterns':[r".*: warning: '.+' is used uninitialized in this function"] },
+ { 'category':'ld', 'severity':severity.MEDIUM, 'members':[], 'option':'-fshort-enums',
+ 'description':'ld: possible enum size mismatch',
+ 'patterns':[r".*: warning: .* uses variable-size enums yet the output is to use 32-bit enums; use of enum values across objects may fail"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wpointer-sign',
+ 'description':'Pointer targets differ in signedness',
+ 'patterns':[r".*: warning: pointer targets in initialization differ in signedness",
+ r".*: warning: pointer targets in assignment differ in signedness",
+ r".*: warning: pointer targets in return differ in signedness",
+ r".*: warning: pointer targets in passing argument [0-9]+ of '.+' differ in signedness"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wstrict-overflow',
+ 'description':'Assuming overflow does not occur',
+ 'patterns':[r".*: warning: assuming signed overflow does not occur when assuming that .* is always (true|false)"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wempty-body',
+ 'description':'Suggest adding braces around empty body',
+ 'patterns':[r".*: warning: suggest braces around empty body in an 'if' statement",
+ r".*: warning: empty body in an if-statement",
+ r".*: warning: suggest braces around empty body in an 'else' statement",
+ r".*: warning: empty body in an else-statement"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wparentheses',
+ 'description':'Suggest adding parentheses',
+ 'patterns':[r".*: warning: suggest explicit braces to avoid ambiguous 'else'",
+ r".*: warning: suggest parentheses around arithmetic in operand of '.+'",
+ r".*: warning: suggest parentheses around comparison in operand of '.+'",
+ r".*: warning: suggest parentheses around '.+?' .+ '.+?'",
+ r".*: warning: suggest parentheses around assignment used as truth value"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Static variable used in non-static inline function',
+ 'patterns':[r".*: warning: '.+' is static but used in inline function '.+' which is not static"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wimplicit int',
+ 'description':'No type or storage class (will default to int)',
+ 'patterns':[r".*: warning: data definition has no type or storage class"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: type defaults to 'int' in declaration of '.+'"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: parameter names \(without types\) in function declaration"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wstrict-aliasing',
+ 'description':'Dereferencing <foo> breaks strict aliasing rules',
+ 'patterns':[r".*: warning: dereferencing .* break strict-aliasing rules"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wpointer-to-int-cast',
+ 'description':'Cast from pointer to integer of different size',
+ 'patterns':[r".*: warning: cast from pointer to integer of different size"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wint-to-pointer-cast',
+ 'description':'Cast to pointer from integer of different size',
+ 'patterns':[r".*: warning: cast to pointer from integer of different size"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Symbol redefined',
+ 'patterns':[r".*: warning: "".+"" redefined"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: this is the location of the previous definition"] },
+ { 'category':'ld', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'ld: type and size of dynamic symbol are not defined',
+ 'patterns':[r".*: warning: type and size of dynamic symbol `.+' are not defined"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Pointer from integer without cast',
+ 'patterns':[r".*: warning: assignment makes pointer from integer without a cast"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Pointer from integer without cast',
+ 'patterns':[r".*: warning: passing argument [0-9]+ of '.+' makes pointer from integer without a cast"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Integer from pointer without cast',
+ 'patterns':[r".*: warning: assignment makes integer from pointer without a cast"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Integer from pointer without cast',
+ 'patterns':[r".*: warning: passing argument [0-9]+ of '.+' makes integer from pointer without a cast"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Integer from pointer without cast',
+ 'patterns':[r".*: warning: return makes integer from pointer without a cast"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wunknown-pragmas',
+ 'description':'Ignoring pragma',
+ 'patterns':[r".*: warning: ignoring #pragma .+"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wclobbered',
+ 'description':'Variable might be clobbered by longjmp or vfork',
+ 'patterns':[r".*: warning: variable '.+' might be clobbered by 'longjmp' or 'vfork'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wclobbered',
+ 'description':'Argument might be clobbered by longjmp or vfork',
+ 'patterns':[r".*: warning: argument '.+' might be clobbered by 'longjmp' or 'vfork'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wredundant-decls',
+ 'description':'Redundant declaration',
+ 'patterns':[r".*: warning: redundant redeclaration of '.+'"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: previous declaration of '.+' was here"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wswitch-enum',
+ 'description':'Enum value not handled in switch',
+ 'patterns':[r".*: warning: enumeration value '.+' not handled in switch"] },
+ { 'category':'java', 'severity':severity.MEDIUM, 'members':[], 'option':'-encoding',
+ 'description':'Java: Non-ascii characters used, but ascii encoding specified',
+ 'patterns':[r".*: warning: unmappable character for encoding ascii"] },
+ { 'category':'java', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Java: Non-varargs call of varargs method with inexact argument type for last parameter',
+ 'patterns':[r".*: warning: non-varargs call of varargs method with inexact argument type for last parameter"] },
+ { 'category':'aapt', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'aapt: String marked untranslatable, but translation exists',
+ 'patterns':[r".*: warning: string '.+' in .* marked untranslatable but exists in locale '??_??'"] },
+ { 'category':'aapt', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'aapt: empty span in string',
+ 'patterns':[r".*: warning: empty '.+' span found in text '.+"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Taking address of temporary',
+ 'patterns':[r".*: warning: taking address of temporary"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Possible broken line continuation',
+ 'patterns':[r".*: warning: backslash and newline separated by space"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Warray-bounds',
+ 'description':'Array subscript out of bounds',
+ 'patterns':[r".*: warning: array subscript is above array bounds"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Decimal constant is unsigned only in ISO C90',
+ 'patterns':[r".*: warning: this decimal constant is unsigned only in ISO C90"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmain',
+ 'description':'main is usually a function',
+ 'patterns':[r".*: warning: 'main' is usually a function"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Typedef ignored',
+ 'patterns':[r".*: warning: 'typedef' was ignored in this declaration"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-Waddress',
+ 'description':'Address always evaluates to true',
+ 'patterns':[r".*: warning: the address of '.+' will always evaluate as 'true'"] },
+ { 'category':'C/C++', 'severity':severity.FIXMENOW, 'members':[], 'option':'',
+ 'description':'Freeing a non-heap object',
+ 'patterns':[r".*: warning: attempt to free a non-heap object '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wchar-subscripts',
+ 'description':'Array subscript has type char',
+ 'patterns':[r".*: warning: array subscript has type 'char'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Constant too large for type',
+ 'patterns':[r".*: warning: integer constant is too large for '.+' type"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Woverflow',
+ 'description':'Constant too large for type, truncated',
+ 'patterns':[r".*: warning: large integer implicitly truncated to unsigned type"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Woverflow',
+ 'description':'Overflow in implicit constant conversion',
+ 'patterns':[r".*: warning: overflow in implicit constant conversion"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Declaration does not declare anything',
+ 'patterns':[r".*: warning: declaration 'class .+' does not declare anything"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wreorder',
+ 'description':'Initialization order will be different',
+ 'patterns':[r".*: warning: '.+' will be initialized after"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: '.+'"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: when initialized here"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmissing-parameter-type',
+ 'description':'Parameter type not specified',
+ 'patterns':[r".*: warning: type of '.+' defaults to 'int'"] },
+ { 'category':'gcc', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Invalid option for C file',
+ 'patterns':[r".*: warning: command line option "".+"" is valid for C\+\+\/ObjC\+\+ but not for C"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'User warning',
+ 'patterns':[r".*: warning: #warning "".+"""] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wextra',
+ 'description':'Dereferencing void*',
+ 'patterns':[r".*: warning: dereferencing 'void \*' pointer"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wextra',
+ 'description':'Comparison of pointer to zero',
+ 'patterns':[r".*: warning: ordered comparison of pointer with integer zero"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wwrite-strings',
+ 'description':'Conversion of string constant to non-const char*',
+ 'patterns':[r".*: warning: deprecated conversion from string constant to '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wstrict-prototypes',
+ 'description':'Function declaration isn''t a prototype',
+ 'patterns':[r".*: warning: function declaration isn't a prototype"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wignored-qualifiers',
+ 'description':'Type qualifiers ignored on function return value',
+ 'patterns':[r".*: warning: type qualifiers ignored on function return type"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'<foo> declared inside parameter list, scope limited to this definition',
+ 'patterns':[r".*: warning: '.+' declared inside parameter list"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: its scope is only this definition or declaration, which is probably not what you want"] },
+ { 'category':'C/C++', 'severity':severity.LOW, 'members':[], 'option':'-Wcomment',
+ 'description':'Line continuation inside comment',
+ 'patterns':[r".*: warning: multi-line comment"] },
+ { 'category':'C/C++', 'severity':severity.HARMLESS, 'members':[], 'option':'',
+ 'description':'Extra tokens after #endif',
+ 'patterns':[r".*: warning: extra tokens at end of #endif directive"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wenum-compare',
+ 'description':'Comparison between different enums',
+ 'patterns':[r".*: warning: comparison between 'enum .+' and 'enum .+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wconversion',
+ 'description':'Implicit conversion of negative number to unsigned type',
+ 'patterns':[r".*: warning: converting negative value '.+' to '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Passing NULL as non-pointer argument',
+ 'patterns':[r".*: warning: passing NULL to non-pointer argument [0-9]+ of '.+'"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wctor-dtor-privacy',
+ 'description':'Class seems unusable because of private ctor/dtor' ,
+ 'patterns':[r".*: warning: all member functions in class '.+' are private"] },
+ # skip this next one, because it only points out some RefBase-based classes where having a private destructor is perfectly fine
+ { 'category':'C/C++', 'severity':severity.SKIP, 'members':[], 'option':'-Wctor-dtor-privacy',
+ 'description':'Class seems unusable because of private ctor/dtor' ,
+ 'patterns':[r".*: warning: 'class .+' only defines a private destructor and has no friends"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wctor-dtor-privacy',
+ 'description':'Class seems unusable because of private ctor/dtor' ,
+ 'patterns':[r".*: warning: 'class .+' only defines private constructors and has no friends"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wpointer-arith',
+ 'description':'void* used in arithmetic' ,
+ 'patterns':[r".*: warning: pointer of type 'void \*' used in (arithmetic|subtraction)",
+ r".*: warning: wrong type argument to increment"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wsign-promo',
+ 'description':'Overload resolution chose to promote from unsigned or enum to signed type' ,
+ 'patterns':[r".*: warning: passing '.+' chooses 'int' over '.* int'"] },
+ { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: in call to '.+'"] },
+ { 'category':'C/C++', 'severity':severity.HIGH, 'members':[], 'option':'-Wextra',
+ 'description':'Base should be explicitly initialized in copy constructor',
+ 'patterns':[r".*: warning: base class '.+' should be explicitly initialized in the copy constructor"] },
+ { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'',
+ 'description':'Converting from <type> to <other type>',
+ 'patterns':[r".*: warning: converting to '.+' from '.+'"] },
+
+ # these next ones are to deal with formatting problems resulting from the log being mixed up by 'make -j'
+ { 'category':'C/C++', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: ,$"] },
+ { 'category':'C/C++', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: $"] },
+ { 'category':'C/C++', 'severity':severity.SKIP, 'members':[], 'option':'',
+ 'description':'',
+ 'patterns':[r".*: warning: In file included from .+,"] },
+
+ # catch-all for warnings this script doesn't know about yet
+ { 'category':'C/C++', 'severity':severity.UNKNOWN, 'members':[], 'option':'',
+ 'description':'Unclassified/unrecognized warnings',
+ 'patterns':[r".*: warning: .+"] },
+]
+
+anchor = 0
+cur_row_color = 0
+row_colors = [ 'e0e0e0', 'd0d0d0' ]
+
+def output(text):
+ print text,
+
+def htmlbig(param):
+ return '<font size="+2">' + param + '</font>'
+
+def dumphtmlprologue(title):
+ output('<html>\n<head>\n<title>' + title + '</title>\n<body>\n')
+ output(htmlbig(title))
+ output('<p>\n')
+
+def tablerow(text):
+ global cur_row_color
+ output('<tr bgcolor="' + row_colors[cur_row_color] + '"><td colspan="2">',)
+ cur_row_color = 1 - cur_row_color
+ output(text,)
+ output('</td></tr>')
+
+def begintable(text, backgroundcolor):
+ global anchor
+ output('<table border="1" rules="cols" frame="box" width="100%" bgcolor="black"><tr bgcolor="' +
+ backgroundcolor + '"><a name="anchor' + str(anchor) + '"><td>')
+ output(htmlbig(text[0]) + '<br>')
+ for i in text[1:]:
+ output(i + '<br>')
+ output('</td>')
+ output('<td width="100" bgcolor="grey"><a align="right" href="#anchor' + str(anchor-1) +
+ '">previous</a><br><a align="right" href="#anchor' + str(anchor+1) + '">next</a>')
+ output('</td></a></tr>')
+ anchor += 1
+
+def endtable():
+ output('</table><p>')
+
+
+# dump some stats about total number of warnings and such
+def dumpstats():
+ known = 0
+ unknown = 0
+ for i in warnpatterns:
+ if i['severity'] == severity.UNKNOWN:
+ unknown += len(i['members'])
+ elif i['severity'] != severity.SKIP:
+ known += len(i['members'])
+ output('Number of classified warnings: <b>' + str(known) + '</b><br>' )
+ output('Number of unclassified warnings: <b>' + str(unknown) + '</b><br>')
+ total = unknown + known
+ output('Total number of warnings: <b>' + str(total) + '</b>')
+ if total < 1000:
+ output('(low count may indicate incremental build)')
+ output('<p>')
+
+def allpatterns(cat):
+ pats = ''
+ for i in cat['patterns']:
+ pats += i
+ pats += ' / '
+ return pats
+
+def descriptionfor(cat):
+ if cat['description'] != '':
+ return cat['description']
+ return allpatterns(cat)
+
+
+# show which warnings no longer occur
+def dumpfixed():
+ tablestarted = False
+ for i in warnpatterns:
+ if len(i['members']) == 0 and i['severity'] != severity.SKIP:
+ if tablestarted == False:
+ tablestarted = True
+ begintable(['Fixed warnings', 'No more occurences. Please consider turning these in to errors if possible, before they are reintroduced in to the build'], 'blue')
+ tablerow(i['description'] + ' (' + allpatterns(i) + ') ' + i['option'])
+ if tablestarted:
+ endtable()
+
+
+# dump a category, provided it is not marked as 'SKIP' and has more than 0 occurrences
+def dumpcategory(cat):
+ if cat['severity'] != severity.SKIP and len(cat['members']) != 0:
+ header = [descriptionfor(cat),str(len(cat['members'])) + ' occurences:']
+ if cat['option'] != '':
+ header[1:1] = [' (related option: ' + cat['option'] +')']
+ begintable(header, colorforseverity(cat['severity']))
+ for i in cat['members']:
+ tablerow(i)
+ endtable()
+
+
+# dump everything for a given severity
+def dumpseverity(sev):
+ for i in warnpatterns:
+ if i['severity'] == sev:
+ dumpcategory(i)
+
+
+def classifywarning(line):
+ for i in warnpatterns:
+ for cpat in i['compiledpatterns']:
+ if cpat.match(line):
+ i['members'].append(line)
+ return
+ else:
+ # If we end up here, there was a problem parsing the log
+ # probably caused by 'make -j' mixing the output from
+ # 2 or more concurrent compiles
+ pass
+
+# precompiling every pattern speeds up parsing by about 30x
+def compilepatterns():
+ for i in warnpatterns:
+ i['compiledpatterns'] = []
+ for pat in i['patterns']:
+ i['compiledpatterns'].append(re.compile(pat))
+
+infile = open(sys.argv[1], 'r')
+warnings = []
+
+platformversion = 'unknown'
+targetproduct = 'unknown'
+targetvariant = 'unknown'
+linecounter = 0
+
+warningpattern = re.compile('.* warning:.*')
+compilepatterns()
+
+# read the log file and classify all the warnings
+lastmatchedline = ''
+for line in infile:
+ if warningpattern.match(line):
+ if line != lastmatchedline:
+ classifywarning(line)
+ lastmatchedline = line
+ else:
+ # save a little bit of time by only doing this for the first few lines
+ if linecounter < 50:
+ linecounter +=1
+ m = re.search('(?<=^PLATFORM_VERSION=).*', line)
+ if m != None:
+ platformversion = m.group(0)
+ m = re.search('(?<=^TARGET_PRODUCT=).*', line)
+ if m != None:
+ targetproduct = m.group(0)
+ m = re.search('(?<=^TARGET_BUILD_VARIANT=).*', line)
+ if m != None:
+ targetvariant = m.group(0)
+
+
+# dump the html output to stdout
+dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant)
+dumpstats()
+dumpseverity(severity.FIXMENOW)
+dumpseverity(severity.HIGH)
+dumpseverity(severity.MEDIUM)
+dumpseverity(severity.LOW)
+dumpseverity(severity.HARMLESS)
+dumpseverity(severity.UNKNOWN)
+dumpfixed()
+
diff --git a/tools/zipalign/Android.mk b/tools/zipalign/Android.mk
index e23b699..f90a31c 100644
--- a/tools/zipalign/Android.mk
+++ b/tools/zipalign/Android.mk
@@ -8,7 +8,9 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
- ZipAlign.cpp
+ ZipAlign.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp
LOCAL_C_INCLUDES += external/zlib
diff --git a/tools/zipalign/README.txt b/tools/zipalign/README.txt
index a2e1a5e..9c7d07e 100644
--- a/tools/zipalign/README.txt
+++ b/tools/zipalign/README.txt
@@ -1,7 +1,9 @@
zipalign -- zip archive alignment tool
usage: zipalign [-f] [-v] <align> infile.zip outfile.zip
+ zipalign -c [-v] <align> infile.zip
+ -c : check alignment only (does not modify file)
-f : overwrite existing outfile.zip
-v : verbose output
<align> is in bytes, e.g. "4" provides 32-bit alignment
@@ -29,3 +31,5 @@
By default, zipalign will not overwrite an existing output file. With the
"-f" flag, an existing file will be overwritten.
+You can use the "-c" flag to test whether a zip archive is properly aligned.
+
diff --git a/tools/zipalign/ZipAlign.cpp b/tools/zipalign/ZipAlign.cpp
index 058f9ed..c2d8159 100644
--- a/tools/zipalign/ZipAlign.cpp
+++ b/tools/zipalign/ZipAlign.cpp
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/*
* Zip alignment tool
*/
-#include "utils/ZipFile.h"
+#include "ZipFile.h"
#include <stdlib.h>
#include <stdio.h>
@@ -29,9 +30,15 @@
void usage(void)
{
fprintf(stderr, "Zip alignment utility\n");
+ fprintf(stderr, "Copyright (C) 2009 The Android Open Source Project\n\n");
fprintf(stderr,
"Usage: zipalign [-f] [-v] <align> infile.zip outfile.zip\n"
- " zipalign -c [-v] <align> infile.zip\n" );
+ " zipalign -c [-v] <align> infile.zip\n\n" );
+ fprintf(stderr,
+ " <align>: alignment in bytes, e.g. '4' provides 32-bit alignment\n");
+ fprintf(stderr, " -c: check alignment only (does not modify file)\n");
+ fprintf(stderr, " -f: overwrite existing outfile.zip\n");
+ fprintf(stderr, " -v: verbose output\n");
}
/*
diff --git a/tools/zipalign/ZipEntry.cpp b/tools/zipalign/ZipEntry.cpp
new file mode 100644
index 0000000..bed0333
--- /dev/null
+++ b/tools/zipalign/ZipEntry.cpp
@@ -0,0 +1,696 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+ bool hasDD;
+
+ //LOGV("initFromCDE ---\n");
+
+ /* read the CDE */
+ result = mCDE.read(fp);
+ if (result != NO_ERROR) {
+ LOGD("mCDE.read failed\n");
+ return result;
+ }
+
+ //mCDE.dump();
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ LOGD("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return UNKNOWN_ERROR;
+ }
+
+ result = mLFH.read(fp);
+ if (result != NO_ERROR) {
+ LOGD("mLFH.read failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ //mLFH.dump();
+
+ /*
+ * We *might* need to read the Data Descriptor at this point and
+ * integrate it into the LFH. If this bit is set, the CRC-32,
+ * compressed size, and uncompressed size will be zero. In practice
+ * these seem to be rare.
+ */
+ hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+ if (hasDD) {
+ // do something clever
+ //LOGD("+++ has data descriptor\n");
+ }
+
+ /*
+ * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
+ * flag is set, because the LFH is incomplete. (Not a problem, since we
+ * prefer the CDE values.)
+ */
+ if (!hasDD && !compareHeaders()) {
+ LOGW("WARNING: header mismatch\n");
+ // keep going?
+ }
+
+ /*
+ * If the mVersionToExtract is greater than 20, we may have an
+ * issue unpacking the record -- could be encrypted, compressed
+ * with something we don't support, or use Zip64 extensions. We
+ * can defer worrying about that to when we're extracting data.
+ */
+
+ return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry. Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+ assert(fileName != NULL && *fileName != '\0'); // name required
+
+ /* most fields are properly initialized by constructor */
+ mCDE.mVersionMadeBy = kDefaultMadeBy;
+ mCDE.mVersionToExtract = kDefaultVersion;
+ mCDE.mCompressionMethod = kCompressStored;
+ mCDE.mFileNameLength = strlen(fileName);
+ if (comment != NULL)
+ mCDE.mFileCommentLength = strlen(comment);
+ mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ strcpy((char*) mCDE.mFileName, fileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ /* TODO: stop assuming null-terminated ASCII here? */
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ strcpy((char*) mCDE.mFileComment, comment);
+ }
+
+ copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* pZipFile,
+ const ZipEntry* pEntry)
+{
+ /*
+ * Copy everything in the CDE over, then fix up the hairy bits.
+ */
+ memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE));
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ if (mCDE.mFileName == NULL)
+ return NO_MEMORY;
+ strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ if (mCDE.mFileComment == NULL)
+ return NO_MEMORY;
+ strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment);
+ }
+ if (mCDE.mExtraFieldLength > 0) {
+ /* we null-terminate this, though it may not be a string */
+ mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1];
+ if (mCDE.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField,
+ mCDE.mExtraFieldLength+1);
+ }
+
+ /* construct the LFH from the CDE */
+ copyCDEtoLFH();
+
+ /*
+ * The LFH "extra" field is independent of the CDE "extra", so we
+ * handle it here.
+ */
+ assert(mLFH.mExtraField == NULL);
+ mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+ if (mLFH.mExtraFieldLength > 0) {
+ mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+ if (mLFH.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+ mLFH.mExtraFieldLength+1);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field. This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+ if (padding <= 0)
+ return INVALID_OPERATION;
+
+ //LOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+ // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+ if (mLFH.mExtraFieldLength > 0) {
+ /* extend existing field */
+ unsigned char* newExtra;
+
+ newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+ if (newExtra == NULL)
+ return NO_MEMORY;
+ memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+ memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+ delete[] mLFH.mExtraField;
+ mLFH.mExtraField = newExtra;
+ mLFH.mExtraFieldLength += padding;
+ } else {
+ /* create new field */
+ mLFH.mExtraField = new unsigned char[padding];
+ memset(mLFH.mExtraField, 0, padding);
+ mLFH.mExtraFieldLength = padding;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+ mLFH.mVersionToExtract = mCDE.mVersionToExtract;
+ mLFH.mGPBitFlag = mCDE.mGPBitFlag;
+ mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+ mLFH.mLastModFileTime = mCDE.mLastModFileTime;
+ mLFH.mLastModFileDate = mCDE.mLastModFileDate;
+ mLFH.mCRC32 = mCDE.mCRC32;
+ mLFH.mCompressedSize = mCDE.mCompressedSize;
+ mLFH.mUncompressedSize = mCDE.mUncompressedSize;
+ mLFH.mFileNameLength = mCDE.mFileNameLength;
+ // the "extra field" is independent
+
+ delete[] mLFH.mFileName;
+ if (mLFH.mFileNameLength > 0) {
+ mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+ strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+ } else {
+ mLFH.mFileName = NULL;
+ }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod)
+{
+ mCDE.mCompressionMethod = compressionMethod;
+ mCDE.mCRC32 = crc32;
+ mCDE.mCompressedSize = compLen;
+ mCDE.mUncompressedSize = uncompLen;
+ mCDE.mCompressionMethod = compressionMethod;
+ if (compressionMethod == kCompressDeflated) {
+ mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
+ }
+ copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up. This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+ if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+ LOGV("cmp: VersionToExtract\n");
+ return false;
+ }
+ if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+ LOGV("cmp: GPBitFlag\n");
+ return false;
+ }
+ if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+ LOGV("cmp: CompressionMethod\n");
+ return false;
+ }
+ if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+ LOGV("cmp: LastModFileTime\n");
+ return false;
+ }
+ if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+ LOGV("cmp: LastModFileDate\n");
+ return false;
+ }
+ if (mCDE.mCRC32 != mLFH.mCRC32) {
+ LOGV("cmp: CRC32\n");
+ return false;
+ }
+ if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+ LOGV("cmp: CompressedSize\n");
+ return false;
+ }
+ if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+ LOGV("cmp: UncompressedSize\n");
+ return false;
+ }
+ if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+ LOGV("cmp: FileNameLength\n");
+ return false;
+ }
+#if 0 // this seems to be used for padding, not real data
+ if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+ LOGV("cmp: ExtraFieldLength\n");
+ return false;
+ }
+#endif
+ if (mCDE.mFileName != NULL) {
+ if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+ LOGV("cmp: FileName\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+ struct tm parts;
+
+ parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+ parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+ parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+ parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+ parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+ parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+ parts.tm_wday = parts.tm_yday = 0;
+ parts.tm_isdst = -1; // DST info "not available"
+
+ return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#ifdef HAVE_LOCALTIME_R
+ struct tm tmResult;
+#endif
+ time_t even;
+ unsigned short zdate, ztime;
+
+ struct tm* ptm;
+
+ /* round up to an even number of seconds */
+ even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+ /* expand */
+#ifdef HAVE_LOCALTIME_R
+ ptm = localtime_r(&even, &tmResult);
+#else
+ ptm = localtime(&even);
+#endif
+
+ int year;
+ year = ptm->tm_year;
+ if (year < 80)
+ year = 80;
+
+ zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+ ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+ mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+ mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kLFHLen];
+
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ LOGD("whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+ // TODO: validate sizes
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* grab extra field */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+ unsigned char buf[kLFHLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+ ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+ LOGD(" LocalFileHeader contents:\n");
+ LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ LOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ LOGD(" filenameLen=%u extraLen=%u\n",
+ mFileNameLength, mExtraFieldLength);
+ if (mFileName != NULL)
+ LOGD(" filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kCDELen];
+
+ /* no re-use */
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+ assert(mFileComment == NULL);
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ LOGD("Whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+ mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+ mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ // TODO: validate sizes and offsets
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* read "extra field" */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+
+ /* grab comment, if any */
+ if (mFileCommentLength != 0) {
+ mFileComment = new unsigned char[mFileCommentLength+1];
+ if (mFileComment == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileComment[mFileCommentLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+ unsigned char buf[kCDELen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+ ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x10], mCRC32);
+ ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+ ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+ ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+ ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+ ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+ ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write comment */
+ if (mFileCommentLength != 0) {
+ if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+ LOGD(" CentralDirEntry contents:\n");
+ LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ LOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
+ mFileNameLength, mExtraFieldLength, mFileCommentLength);
+ LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+ mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+ mLocalHeaderRelOffset);
+
+ if (mFileName != NULL)
+ LOGD(" filename: '%s'\n", mFileName);
+ if (mFileComment != NULL)
+ LOGD(" comment: '%s'\n", mFileComment);
+}
+
diff --git a/tools/zipalign/ZipEntry.h b/tools/zipalign/ZipEntry.h
new file mode 100644
index 0000000..7f721b4
--- /dev/null
+++ b/tools/zipalign/ZipEntry.h
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/zipalign/ZipFile.cpp b/tools/zipalign/ZipFile.cpp
new file mode 100644
index 0000000..62c9383
--- /dev/null
+++ b/tools/zipalign/ZipFile.cpp
@@ -0,0 +1,1297 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <utils/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO "rb"
+#define FILE_OPEN_RW "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+ if (err == ENOENT)
+ return NAME_NOT_FOUND;
+ else if (err == EACCES)
+ return PERMISSION_DENIED;
+ else
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+ bool newArchive = false;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ if (flags & kOpenTruncate) {
+ newArchive = true;
+ } else {
+ newArchive = (access(zipFileName, F_OK) != 0);
+ if (!(flags & kOpenCreate) && newArchive) {
+ /* not creating, must already exist */
+ LOGD("File %s does not exist", zipFileName);
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fopen(zipFileName, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ LOGD("fopen failed: %d\n", err);
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= (int) mEntries.size())
+ return NULL;
+
+ return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+ /*
+ * Do a stupid linear string-compare search.
+ *
+ * There are various ways to speed this up, especially since it's rare
+ * to intermingle changes to the archive with "get by name" calls. We
+ * don't want to sort the mEntries vector itself, however, because
+ * it's used to recreate the Central Directory.
+ *
+ * (Hash table works, parallel list of pointers in sorted order is good.)
+ */
+ int idx;
+
+ for (idx = mEntries.size()-1; idx >= 0; idx--) {
+ ZipEntry* pEntry = mEntries[idx];
+ if (!pEntry->getDeleted() &&
+ strcmp(fileName, pEntry->getFileName()) == 0)
+ {
+ return pEntry;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+ int count = mEntries.size();
+
+ while (--count >= 0)
+ delete mEntries[count];
+
+ mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+ status_t result = NO_ERROR;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ LOGD("Length is %ld -- too small\n", (long)fileLength);
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ LOGD("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ LOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ LOGD("short file? wanted %ld\n", readAmount);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ LOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ LOGD("EOCD not found, not Zip\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != NO_ERROR) {
+ LOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+ //mEOCD.dump();
+
+ if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+ mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+ {
+ LOGD("Archive spanning not supported\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ LOGD("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initFromCDE(mZipFp);
+ if (result != NO_ERROR) {
+ LOGD("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ mEntries.add(pEntry);
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ {
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ LOGD("EOCD check read failed\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ LOGD("EOCD read check failed\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ LOGV("+++ EOCD read check passed\n");
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position. The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written. Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result = NO_ERROR;
+ long lfhPosn, startPosn, endPosn, uncompressedLen;
+ FILE* inputFp = NULL;
+ unsigned long crc;
+ time_t modWhen;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ assert(compressionMethod == ZipEntry::kCompressDeflated ||
+ compressionMethod == ZipEntry::kCompressStored);
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ /* make sure it doesn't already exist */
+ if (getEntryByName(storageName) != NULL)
+ return ALREADY_EXISTS;
+
+ if (!data) {
+ inputFp = fopen(fileName, FILE_OPEN_RO);
+ if (inputFp == NULL)
+ return errnoToStatus(errno);
+ }
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ pEntry->initNew(storageName, NULL);
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH, even though it's still mostly blank. We need it
+ * as a place-holder. In theory the LFH isn't necessary, but in
+ * practice some utilities demand it.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+ startPosn = ftell(mZipFp);
+
+ /*
+ * Copy the data in, possibly compressing it as we go.
+ */
+ if (sourceType == ZipEntry::kCompressStored) {
+ if (compressionMethod == ZipEntry::kCompressDeflated) {
+ bool failed = false;
+ result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+ if (result != NO_ERROR) {
+ LOGD("compression failed, storing\n");
+ failed = true;
+ } else {
+ /*
+ * Make sure it has compressed "enough". This probably ought
+ * to be set through an API call, but I don't expect our
+ * criteria to change over time.
+ */
+ long src = inputFp ? ftell(inputFp) : size;
+ long dst = ftell(mZipFp) - startPosn;
+ if (dst + (dst / 10) > src) {
+ LOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+ src, dst);
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ compressionMethod = ZipEntry::kCompressStored;
+ if (inputFp) rewind(inputFp);
+ fseek(mZipFp, startPosn, SEEK_SET);
+ /* fall through to kCompressStored case */
+ }
+ }
+ /* handle "no compression" request, or failed compression from above */
+ if (compressionMethod == ZipEntry::kCompressStored) {
+ if (inputFp) {
+ result = copyFpToFp(mZipFp, inputFp, &crc);
+ } else {
+ result = copyDataToFp(mZipFp, data, size, &crc);
+ }
+ if (result != NO_ERROR) {
+ // don't need to truncate; happens in CDE rewrite
+ LOGD("failed copying data in\n");
+ goto bail;
+ }
+ }
+
+ // currently seeked to end of file
+ uncompressedLen = inputFp ? ftell(inputFp) : size;
+ } else if (sourceType == ZipEntry::kCompressDeflated) {
+ /* we should support uncompressed-from-compressed, but it's not
+ * important right now */
+ assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+ bool scanResult;
+ int method;
+ long compressedLen;
+
+ scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+ &compressedLen, &crc);
+ if (!scanResult || method != ZipEntry::kCompressDeflated) {
+ LOGD("this isn't a deflated gzip file?");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+ if (result != NO_ERROR) {
+ LOGD("failed copying gzip data in\n");
+ goto bail;
+ }
+ } else {
+ assert(false);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * We could write the "Data Descriptor", but there doesn't seem to
+ * be any point since we're going to go back and write the LFH.
+ *
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp); // seeked to end of compressed data
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+ compressionMethod);
+ modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+ pEntry->setModWhen(modWhen);
+ pEntry->setLFHOffset(lfhPosn);
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Go back and write the LFH.
+ */
+ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.add(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+bail:
+ if (inputFp != NULL)
+ fclose(inputFp);
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result;
+ long lfhPosn, endPosn;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ if (pEntry == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
+ if (result != NO_ERROR)
+ goto bail;
+ if (padding != 0) {
+ result = pEntry->addPadding(padding);
+ if (result != NO_ERROR)
+ goto bail;
+ }
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH. Since we're not recompressing the data, we already
+ * have all of the fields filled out.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Copy the data over.
+ *
+ * If the "has data descriptor" flag is set, we want to copy the DD
+ * fields as well. This is a fixed-size area immediately following
+ * the data.
+ */
+ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ off_t copyLen;
+ copyLen = pSourceEntry->getCompressedLen();
+ if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+ copyLen += ZipEntry::kDataDescriptorLen;
+
+ if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+ != NO_ERROR)
+ {
+ LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp);
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.add(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+ result = NO_ERROR;
+
+bail:
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (1) {
+ count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+ if (ferror(srcFp) || ferror(dstFp))
+ return errnoToStatus(errno);
+ if (count == 0)
+ break;
+
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ LOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+ if (size > 0) {
+ *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+ if (fwrite(data, 1, size, dstFp) != size) {
+ LOGD("fwrite %d bytes failed\n", (int) size);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (length) {
+ long readSize;
+
+ readSize = sizeof(tmpBuf);
+ if (readSize > length)
+ readSize = length;
+
+ count = fread(tmpBuf, 1, readSize, srcFp);
+ if ((long) count != readSize) { // error or unexpected EOF
+ LOGD("fread %d bytes failed\n", (int) readSize);
+ return UNKNOWN_ERROR;
+ }
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ LOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+
+ length -= readSize;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ status_t result = NO_ERROR;
+ const size_t kBufSize = 32768;
+ unsigned char* inBuf = NULL;
+ unsigned char* outBuf = NULL;
+ z_stream zstream;
+ bool atEof = false; // no feof() aviailable yet
+ unsigned long crc;
+ int zerr;
+
+ /*
+ * Create an input buffer and an output buffer.
+ */
+ inBuf = new unsigned char[kBufSize];
+ outBuf = new unsigned char[kBufSize];
+ if (inBuf == NULL || outBuf == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ zstream.data_type = Z_UNKNOWN;
+
+ zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) {
+ result = UNKNOWN_ERROR;
+ if (zerr == Z_VERSION_ERROR) {
+ LOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ size_t getSize;
+ int flush;
+
+ /* only read if the input buffer is empty */
+ if (zstream.avail_in == 0 && !atEof) {
+ LOGV("+++ reading %d bytes\n", (int)kBufSize);
+ if (data) {
+ getSize = size > kBufSize ? kBufSize : size;
+ memcpy(inBuf, data, getSize);
+ data = ((const char*)data) + getSize;
+ size -= getSize;
+ } else {
+ getSize = fread(inBuf, 1, kBufSize, srcFp);
+ if (ferror(srcFp)) {
+ LOGD("deflate read failed (errno=%d)\n", errno);
+ goto z_bail;
+ }
+ }
+ if (getSize < kBufSize) {
+ LOGV("+++ got %d bytes, EOF reached\n",
+ (int)getSize);
+ atEof = true;
+ }
+
+ crc = crc32(crc, inBuf, getSize);
+
+ zstream.next_in = inBuf;
+ zstream.avail_in = getSize;
+ }
+
+ if (atEof)
+ flush = Z_FINISH; /* tell zlib that we're done */
+ else
+ flush = Z_NO_FLUSH; /* more to come! */
+
+ zerr = deflate(&zstream, flush);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ LOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+ result = UNKNOWN_ERROR;
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+ {
+ LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+ if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+ (size_t)(zstream.next_out - outBuf))
+ {
+ LOGD("write %d failed in deflate\n",
+ (int) (zstream.next_out - outBuf));
+ goto z_bail;
+ }
+
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ *pCRC32 = crc;
+
+z_bail:
+ deflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] inBuf;
+ delete[] outBuf;
+
+ return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+ /*
+ * Should verify that pEntry is actually part of this archive, and
+ * not some stray ZipEntry from a different file.
+ */
+
+ /* mark entry as deleted, and mark archive as dirty */
+ pEntry->setDeleted();
+ mNeedCDRewrite = true;
+ return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+ status_t result = NO_ERROR;
+ long eocdPosn;
+ int i, count;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+ if (!mNeedCDRewrite)
+ return NO_ERROR;
+
+ assert(mZipFp != NULL);
+
+ result = crunchArchive();
+ if (result != NO_ERROR)
+ return result;
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ count = mEntries.size();
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ pEntry->mCDE.write(mZipFp);
+ }
+
+ eocdPosn = ftell(mZipFp);
+ mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+ mEOCD.write(mZipFp);
+
+ /*
+ * If we had some stuff bloat up during compression and get replaced
+ * with plain files, or if we deleted some entries, there's a lot
+ * of wasted space at the end of the file. Remove it now.
+ */
+ if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+ LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+ // not fatal
+ }
+
+ /* should we clear the "newly added" flag in all entries now? */
+
+ mNeedCDRewrite = false;
+ return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+ status_t result = NO_ERROR;
+ int i, count;
+ long delCount, adjust;
+
+#if 0
+ printf("CONTENTS:\n");
+ for (i = 0; i < (int) mEntries.size(); i++) {
+ printf(" %d: lfhOff=%ld del=%d\n",
+ i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+ }
+ printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+ /*
+ * Roll through the set of files, shifting them as appropriate. We
+ * could probably get a slight performance improvement by sliding
+ * multiple files down at once (because we could use larger reads
+ * when operating on batches of small files), but it's not that useful.
+ */
+ count = mEntries.size();
+ delCount = adjust = 0;
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ long span;
+
+ if (pEntry->getLFHOffset() != 0) {
+ long nextOffset;
+
+ /* Get the length of this entry by finding the offset
+ * of the next entry. Directory entries don't have
+ * file offsets, so we need to find the next non-directory
+ * entry.
+ */
+ nextOffset = 0;
+ for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+ nextOffset = mEntries[ii]->getLFHOffset();
+ if (nextOffset == 0)
+ nextOffset = mEOCD.mCentralDirOffset;
+ span = nextOffset - pEntry->getLFHOffset();
+
+ assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+ } else {
+ /* This is a directory entry. It doesn't have
+ * any actual file contents, so there's no need to
+ * move anything.
+ */
+ span = 0;
+ }
+
+ //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+ // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+ if (pEntry->getDeleted()) {
+ adjust += span;
+ delCount++;
+
+ delete pEntry;
+ mEntries.removeAt(i);
+
+ /* adjust loop control */
+ count--;
+ i--;
+ } else if (span != 0 && adjust > 0) {
+ /* shuffle this entry back */
+ //printf("+++ Shuffling '%s' back %ld\n",
+ // pEntry->getFileName(), adjust);
+ result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+ pEntry->getLFHOffset(), span);
+ if (result != NO_ERROR) {
+ /* this is why you use a temp file */
+ LOGE("error during crunch - archive is toast\n");
+ return result;
+ }
+
+ pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+ }
+ }
+
+ /*
+ * Fix EOCD info. We have to wait until the end to do some of this
+ * because we use mCentralDirOffset to determine "span" for the
+ * last entry.
+ */
+ mEOCD.mCentralDirOffset -= adjust;
+ mEOCD.mNumEntries -= delCount;
+ mEOCD.mTotalNumEntries -= delCount;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+
+ assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+ assert(mEOCD.mNumEntries == count);
+
+ return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+ if (dst == src || n <= 0)
+ return NO_ERROR;
+
+ unsigned char readBuf[32768];
+
+ if (dst < src) {
+ /* shift stuff toward start of file; must read from start */
+ while (n != 0) {
+ size_t getSize = sizeof(readBuf);
+ if (getSize > n)
+ getSize = n;
+
+ if (fseek(fp, (long) src, SEEK_SET) != 0) {
+ LOGD("filemove src seek %ld failed\n", (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fread(readBuf, 1, getSize, fp) != getSize) {
+ LOGD("filemove read %ld off=%ld failed\n",
+ (long) getSize, (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+ LOGD("filemove dst seek %ld failed\n", (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+ LOGD("filemove write %ld off=%ld failed\n",
+ (long) getSize, (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ src += getSize;
+ dst += getSize;
+ n -= getSize;
+ }
+ } else {
+ /* shift stuff toward end of file; must read from end */
+ assert(false); // write this someday, maybe
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+ struct stat sb;
+
+ if (fstat(fd, &sb) < 0) {
+ LOGD("HEY: fstat on fd %d failed\n", fd);
+ return (time_t) -1;
+ }
+
+ return sb.st_mtime;
+}
+
+
+#if 0 /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush(). The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+ if (!mReadOnly)
+ return INVALID_OPERATION;
+ assert(mZipFp != NULL);
+
+ int fd;
+ fd = dup(fileno(mZipFp));
+ if (fd < 0) {
+ LOGD("didn't work, errno=%d\n", errno);
+ }
+
+ return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+ return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+ size_t unlen = entry->getUncompressedLen();
+ size_t clen = entry->getCompressedLen();
+
+ void* buf = malloc(unlen);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ fseek(mZipFp, 0, SEEK_SET);
+
+ off_t offset = entry->getFileOffset();
+ if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+ goto bail;
+ }
+
+ switch (entry->getCompressionMethod())
+ {
+ case ZipEntry::kCompressStored: {
+ ssize_t amt = fread(buf, 1, unlen, mZipFp);
+ if (amt != (ssize_t)unlen) {
+ goto bail;
+ }
+#if 0
+ printf("data...\n");
+ const unsigned char* p = (unsigned char*)buf;
+ const unsigned char* end = p+unlen;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%08x ", (int)(offset+(i*0x10)));
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ }
+ break;
+ case ZipEntry::kCompressDeflated: {
+ if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+ goto bail;
+ }
+ }
+ break;
+ default:
+ goto bail;
+ }
+ return buf;
+
+bail:
+ free(buf);
+ return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ /* don't allow re-use */
+ assert(mComment == NULL);
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ LOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return INVALID_OPERATION;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return UNKNOWN_ERROR;
+
+ mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+ mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+ // TODO: validate mCentralDirOffset
+
+ if (mCommentLen > 0) {
+ if (kEOCDLen + mCommentLen > len) {
+ LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ kEOCDLen, mCommentLen, len);
+ return UNKNOWN_ERROR;
+ }
+ mComment = new unsigned char[mCommentLen];
+ memcpy(mComment, buf + kEOCDLen, mCommentLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+ unsigned char buf[kEOCDLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+ ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+ ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+ ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+ ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+ ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+ ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+ if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+ return UNKNOWN_ERROR;
+ if (mCommentLen > 0) {
+ assert(mComment != NULL);
+ if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+ LOGD(" EndOfCentralDir contents:\n");
+ LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+ mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+ LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+ mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
diff --git a/tools/zipalign/ZipFile.h b/tools/zipalign/ZipFile.h
new file mode 100644
index 0000000..dbbd072
--- /dev/null
+++ b/tools/zipalign/ZipFile.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <utils/Vector.h>
+#include <utils/Errors.h>
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ typedef enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ Vector<ZipEntry*> mEntries;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H