Remove changing uids/timestamps from zip/jar files

Pass -X to zip so that Unix UID/GID and extra timestamps aren't
saved into the zip files.

Add a new option to zipalign, -t, to replace all timestamps with static
timestamps (2008 Jan 1 00:00:00). Use this for all non-APK zip files.
APK zip timestamps are set based on the certificate date in SignApk.

Bug: 24201956
Change-Id: Ifb619fc499ba9d99fc624f2acd5f8de36d78ef8e
diff --git a/core/Makefile b/core/Makefile
index d94318d..4f219be 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -666,12 +666,18 @@
 #
 # This rule adds to ALL_DEFAULT_INSTALLED_MODULES, so it needs to come
 # before the rules that use that variable to build the image.
-ALL_DEFAULT_INSTALLED_MODULES += $(TARGET_OUT_ETC)/security/otacerts.zip
-$(TARGET_OUT_ETC)/security/otacerts.zip: KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
-$(TARGET_OUT_ETC)/security/otacerts.zip: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR))
+OTACERTS_ZIP_BUILT := $(call intermediates-dir-for,PACKAGING,otacerts)/otacerts.zip
+OTACERTS_ZIP := $(TARGET_OUT_ETC)/security/otacerts.zip
+ALL_DEFAULT_INSTALLED_MODULES += $(OTACERTS_ZIP)
+
+$(OTACERTS_ZIP_BUILT): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
+$(OTACERTS_ZIP_BUILT): $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR)) | $(ZIPALIGN)
 	$(hide) rm -f $@
 	$(hide) mkdir -p $(dir $@)
-	$(hide) zip -qj $@ $<
+	$(hide) zip -qjX $@ $<
+	$(remove-timestamps-from-package)
+
+$(eval $(call copy-one-file,$(OTACERTS_ZIP_BUILT),$(OTACERTS_ZIP)))
 
 .PHONY: otacerts
 otacerts: $(TARGET_OUT_ETC)/security/otacerts.zip
@@ -928,9 +934,13 @@
 		$(call build-recoveryimage-target, $@)
 
 ifneq ($(BOARD_USES_FULL_RECOVERY_IMAGE),true)
-$(RECOVERY_RESOURCE_ZIP): $(INSTALLED_RECOVERYIMAGE_TARGET)
+RECOVERY_RESOURCE_ZIP_BUILT := $(call intermediates-dir-for,PACKAGING,recovery_resource)/recovery_resource.zip
+$(RECOVERY_RESOURCE_ZIP_BUILT): $(INSTALLED_RECOVERYIMAGE_TARGET) | $(ZIPALIGN)
 	$(hide) mkdir -p $(dir $@)
-	$(hide) find $(TARGET_RECOVERY_ROOT_OUT)/res -type f | sort | zip -0qrj $@ -@
+	$(hide) find $(TARGET_RECOVERY_ROOT_OUT)/res -type f | sort | zip -0qrjX $@ -@
+	$(remove-timestamps-from-package)
+
+$(eval $(call copy-one-file,$(RECOVERY_RESOURCE_ZIP_BUILT),$(RECOVERY_RESOURCE_ZIP)))
 endif
 
 .PHONY: recoveryimage-nodeps
@@ -1121,19 +1131,19 @@
 $(INSTALLED_PLATFORM_ZIP) : $(INTERNAL_SYSTEMIMAGE_FILES)
 	$(call pretty,"Platform zip package: $(INSTALLED_PLATFORM_ZIP)")
 	$(hide) rm -f $@
-	$(hide) cd $(dir $@) && zip -qry $(notdir $@) \
+	$(hide) cd $(dir $@) && zip -qryX $(notdir $@) \
 		$(TARGET_COPY_OUT_SYSTEM) \
 		$(patsubst $(PRODUCT_OUT)/%, %, $(TARGET_OUT_NOTICE_FILES)) \
 		$(addprefix symbols/,$(PDK_SYMBOL_FILES_LIST))
 ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE
-	$(hide) cd $(dir $@) && zip -qry $(notdir $@) \
+	$(hide) cd $(dir $@) && zip -qryX $(notdir $@) \
 		$(TARGET_COPY_OUT_VENDOR)
 endif
 ifneq ($(PDK_PLATFORM_JAVA_ZIP_CONTENTS),)
-	$(hide) cd $(OUT_DIR) && zip -qry $(patsubst $(OUT_DIR)/%,%,$@) $(PDK_PLATFORM_JAVA_ZIP_CONTENTS)
+	$(hide) cd $(OUT_DIR) && zip -qryX $(patsubst $(OUT_DIR)/%,%,$@) $(PDK_PLATFORM_JAVA_ZIP_CONTENTS)
 endif
 ifneq ($(PDK_PLATFORM_ZIP_PRODUCT_BINARIES),)
-	$(hide) zip -qry $@ $(PDK_PLATFORM_ZIP_PRODUCT_BINARIES)
+	$(hide) zip -qryX $@ $(PDK_PLATFORM_ZIP_PRODUCT_BINARIES)
 endif
 
 .PHONY: platform
@@ -1402,9 +1412,9 @@
 	$(hide) $(ACP) -p system/extras/verity/build_verity_metadata.py $(zip_root)/system/extras/verity/
 	$(hide) $(ACP) -r -d -p build/tools/releasetools/* $(zip_root)/releasetools
 	$(hide) rm -rf $@ $(zip_root)/releasetools/*.pyc
-	$(hide) (cd $(zip_root) && zip -qry $(abspath $@) *)
-	$(hide) zip -qry $(abspath $@) build/target/product/security/
-	$(hide) find device vendor -name \*.pk8 -o -name \*.x509.pem -o -name oem.prop | xargs zip -qry $(abspath $@)>/dev/null || true
+	$(hide) (cd $(zip_root) && zip -qryX $(abspath $@) *)
+	$(hide) zip -qryX $(abspath $@) build/target/product/security/
+	$(hide) find device vendor -name \*.pk8 -o -name \*.x509.pem -o -name oem.prop | xargs zip -qryX $(abspath $@)>/dev/null || true
 
 .PHONY: otatools-package
 otatools-package: $(BUILT_OTATOOLS_PACKAGE)
@@ -1626,7 +1636,7 @@
 	$(hide) $(ACP) -r $(TARGET_OUT_BREAKPAD) $(zip_root)/BREAKPAD
 endif
 	@# Zip everything up, preserving symlinks
-	$(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
+	$(hide) (cd $(zip_root) && zip -qryX ../$(notdir $@) .)
 	@# Run fs_config on all the system, vendor, boot ramdisk,
 	@# and recovery ramdisk files in the zip, and save the output
 	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt
@@ -1639,7 +1649,7 @@
 ifneq ($(INSTALLED_RECOVERYIMAGE_TARGET),)
 	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt
 endif
-	$(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt)
+	$(hide) (cd $(zip_root) && zip -qX ../$(notdir $@) META/*filesystem_config.txt)
 	$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
 	    ./build/tools/releasetools/add_img_to_target_files -v -p $(HOST_OUT) $@
 
@@ -1719,7 +1729,7 @@
 	@echo "Package symbols: $@"
 	$(hide) rm -rf $@
 	$(hide) mkdir -p $(dir $@) $(TARGET_OUT_UNSTRIPPED)
-	$(hide) zip -qr $@ $(TARGET_OUT_UNSTRIPPED)
+	$(hide) zip -qrX $@ $(TARGET_OUT_UNSTRIPPED)
 
 # -----------------------------------------------------------------
 # A zip of the Android Apps. Not keeping full path so that we don't
@@ -1736,7 +1746,7 @@
 	@echo "Package apps: $@"
 	$(hide) rm -rf $@
 	$(hide) mkdir -p $(dir $@)
-	$(hide) zip -qj $@ $(TARGET_OUT_APPS)/*/*.apk $(TARGET_OUT_APPS_PRIVILEGED)/*/*.apk
+	$(hide) zip -qjX $@ $(TARGET_OUT_APPS)/*/*.apk $(TARGET_OUT_APPS_PRIVILEGED)/*/*.apk
 
 
 #------------------------------------------------------------------
@@ -1749,7 +1759,7 @@
 $(EMMA_META_ZIP) :
 	@echo "Collecting Emma coverage meta files."
 	$(hide) find $(TARGET_COMMON_OUT_ROOT) $(HOST_COMMON_OUT_ROOT) -name "coverage.em" | \
-		zip -@ -q $@
+		zip -@ -qX $@
 
 endif # EMMA_INSTRUMENT=true
 
@@ -1765,7 +1775,7 @@
 	$(hide) dict_files=`find $(TARGET_OUT_COMMON_INTERMEDIATES)/APPS -name proguard_dictionary`; \
 		if [ -n "$$dict_files" ]; then \
 		  unobfuscated_jars=$${dict_files//proguard_dictionary/classes.jar}; \
-		  zip -q $@ $$dict_files $$unobfuscated_jars; \
+		  zip -qX $@ $$dict_files $$unobfuscated_jars; \
 		else \
 		  touch $(dir $@)/zipdummy; \
 		  (cd $(dir $@) && zip -q $(notdir $@) zipdummy); \
@@ -1796,7 +1806,7 @@
 
 $(INTERNAL_EMULATOR_PACKAGE_TARGET): $(INTERNAL_EMULATOR_PACKAGE_FILES)
 	@echo "Package: $@"
-	$(hide) zip -qj $@ $(INTERNAL_EMULATOR_PACKAGE_FILES)
+	$(hide) zip -qjX $@ $(INTERNAL_EMULATOR_PACKAGE_FILES)
 
 endif
 # -----------------------------------------------------------------
@@ -1931,7 +1941,7 @@
 		HOST_OUT_EXECUTABLES=$(HOST_OUT_EXECUTABLES) HOST_OS=$(HOST_OS) \
 			development/build/tools/sdk_clean.sh $(PRIVATE_DIR) && \
 		chmod -R ug+rwX $(PRIVATE_DIR) && \
-		cd $(dir $@) && zip -rq $(notdir $@) $(PRIVATE_NAME) \
+		cd $(dir $@) && zip -rqX $(notdir $@) $(PRIVATE_NAME) \
 	) || ( rm -rf $(PRIVATE_DIR) $@ && exit 44 )
 
 
diff --git a/core/definitions.mk b/core/definitions.mk
index ecc7253..b450394 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -2094,13 +2094,13 @@
 $(foreach abi,$(PRIVATE_JNI_SHARED_LIBRARIES_ABI),\
   $(call _add-jni-shared-libs-to-package-per-abi,$(abi),\
     $(patsubst $(abi):%,%,$(filter $(abi):%,$(PRIVATE_JNI_SHARED_LIBRARIES)))))
-$(hide) (cd $(dir $@) && zip -qr $(JNI_COMPRESS_FLAGS) $(notdir $@) lib)
+$(hide) (cd $(dir $@) && zip -qrX $(JNI_COMPRESS_FLAGS) $(notdir $@) lib)
 $(hide) rm -rf $(dir $@)lib
 endef
 
 #TODO: update the manifest to point to the dex file
 define add-dex-to-package
-$(hide) zip -qj $@ $(dir $(PRIVATE_DEX_FILE))classes*.dex
+$(hide) zip -qjX $@ $(dir $(PRIVATE_DEX_FILE))classes*.dex
 endef
 
 # Add java resources added by the current module.
@@ -2147,6 +2147,17 @@
 $(hide) mv $@.aligned $@
 endef
 
+# Remove dynamic timestamps from packages
+#
+define remove-timestamps-from-package
+$(hide) mv $@ $@.timed
+$(hide) $(ZIPALIGN) \
+    -f -t \
+    1 \
+    $@.timed $@.untimed
+$(hide) mv $@.untimed $@
+endef
+
 # Uncompress shared libraries embedded in an apk.
 #
 define uncompress-shared-libs
@@ -2154,7 +2165,7 @@
   rm -rf $(dir $@)uncompressedlibs && mkdir $(dir $@)uncompressedlibs; \
   unzip $@ $(PRIVATE_EMBEDDED_JNI_LIBS) -d $(dir $@)uncompressedlibs && \
   zip -d $@ 'lib/*.so' && \
-  ( cd $(dir $@)uncompressedlibs && zip -D -r -0 ../$(notdir $@) lib ) && \
+  ( cd $(dir $@)uncompressedlibs && zip -D -r -X -0 ../$(notdir $@) lib ) && \
   rm -rf $(dir $@)uncompressedlibs; \
   fi
 endef
diff --git a/core/droiddoc.mk b/core/droiddoc.mk
index cc2a915..74f7d8b 100644
--- a/core/droiddoc.mk
+++ b/core/droiddoc.mk
@@ -241,7 +241,7 @@
 	@echo Package docs: $@
 	@rm -f $@
 	@mkdir -p $(dir $@)
-	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_DOCS_DIR) && zip -rq $$F * )
+	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_DOCS_DIR) && zip -rqX $$F * )
 
 $(LOCAL_MODULE)-docs.zip : $(out_zip)
 
diff --git a/core/java_library.mk b/core/java_library.mk
index 5a2d19b..0ce7dfe 100644
--- a/core/java_library.mk
+++ b/core/java_library.mk
@@ -81,7 +81,7 @@
 $(common_javalib.jar): PRIVATE_DEX_FILE := $(built_dex)
 $(common_javalib.jar): PRIVATE_SOURCE_ARCHIVE := $(full_classes_jarjar_jar)
 $(common_javalib.jar): PRIVATE_DONT_DELETE_JAR_DIRS := $(LOCAL_DONT_DELETE_JAR_DIRS)
-$(common_javalib.jar) : $(built_dex) $(java_resource_sources)
+$(common_javalib.jar) : $(built_dex) $(java_resource_sources) | $(ZIPALIGN)
 	@echo "target Jar: $(PRIVATE_MODULE) ($@)"
 ifdef LOCAL_JACK_ENABLED
 	$(create-empty-package)
@@ -92,6 +92,7 @@
 ifdef LOCAL_JACK_ENABLED
 	$(add-carried-jack-resources)
 endif
+	$(remove-timestamps-from-package)
 
 ifdef LOCAL_DEX_PREOPT
 ifneq ($(dexpreopt_boot_jar_module),) # boot jar
diff --git a/core/tasks/cts.mk b/core/tasks/cts.mk
index 56a7f6f..38f1301 100644
--- a/core/tasks/cts.mk
+++ b/core/tasks/cts.mk
@@ -387,7 +387,7 @@
 $(INTERNAL_CTS_TARGET): TMP_DIR := $(cts_dir)/temp
 $(INTERNAL_CTS_TARGET): $(cts_dir)/all_cts_files_stamp $(DEFAULT_TEST_PLAN)
 	$(hide) echo "Package CTS: $@"
-	$(hide) cd $(dir $@) && zip -rq $(notdir $@) $(PRIVATE_NAME)
+	$(hide) cd $(dir $@) && zip -rqX $(notdir $@) $(PRIVATE_NAME)
 
 .PHONY: cts
 cts: $(INTERNAL_CTS_TARGET) adb
diff --git a/core/tasks/sdk-addon.mk b/core/tasks/sdk-addon.mk
index 5ac9b7d..362b229 100644
--- a/core/tasks/sdk-addon.mk
+++ b/core/tasks/sdk-addon.mk
@@ -111,13 +111,13 @@
 	    $(ACP) -r $$d $(PRIVATE_STAGING_DIR)/docs ;\
 	  done
 	$(hide) mkdir -p $(dir $@)
-	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_STAGING_DIR)/.. && zip -rq $$F $(notdir $(PRIVATE_STAGING_DIR)) )
+	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_STAGING_DIR)/.. && zip -rqX $$F $(notdir $(PRIVATE_STAGING_DIR)) )
 
 $(full_target_img): PRIVATE_STAGING_DIR := $(call append-path,$(staging),$(addon_dir_img))/images/$(TARGET_CPU_ABI)
 $(full_target_img): $(full_target) $(addon_img_source_prop)
 	@echo Packaging SDK Addon System-Image: $@
 	$(hide) mkdir -p $(dir $@)
-	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_STAGING_DIR)/.. && zip -rq $$F $(notdir $(PRIVATE_STAGING_DIR)) )
+	$(hide) ( F=$$(pwd)/$@ ; cd $(PRIVATE_STAGING_DIR)/.. && zip -rqX $$F $(notdir $(PRIVATE_STAGING_DIR)) )
 
 
 .PHONY: sdk_addon
diff --git a/core/tasks/tools/package-modules.mk b/core/tasks/tools/package-modules.mk
index a70e644..24a7608 100644
--- a/core/tasks/tools/package-modules.mk
+++ b/core/tasks/tools/package-modules.mk
@@ -59,4 +59,4 @@
 	$(call copy-tests-in-batch,$(wordlist 1201,9999,$(PRIVATE_COPY_PAIRS)))
 	$(hide) $(foreach f, $(PRIVATE_PICKUP_FILES),\
 	  cp -RfL $(f) $(dir $@);)
-	$(hide) cd $(dir $@) && zip -rq $(notdir $@) *
+	$(hide) cd $(dir $@) && zip -rqX $(notdir $@) *
diff --git a/tools/zipalign/README.txt b/tools/zipalign/README.txt
index 0b80b35..1cdf612 100644
--- a/tools/zipalign/README.txt
+++ b/tools/zipalign/README.txt
@@ -6,6 +6,7 @@
   -c : check alignment only (does not modify file)
   -f : overwrite existing outfile.zip
   -p : page align stored shared object files
+  -t : remove dynamic timestamps
   -v : verbose output
   <align> is in bytes, e.g. "4" provides 32-bit alignment
   infile.zip is an existing Zip archive
@@ -38,3 +39,5 @@
 uncompressed in the zip archive, to a 4096-byte page boundary.  This
 facilitates directly loading shared libraries from inside a zip archive.
 
+The "-t" flag removes all timestamps that could change from the archive.
+
diff --git a/tools/zipalign/ZipAlign.cpp b/tools/zipalign/ZipAlign.cpp
index a2dfd02..2c81a03 100644
--- a/tools/zipalign/ZipAlign.cpp
+++ b/tools/zipalign/ZipAlign.cpp
@@ -39,6 +39,7 @@
     fprintf(stderr, "  -c: check alignment only (does not modify file)\n");
     fprintf(stderr, "  -f: overwrite existing outfile.zip\n");
     fprintf(stderr, "  -p: page align stored shared object files\n");
+    fprintf(stderr, "  -t: remove dynamic timestamps\n");
     fprintf(stderr, "  -v: verbose output\n");
     fprintf(stderr, "  -z: recompress using Zopfli\n");
 }
@@ -64,7 +65,7 @@
  * Copy all entries from "pZin" to "pZout", aligning as needed.
  */
 static int copyAndAlign(ZipFile* pZin, ZipFile* pZout, int alignment, bool zopfli,
-    bool pageAlignSharedLibs)
+    bool pageAlignSharedLibs, bool removeTime)
 {
     int numEntries = pZin->getNumEntries();
     ZipEntry* pEntry;
@@ -88,10 +89,10 @@
             //    (long) pEntry->getUncompressedLen());
 
             if (zopfli) {
-                status = pZout->addRecompress(pZin, pEntry, &pNewEntry);
+                status = pZout->addRecompress(pZin, pEntry, removeTime, &pNewEntry);
                 bias += pNewEntry->getCompressedLen() - pEntry->getCompressedLen();
             } else {
-                status = pZout->add(pZin, pEntry, padding, &pNewEntry);
+                status = pZout->add(pZin, pEntry, padding, removeTime, &pNewEntry);
             }
         } else {
             const int alignTo = getAlignment(pageAlignSharedLibs, alignment, pEntry);
@@ -107,7 +108,7 @@
             //printf("--- %s: orig at %ld(+%d) len=%ld, adding pad=%d\n",
             //    pEntry->getFileName(), (long) pEntry->getFileOffset(),
             //    bias, (long) pEntry->getUncompressedLen(), padding);
-            status = pZout->add(pZin, pEntry, padding, &pNewEntry);
+            status = pZout->add(pZin, pEntry, padding, removeTime, &pNewEntry);
         }
 
         if (status != NO_ERROR)
@@ -126,7 +127,8 @@
  * output file exists and "force" wasn't specified.
  */
 static int process(const char* inFileName, const char* outFileName,
-    int alignment, bool force, bool zopfli, bool pageAlignSharedLibs)
+    int alignment, bool force, bool zopfli, bool pageAlignSharedLibs,
+    bool removeTime)
 {
     ZipFile zin, zout;
 
@@ -157,7 +159,7 @@
         return 1;
     }
 
-    int result = copyAndAlign(&zin, &zout, alignment, zopfli, pageAlignSharedLibs);
+    int result = copyAndAlign(&zin, &zout, alignment, zopfli, pageAlignSharedLibs, removeTime);
     if (result != 0) {
         printf("zipalign: failed rewriting '%s' to '%s'\n",
             inFileName, outFileName);
@@ -228,6 +230,7 @@
     bool verbose = false;
     bool zopfli = false;
     bool pageAlignSharedLibs = false;
+    bool removeTime = false;
     int result = 1;
     int alignment;
     char* endp;
@@ -260,6 +263,9 @@
             case 'p':
                 pageAlignSharedLibs = true;
                 break;
+            case 't':
+                removeTime = true;
+                break;
             default:
                 fprintf(stderr, "ERROR: unknown flag -%c\n", *cp);
                 wantUsage = true;
@@ -290,7 +296,7 @@
         result = verify(argv[1], alignment, verbose, pageAlignSharedLibs);
     } else {
         /* create the new archive */
-        result = process(argv[1], argv[2], alignment, force, zopfli, pageAlignSharedLibs);
+        result = process(argv[1], argv[2], alignment, force, zopfli, pageAlignSharedLibs, removeTime);
 
         /* trust, but verify */
         if (result == 0) {
diff --git a/tools/zipalign/ZipEntry.cpp b/tools/zipalign/ZipEntry.cpp
index b2270cb..9347451 100644
--- a/tools/zipalign/ZipEntry.cpp
+++ b/tools/zipalign/ZipEntry.cpp
@@ -386,6 +386,14 @@
     mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
 }
 
+/*
+ * Set static timestamps
+ */
+void ZipEntry::removeTimestamps()
+{
+    mCDE.mLastModFileTime = mLFH.mLastModFileTime = 0;
+    mCDE.mLastModFileDate = mLFH.mLastModFileDate = 28 << 9 | 1 << 5 | 1;
+}
 
 /*
  * ===========================================================================
diff --git a/tools/zipalign/ZipEntry.h b/tools/zipalign/ZipEntry.h
index 7f721b4..0acd051 100644
--- a/tools/zipalign/ZipEntry.h
+++ b/tools/zipalign/ZipEntry.h
@@ -186,6 +186,12 @@
     void setModWhen(time_t when);
 
     /*
+     * Set a static modification date. This only affects the standard
+     * zip modification date, not the universal time extra field.
+     */
+    void removeTimestamps();
+
+    /*
      * Return the offset of the local file header.
      */
     off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
diff --git a/tools/zipalign/ZipFile.cpp b/tools/zipalign/ZipFile.cpp
index 3c5ec15..0ca4d0d 100644
--- a/tools/zipalign/ZipFile.cpp
+++ b/tools/zipalign/ZipFile.cpp
@@ -359,7 +359,7 @@
  */
 status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
     const char* storageName, int sourceType, int compressionMethod,
-    ZipEntry** ppEntry)
+    bool removeTime, ZipEntry** ppEntry)
 {
     ZipEntry* pEntry = NULL;
     status_t result = NO_ERROR;
@@ -499,8 +499,12 @@
      */
     pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
         compressionMethod);
-    modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
-    pEntry->setModWhen(modWhen);
+    if (removeTime) {
+        pEntry->removeTimestamps();
+    } else {
+        modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+        pEntry->setModWhen(modWhen);
+    }
     pEntry->setLFHOffset(lfhPosn);
     mEOCD.mNumEntries++;
     mEOCD.mTotalNumEntries++;
@@ -539,7 +543,7 @@
  * 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)
+    int padding, bool removeTime, ZipEntry** ppEntry)
 {
     ZipEntry* pEntry = NULL;
     status_t result;
@@ -571,6 +575,8 @@
         if (result != NO_ERROR)
             goto bail;
     }
+    if (removeTime)
+        pEntry->removeTimestamps();
 
     /*
      * From here on out, failures are more interesting.
@@ -646,7 +652,7 @@
  * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
  */
 status_t ZipFile::addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-    ZipEntry** ppEntry)
+    bool removeTime, ZipEntry** ppEntry)
 {
     ZipEntry* pEntry = NULL;
     status_t result;
@@ -674,6 +680,9 @@
     if (result != NO_ERROR)
         goto bail;
 
+    if (removeTime)
+        pEntry->removeTimestamps();
+
     /*
      * From here on out, failures are more interesting.
      */
diff --git a/tools/zipalign/ZipFile.h b/tools/zipalign/ZipFile.h
index b99cda5..787576f 100644
--- a/tools/zipalign/ZipFile.h
+++ b/tools/zipalign/ZipFile.h
@@ -77,17 +77,17 @@
      *
      * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
      */
-    status_t add(const char* fileName, int compressionMethod,
+    status_t add(const char* fileName, int compressionMethod, bool removeTime,
         ZipEntry** ppEntry)
     {
-        return add(fileName, fileName, compressionMethod, ppEntry);
+        return add(fileName, fileName, compressionMethod, removeTime, ppEntry);
     }
     status_t add(const char* fileName, const char* storageName,
-        int compressionMethod, ZipEntry** ppEntry)
+        int compressionMethod, bool removeTime, ZipEntry** ppEntry)
     {
         return addCommon(fileName, NULL, 0, storageName,
                          ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
+                         compressionMethod, removeTime, ppEntry);
     }
 
     /*
@@ -96,11 +96,12 @@
      * 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)
+        bool removeTime, ZipEntry** ppEntry)
     {
         return addCommon(fileName, NULL, 0, storageName,
                          ZipEntry::kCompressDeflated,
-                         ZipEntry::kCompressDeflated, ppEntry);
+                         ZipEntry::kCompressDeflated,
+                         removeTime, ppEntry);
     }
 
     /*
@@ -109,11 +110,11 @@
      * 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)
+        int compressionMethod, bool removeTime, ZipEntry** ppEntry)
     {
         return addCommon(NULL, data, size, storageName,
                          ZipEntry::kCompressStored,
-                         compressionMethod, ppEntry);
+                         compressionMethod, removeTime, ppEntry);
     }
 
     /*
@@ -124,7 +125,7 @@
      * 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);
+        int padding, bool removeTime, ZipEntry** ppEntry);
 
     /*
      * Add an entry by copying it from another zip file, recompressing with
@@ -133,7 +134,7 @@
      * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
      */
     status_t addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
-        ZipEntry** ppEntry);
+        bool removeTime, ZipEntry** ppEntry);
 
     /*
      * Mark an entry as having been removed.  It is not actually deleted
@@ -232,7 +233,7 @@
     /* 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);
+        bool removeTime, ZipEntry** ppEntry);
 
     /* copy all of "srcFp" into "dstFp" */
     status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);