am 7c1b98d4: Merge "squashfs_utils: Add host library and parsing from buffer"

* commit '7c1b98d4666f42c238954093f4b946e8e28a84a6':
  squashfs_utils: Add host library and parsing from buffer
diff --git a/ext4_utils/ext4_crypt_init_extensions.cpp b/ext4_utils/ext4_crypt_init_extensions.cpp
index 7ca471a..e1b69e1 100644
--- a/ext4_utils/ext4_crypt_init_extensions.cpp
+++ b/ext4_utils/ext4_crypt_init_extensions.cpp
@@ -26,7 +26,7 @@
     int sock = -1;
 
     while (true) {
-        sock = socket_local_client("vold",
+        sock = socket_local_client("cryptd",
                                    ANDROID_SOCKET_NAMESPACE_RESERVED,
                                    SOCK_STREAM);
         if (sock >= 0) {
@@ -60,18 +60,19 @@
 
     struct pollfd poll_sock = {sock, POLLIN, 0};
 
-    int rc = poll(&poll_sock, 1, vold_command_timeout_ms);
+    int rc = TEMP_FAILURE_RETRY(poll(&poll_sock, 1, vold_command_timeout_ms));
     if (rc < 0) {
         KLOG_ERROR(TAG, "Error in poll %s\n", strerror(errno));
         return "";
     }
+
     if (!(poll_sock.revents & POLLIN)) {
         KLOG_ERROR(TAG, "Timeout\n");
         return "";
     }
     char buffer[4096];
     memset(buffer, 0, sizeof(buffer));
-    rc = read(sock, buffer, sizeof(buffer));
+    rc = TEMP_FAILURE_RETRY(read(sock, buffer, sizeof(buffer)));
     if (rc <= 0) {
         if (rc == 0) {
             KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index 950628c..e39a61f 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -262,13 +262,13 @@
 
     struct f2fs_checkpoint *cp1, *cp2, *cur_cp;
     int cur_cp_no;
-    unsigned long blk_size;// = 1<<le32_to_cpu(info->sb->log_blocksize);
+    unsigned long blk_size;
     unsigned long long cp1_version = 0, cp2_version = 0;
     unsigned long long cp1_start_blk_no;
     unsigned long long cp2_start_blk_no;
     u32 bmp_size;
 
-    blk_size = 1U<<le32_to_cpu(sb->log_blocksize);
+    blk_size = 1U << le32_to_cpu(sb->log_blocksize);
 
     /*
      * Find valid cp by reading both packs and finding most recent one.
@@ -489,7 +489,8 @@
     u64 block;
     unsigned int used, found, started = 0, i;
 
-    for (block=startblock; block<info->total_blocks; block++) {
+    block = startblock;
+    while (block < info->total_blocks) {
         /* TODO: Save only relevant portions of metadata */
         if (block < info->main_blkaddr) {
             if (func(block, data)) {
@@ -512,17 +513,24 @@
 
             /* get SIT entry from SIT section */
             if (!found) {
-                sit_block_num_cur = segnum/SIT_ENTRY_PER_BLOCK;
+                sit_block_num_cur = segnum / SIT_ENTRY_PER_BLOCK;
                 sit_entry = &info->sit_blocks[sit_block_num_cur].entries[segnum % SIT_ENTRY_PER_BLOCK];
             }
 
             block_offset = (block - info->main_blkaddr) % info->blocks_per_segment;
 
+            if (block_offset == 0 && GET_SIT_VBLOCKS(sit_entry) == 0) {
+                block += info->blocks_per_segment;
+                continue;
+            }
+
             used = f2fs_test_bit(block_offset, (char *)sit_entry->valid_map);
             if(used)
                 if (func(block, data))
                     return -1;
         }
+
+        block++;
     }
     return 0;
 }
@@ -548,7 +556,7 @@
 {
     struct privdata *d = data;
     char *buf;
-    int pdone = (pos*100)/d->info->total_blocks;
+    int pdone = (pos * 100) / d->info->total_blocks;
     if (pdone > d->done) {
         d->done = pdone;
         printf("Done with %d percent\n", d->done);
@@ -562,7 +570,7 @@
     }
 
     off64_t ret;
-    ret = lseek64(d->outfd, pos*F2FS_BLKSIZE, SEEK_SET);
+    ret = lseek64(d->outfd, pos * F2FS_BLKSIZE, SEEK_SET);
     if (ret < 0) {
         SLOGE("failed to seek\n");
         return ret;
diff --git a/squashfs_utils/mksquashfsimage.sh b/squashfs_utils/mksquashfsimage.sh
index dab80ba..260a0fd 100755
--- a/squashfs_utils/mksquashfsimage.sh
+++ b/squashfs_utils/mksquashfsimage.sh
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE]
+${0##*/} SRC_DIR OUTPUT_FILE [-s] [-m MOUNT_POINT] [-c FILE_CONTEXTS] [-b BLOCK_SIZE] [-z COMPRESSOR] [-zo COMPRESSOR_OPT]
 EOT
 }
 
@@ -48,6 +48,19 @@
     shift; shift
 fi
 
+COMPRESSOR="lz4"
+COMPRESSOR_OPT="-Xhc"
+if [[ "$1" == "-z" ]]; then
+    COMPRESSOR=$2
+    COMPRESSOR_OPT=
+    shift; shift
+fi
+
+if [[ "$1" == "-zo" ]]; then
+    COMPRESSOR_OPT=$2
+    shift; shift
+fi
+
 OPT=""
 if [ -n "$MOUNT_POINT" ]; then
   OPT="$OPT -mount-point $MOUNT_POINT"
@@ -59,7 +72,7 @@
   OPT="$OPT -b $BLOCK_SIZE"
 fi
 
-MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp lz4 -Xhc -no-exports -noappend -no-recovery -android-fs-config $OPT"
+MAKE_SQUASHFS_CMD="mksquashfs $SRC_DIR/ $OUTPUT_FILE -no-progress -comp $COMPRESSOR $COMPRESSOR_OPT -no-exports -noappend -no-recovery -android-fs-config $OPT"
 echo $MAKE_SQUASHFS_CMD
 $MAKE_SQUASHFS_CMD
 
diff --git a/tests/workloads/atrace-uncompress.py b/tests/workloads/atrace-uncompress.py
new file mode 100644
index 0000000..5efb698
--- /dev/null
+++ b/tests/workloads/atrace-uncompress.py
@@ -0,0 +1,35 @@
+#
+# Uncompress a file generated via atrace -z
+#
+# Usage: python atrace-uncompress.py infile > outfile
+#
+import sys, zlib
+
+def main():
+
+	if len(sys.argv) != 2:
+		print >> sys.stderr, ('Usage: %s inputfile' % sys.argv[0])
+		sys.exit(1)
+
+	infile = open(sys.argv[1], "rb")
+	out = infile.read()
+	parts = out.split('\nTRACE:', 1)
+
+	data = ''.join(parts[1])
+
+	# Remove CR characters
+	if data.startswith('\r\n'):
+		data = data.replace('\r\n', '\n')
+
+	# Skip the initial newline.
+	data = data[1:]
+
+	if not data:
+		print >> sys.stderr, ('No trace data found')
+		sys.exit(1)
+
+	out = zlib.decompress(data)
+	print(out)
+
+if __name__ == '__main__':
+	main()
diff --git a/tests/workloads/capture.sh b/tests/workloads/capture.sh
new file mode 100755
index 0000000..3b2f446
--- /dev/null
+++ b/tests/workloads/capture.sh
Binary files differ
diff --git a/tests/workloads/defs.sh b/tests/workloads/defs.sh
new file mode 100755
index 0000000..a2b7138
--- /dev/null
+++ b/tests/workloads/defs.sh
Binary files differ
diff --git a/tests/workloads/feedly-chrome.sh b/tests/workloads/feedly-chrome.sh
new file mode 100755
index 0000000..4c7002f
--- /dev/null
+++ b/tests/workloads/feedly-chrome.sh
@@ -0,0 +1,111 @@
+# Script to automate the following sequence:
+# - Open Feedly
+# - Open an article
+# - Scroll to bottome
+# - Open the same article in Chrome
+# - Scroll the article
+# - Back to Feely (should still be in memory)
+# - Home screen
+# ---- repeat ----
+#
+# Currently works on volantis only (verticle orientation)
+#
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case "$DEVICE" in
+(volantis)
+	echo volantis...
+	feedlyArticle="500 700"
+	feedlyOptions="1480 100"
+	feedlyBrowserSelect="1350 650"
+	feedlyArticleSwipeUp="700 700 700 50 50"
+	feedlyArticleSwipeDown="700 200 700 700 50"
+	chromeSwipe="700 700 700 50 50"
+	;;
+(shamu|*)
+	echo shamu...
+	feedlyArticle="676 500"
+	feedlyOptions="1327 207"
+	feedlyBrowserSelect="1278 1191"
+	feedlyArticleSwipeUp="700 1847 700 400 50"
+	feedlyArticleSwipeDown="700 400 700 1847 50"
+	chromeSwipe="700 1847 700 400 50"
+	;;
+(hammerhead|*)
+	echo "Error: No feedly screen geometry information available for $DEVICE"
+	exit 1;;
+esac
+
+feedlySwitchToTime=600
+
+# start feedly, if not installed, error out
+t=$(forceStartActivity feedly)
+checkIsRunning feedly "initial start of feedly"
+echo Feedly start time = ${t}ms
+
+# start chrome, if not installed, error out
+t=$(forceStartActivity chrome)
+checkIsRunning chrome "initial start of chrome"
+echo Chrome start time = ${t}ms
+sleep 1
+
+feedlyStartTimes=0
+
+cur=1
+while [ $cur -le $iterations ]
+do
+	echo =======================================
+	echo Iteration $cur of $iterations
+	echo =======================================
+	startInstramentation
+	t=$(startActivity feedly)
+	if [ $(checkStartTime "$t" $feedlySwitchToTime) != true ]; then
+		handleError Feedly took too long to start: $t v $feedlySwitchToTime: $?
+		# for now, not fatal
+		# exit 1
+	fi
+	sleep 2
+	((feedlyStartTimes=feedlyStartTimes+t))
+	echo feedly started in ${t}ms
+	checkIsRunning chrome "switch back to feedly"
+	checkIsRunning googlequicksearchbox "switch back to feedly"
+
+	# click on first article
+	doTap $feedlyArticle
+	sleep 2
+
+	# scroll through article
+	doSwipe $feedlyArticleSwipeUp
+	sleep 5
+	checkIsRunning chrome "feedly swipe"
+	checkIsRunning googlequicksearchbox "feedly swipe"
+
+	# scroll back to top
+	doSwipe $feedlyArticleSwipeDown
+	sleep 2
+
+	# switch to chrome
+	# 1. click on menu bar
+	doTap $feedlyOptions
+	sleep 1
+	# 2. click on browser
+	doTap $feedlyBrowserSelect
+	sleep 10
+
+	checkIsRunning feedly "switch to chrome"
+	checkIsRunning googlequicksearchbox "switch to chrome"
+
+	# Now we're back in chrome, swipe to bottom of article
+	doSwipe $chromeSwipe
+	sleep 2
+	checkIsRunning feedly "swiped chrome"
+	stopInstramentation
+	((cur=cur+1))
+done
+((feedlyAve=feedlyStartTimes/iterations))
+echo Avg start times: feedly: ${feedlyAve}ms
+
+doKeyevent HOME
diff --git a/tests/workloads/recentfling.sh b/tests/workloads/recentfling.sh
new file mode 100755
index 0000000..092c8d9
--- /dev/null
+++ b/tests/workloads/recentfling.sh
@@ -0,0 +1,150 @@
+#
+# Script to start a set of apps, switch to recents and fling it back and forth.
+# For each iteration, Total frames and janky frames are reported.
+#
+# Options are described below.
+#
+# Works for volantis, shamu, and hammerhead. Can be pushed and executed on
+# the device.
+#
+iterations=10
+startapps=1
+capturesystrace=0
+
+function processLocalOption {
+	ret=0
+	case "$1" in
+	(-N) startapps=0;;
+	(-A) unset appList;;
+	(-L) appList=$2; shift; ret=1;;
+	(-T) capturesystrace=1;;
+	(*)
+		echo "$0: unrecognized option: $1"
+		echo; echo "Usage: $0 [options]"
+		echo "-A : use all known applications"
+		echo "-L applist : list of applications"
+		echo "   default: $appList"
+		echo "-N : no app startups, just fling"
+		echo "-g : generate activity strings"
+		echo "-i iterations"
+		echo "-T : capture systrace on each iteration"
+		exit 1;;
+	esac
+	return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+case $DEVICE in
+(shamu|hammerhead)
+	flingtime=300
+	downCount=2
+	upCount=6
+	UP="70 400 70 100 $flingtime"
+	DOWN="70 100 70 400 $flingtime";;
+(bullhead)
+	flingtime=200
+	downCount=5
+	upCount=5
+	UP="500 1200 500 550 $flingtime"
+	DOWN="500 550 500 1200 $flingtime";;
+(volantis)
+	flingtime=400
+	downCount=5
+	upCount=6
+	UP="70 400 70 70 $flingtime"
+	DOWN="70 70 70 400 $flingtime";;
+(*)
+	echo "Error: No display information available for $DEVICE"
+	exit 1;;
+esac
+
+doKeyevent HOME
+if [ $startapps -gt 0 ]; then
+
+	# start a bunch of apps
+	for app in $appList
+	do
+		echo Starting $app ...
+		t=$(startActivity $app)
+	done
+fi
+
+function swipe {
+	count=0
+	while [ $count -lt $2 ]
+	do
+		doSwipe $1
+		((count=count+1))
+	done
+}
+
+cur=1
+frameSum=0
+jankSum=0
+latency90Sum=0
+latency95Sum=0
+latency99Sum=0
+
+echo Fling recents...
+doKeyevent HOME
+sleep 0.5
+resetJankyFrames
+
+while [ $cur -le $iterations ]
+do
+	if [ $capturesystrace -gt 0 ]; then
+		${ADB}atrace --async_start -z -c -b 16000 freq gfx view idle sched
+	fi
+	doKeyevent APP_SWITCH
+	sleep 0.5
+	swipe "$DOWN" $downCount
+	sleep 1
+	swipe "$UP" $upCount
+	sleep 1
+	swipe "$DOWN" $downCount
+	sleep 1
+	swipe "$UP" $upCount
+	sleep 1
+	if [ $capturesystrace -gt 0 ]; then
+		${ADB}atrace --async_dump -z -c -b 16000 freq gfx view idle sched > trace.${cur}.out
+	fi
+	doKeyevent HOME
+	sleep 0.5
+
+	set -- $(getJankyFrames)
+	totalDiff=$1
+	jankyDiff=$2
+	latency90=$3
+	latency95=$4
+	latency99=$5
+	if [ ${totalDiff:=0} -eq 0 ]; then
+		echo Error: could not read frame info with \"dumpsys gfxinfo\"
+		exit 1
+	fi
+
+	((frameSum=frameSum+totalDiff))
+	((jankSum=jankSum+jankyDiff))
+	((latency90Sum=latency90Sum+latency90))
+	((latency95Sum=latency95Sum+latency95))
+	((latency99Sum=latency99Sum+latency99))
+	if [ "$totalDiff" -eq 0 ]; then
+		echo Error: no frames detected. Is the display off?
+		exit 1
+	fi
+	((jankPct=jankyDiff*100/totalDiff))
+	resetJankyFrames
+
+	echo Frames: $totalDiff latency: $latency90/$latency95/$latency99 Janks: $jankyDiff\(${jankPct}%\)
+	((cur=cur+1))
+done
+doKeyevent HOME
+((aveJankPct=jankSum*100/frameSum))
+((aveJanks=jankSum/iterations))
+((aveFrames=frameSum/iterations))
+((aveLatency90=latency90Sum/iterations))
+((aveLatency95=latency95Sum/iterations))
+((aveLatency99=latency99Sum/iterations))
+echo AVE: Frames: $aveFrames latency: $aveLatency90/$aveLatency95/$aveLatency99 Janks: $aveJanks\(${aveJankPct}%\)
diff --git a/tests/workloads/systemapps.sh b/tests/workloads/systemapps.sh
new file mode 100755
index 0000000..a263e7d
--- /dev/null
+++ b/tests/workloads/systemapps.sh
@@ -0,0 +1,264 @@
+# Script to start a set of apps in order and then in each iteration
+# switch the focus to each one. For each iteration, the time to start
+# the app is reported as measured using atrace events and via am ThisTime.
+# The output also reports if applications are restarted (eg, killed by
+# LMK since previous iteration) or if there were any direct reclaim
+# events.
+#
+# Variation: the "-T" option skips all of the atrace instramentation and
+# attempts to start the apps as quickly as possible.
+#
+# Example 1: start all default apps. 2 iterations
+#
+# ./systemapps.sh -i 2
+#
+# Example 2: just start chrome, feedly, and the home screen in a loop
+#
+# ./systemapps.sh -L "chrome feedly home" -i 5
+#
+# Example 3: just start the default apps as quickly as possible
+#
+# ./systemapps.sh -T
+#
+# Other options are described below.
+#
+iterations=1
+tracecategories="gfx view am input memreclaim"
+totaltimetest=0
+forcecoldstart=0
+waitTime=3.0
+
+appList="gmail hangouts chrome youtube play home"
+
+function processLocalOption {
+	ret=0
+	case "$1" in
+	(-A) unset appList;;
+	(-F) forcecoldstart=1;;
+	(-L) appList=$2; shift; ret=1;;
+	(-T) totaltimetest=1;;
+	(-W) waitTime=$2; shift; ret=1;;
+	(*)
+		echo "$0: unrecognized option: $1"
+		echo; echo "Usage: $0 [options]"
+		echo "-A : use all known applications"
+		echo "-F : force cold-start for all apps"
+		echo "-L applist : list of applications"
+		echo "   default: $appList"
+		echo "-T : total time to start all apps"
+		echo "-W : time to wait between apps"
+		echo "-g : generate activity strings"
+		echo "-i iterations"
+		echo "-n : keep trace files"
+		echo "-o output file"
+		echo "-s : stop on error"
+		echo "-t trace categories"
+		exit 1;;
+	esac
+	return $ret
+}
+
+CMDDIR=$(dirname $0 2>/dev/null)
+CMDDIR=${CMDDIR:=.}
+. $CMDDIR/defs.sh
+
+tmpTraceOutBase=./tmptrace
+
+if [ $user !=  "root" -a $totaltimetest -eq 0 ]; then
+	handleError Must be root on device
+	exit 1
+fi
+doKeyevent HOME
+
+function computeStats {
+	label=$1
+	t=$2
+	restart=$3
+	reclaim=$4
+	frames=$5
+	janks=$6
+	l90=$7
+	l95=$8
+	l99=$9
+	curMax=$(eval "echo \$${label}max")
+	curMax=${curMax:=0}
+	curMin=$(eval "echo \$${label}min")
+	curMin=${curMin:=100000}
+	curSum=$(eval "echo \$${label}sum")
+	curSum=${curSum:=0}
+	curRestart=$(eval "echo \$${label}restart")
+	curRestart=${curRestart:=0}
+	curReclaim=$(eval "echo \$${label}reclaim")
+	curReclaim=${curReclaim:=0}
+	curFrames=$(eval "echo \$${label}frames")
+	curFrames=${curFrames:=0}
+	curJanks=$(eval "echo \$${label}janks")
+	curJanks=${curJanks:=0}
+	cur90=$(eval "echo \$${label}90")
+	cur90=${cur90:=0}
+	cur95=$(eval "echo \$${label}95")
+	cur95=${cur95:=0}
+	cur99=$(eval "echo \$${label}99")
+	cur99=${cur99:=0}
+	if [ $curMax -lt $t ]; then
+		eval "${label}max=$t"
+	fi
+	if [ $curMin -gt $t ]; then
+		eval "${label}min=$t"
+	fi
+	((curSum=curSum+t))
+	eval "${label}sum=$curSum"
+
+	((curRestart=curRestart+${restart:=0}))
+	eval "${label}restart=$curRestart"
+	((curReclaim=curReclaim+${reclaim:=0}))
+	eval "${label}reclaim=$curReclaim"
+	((curFrames=curFrames+${frames:=0}))
+	eval "${label}frames=$curFrames"
+	((curJanks=curJanks+${janks:=0}))
+	eval "${label}janks=$curJanks"
+	((cur90=cur90+${l90:=0}))
+	eval "${label}90=$cur90"
+	((cur95=cur95+${l95:=0}))
+	eval "${label}95=$cur95"
+	((cur99=cur99+${l99:=0}))
+	eval "${label}99=$cur99"
+}
+function getStats {
+	label=$1
+	echo $(eval "echo \$${label}max") $(eval "echo \$${label}min") $(eval "echo \$${label}sum") \
+		$(eval "echo \$${label}restart") $(eval "echo \$${label}reclaim") \
+		$(eval "echo \$${label}frames") $(eval "echo \$${label}janks") \
+		$(eval "echo \$${label}90") $(eval "echo \$${label}95") $(eval "echo \$${label}99")
+}
+
+cur=1
+totaltime=0
+startTimestamp=$(date +"%s %N")
+
+while [ $cur -le $iterations ]
+do
+	if [ $iterations -gt 1 ]; then
+		echo =========================================
+		echo Iteration $cur of $iterations
+		echo =========================================
+	fi
+	if [ $iterations -gt 1 -o $cur -eq 1 ]; then
+		if [ $totaltimetest -eq 0 ]; then
+			printf "%-6s    %7s(ms)  %6s(ms) %s %s %s     %s\n" App  Time AmTime Restart DirReclaim Jank Latency
+		fi
+	fi
+
+	appnum=-1
+	for app in $appList
+	do
+		vout Starting $app...
+		((appnum=appnum+1))
+		loopTimestamp=$(date +"%s %N")
+		resetJankyFrames
+		resetJankyFrames $(getPackageName $app)
+		if [ $totaltimetest -eq 0 ]; then
+			tmpTraceOut="$tmpTraceOutBase-$app.out"
+			>$tmpTraceOut
+			startInstramentation
+		else
+			if [ $appnum -eq 0 ]; then
+				printf "%-8s %5s(ms) %3s(ms) %s      %s\n" App Start Iter Jank Latency
+			fi
+		fi
+		if [ $forcecoldstart -eq 0 ]; then
+			t=$(startActivity $app)
+		else
+			t=$(forceStartActivity $app)
+		fi
+
+		# let app finish drawing before checking janks
+		sleep $waitTime
+		set -- $(getJankyFrames $(getPackageName $app))
+		frames=$1
+		janks=$2
+		l90=$3
+		l95=$4
+		l99=$5
+		set -- $(getJankyFrames)
+		systemFrames=$1
+		systemJanks=$2
+		s90=$3
+		s95=$4
+		s99=$5
+		((frames=frames+systemFrames))
+		((janks=janks+systemJanks))
+		((l90=l90+s90))
+		((l95=l95+s95))
+		((l99=l99+s99))
+
+		loopEndTimestamp=$(date +"%s %N")
+		diffTime=$(computeTimeDiff $loopTimestamp $loopEndTimestamp)
+
+		if [ $frames -eq 0 ]; then
+			janks=0
+			jankPct=0
+		else
+			((jankPct=100*janks/frames))
+		fi
+		if [ $totaltimetest -gt 0 ]; then
+			# Note: using %f since %d doesn't work correctly
+			# when running on lollipop
+			printf "%-10s %5.0f   %5.0f    %4.0f(%2.0f%%) %2.0f/%2.0f/%2.0f\n" $app $t $diffTime $janks $jankPct $l90 $l95 $l99
+			((totaltime=totaltime+t))
+			continue
+		else
+			stopAndDumpInstramentation $tmpTraceOut
+			actName=$(getActivityName $app)
+			pkgName=$(getPackageName $app)
+			stime=$(getStartTime $actName $tmpTraceOut)
+			relaunch=$?
+			etime=$(getEndTime $pkgName $tmpTraceOut)
+			((tdiff=$etime-$stime))
+			if [ $etime -eq 0 -o $stime -eq 0 ]; then
+				handleError $app : could not compute start time stime=$stime  etime=$etime
+				# use AmTime so statistics make sense
+				tdiff=$t
+			fi
+			checkForDirectReclaim $actName $tmpTraceOut
+			directReclaim=$?
+
+			printf "%-12s %5d     %5d     %5d    %5d    %5d(%d%%) %d/%d/%d\n" "$app" "$tdiff" "$t" "$relaunch" "$directReclaim" "$janks" "$jankPct" $l90 $l95 $l99
+			computeStats "$app" "$tdiff" "$relaunch" "$directReclaim" "$frames" "$janks" $l90 $l95 $l99
+
+			if [ $savetmpfiles -eq 0 ]; then
+				rm -f $tmpTraceOut
+			fi
+		fi
+	done
+	((cur=cur+1))
+done
+endTimestamp=$(date +"%s %N")
+diffTime=$(computeTimeDiff $startTimestamp $endTimestamp)
+if [ $totaltimetest -gt 0 ]; then
+	printf "%-10s %5.0f   %5.0f\n" TOTAL $totaltime $diffTime
+fi
+
+if [ $iterations -gt 1 -a $totaltimetest -eq 0 ]; then
+	echo
+	echo =========================================
+	printf "Stats after $iterations iterations:\n"
+	echo =========================================
+	printf "%-6s    %7s(ms) %6s(ms) %6s(ms)    %s    %s %s     %s\n" App Max Ave Min Restart DirReclaim Jank Latency
+	for app in $appList
+	do
+		set -- $(getStats $app)
+		sum=$3
+		((ave=sum/iterations))
+		frames=$6
+		janks=$7
+		l90=$8
+		l95=$9
+		l99=${10}
+		((ave90=l90/iterations))
+		((ave95=l95/iterations))
+		((ave99=l99/iterations))
+		((jankPct=100*janks/frames))
+		printf "%-12s %5d      %5d      %5d      %5d      %5d     %5d(%d%%) %d/%d/%d\n" $app $1 $ave $2 $4 $5 $janks $jankPct $ave90 $ave95 $ave99
+	done
+fi
diff --git a/verity/Android.mk b/verity/Android.mk
index bbe74bb..586ca58 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -1,5 +1,6 @@
 LOCAL_PATH:= $(call my-dir)
 
+ifeq ($(HOST_OS),linux)
 include $(CLEAR_VARS)
 LOCAL_MODULE := verify_boot_signature
 LOCAL_SRC_FILES := verify_boot_signature.c
@@ -8,6 +9,7 @@
 LOCAL_SHARED_LIBRARIES := libcrypto-host
 LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
 include $(BUILD_HOST_EXECUTABLE)
+endif
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := generate_verity_key
diff --git a/verity/Utils.java b/verity/Utils.java
index 3576e3b..937c206 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -35,6 +35,8 @@
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.InvalidKeySpecException;
@@ -52,6 +54,7 @@
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.util.encoders.Base64;
 
 public class Utils {
@@ -63,10 +66,16 @@
         ID_TO_ALG = new HashMap<String, String>();
         ALG_TO_ID = new HashMap<String, String>();
 
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
         ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
 
+        ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
+        ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
+        ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
         ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
         ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
         ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
@@ -208,15 +217,36 @@
         }
     }
 
-    private static String getSignatureAlgorithm(Key key) {
-        if ("RSA".equals(key.getAlgorithm())) {
+    private static String getSignatureAlgorithm(Key key) throws Exception {
+        if ("EC".equals(key.getAlgorithm())) {
+            int curveSize;
+            KeyFactory factory = KeyFactory.getInstance("EC");
+
+            if (key instanceof PublicKey) {
+                ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else if (key instanceof PrivateKey) {
+                ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else {
+                throw new InvalidKeySpecException();
+            }
+
+            if (curveSize <= 256) {
+                return "SHA256withECDSA";
+            } else if (curveSize <= 384) {
+                return "SHA384withECDSA";
+            } else {
+                return "SHA512withECDSA";
+            }
+        } else if ("RSA".equals(key.getAlgorithm())) {
             return "SHA256withRSA";
         } else {
             throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
         }
     }
 
-    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
         String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
 
         if (id == null) {
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
index 5c9d7d2..6b3f49e 100644
--- a/verity/VerityVerifier.java
+++ b/verity/VerityVerifier.java
@@ -20,31 +20,83 @@
 import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.lang.Math;
 import java.lang.Process;
 import java.lang.Runtime;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
 import java.security.PublicKey;
-import java.security.PrivateKey;
 import java.security.Security;
 import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.xml.bind.DatatypeConverter;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public class VerityVerifier {
 
+    private ArrayList<Integer> hashBlocksLevel;
+    private byte[] hashTree;
+    private byte[] rootHash;
+    private byte[] salt;
+    private byte[] signature;
+    private byte[] table;
+    private File image;
+    private int blockSize;
+    private int hashBlockSize;
+    private int hashOffsetForData;
+    private int hashSize;
+    private int hashTreeSize;
+    private long hashStart;
+    private long imageSize;
+    private MessageDigest digest;
+
     private static final int EXT4_SB_MAGIC = 0xEF53;
     private static final int EXT4_SB_OFFSET = 0x400;
     private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
     private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
     private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
     private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
+    private static final int MINCRYPT_OFFSET_MODULUS = 0x8;
+    private static final int MINCRYPT_OFFSET_EXPONENT = 0x208;
+    private static final int MINCRYPT_MODULUS_SIZE = 0x100;
+    private static final int MINCRYPT_EXPONENT_SIZE = 0x4;
+    private static final int VERITY_FIELDS = 10;
     private static final int VERITY_MAGIC = 0xB001B001;
     private static final int VERITY_SIGNATURE_SIZE = 256;
     private static final int VERITY_VERSION = 0;
 
+    public VerityVerifier(String fname) throws Exception {
+        digest = MessageDigest.getInstance("SHA-256");
+        hashSize = digest.getDigestLength();
+        hashBlocksLevel = new ArrayList<Integer>();
+        hashTreeSize = -1;
+        openImage(fname);
+        readVerityData();
+    }
+
+    /**
+     * Reverses the order of bytes in a byte array
+     * @param value Byte array to reverse
+     */
+    private static byte[] reverse(byte[] value) {
+        for (int i = 0; i < value.length / 2; i++) {
+            byte tmp = value[i];
+            value[i] = value[value.length - i - 1];
+            value[value.length - i - 1] = tmp;
+        }
+
+        return value;
+    }
+
     /**
      * Converts a 4-byte little endian value to a Java integer
      * @param value Little endian integer to convert
      */
-     public static int fromle(int value) {
+    private static int fromle(int value) {
         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
         return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
     }
@@ -53,28 +105,51 @@
      * Converts a 2-byte little endian value to Java a integer
      * @param value Little endian short to convert
      */
-     public static int fromle(short value) {
+    private static int fromle(short value) {
         return fromle(value << 16);
     }
 
     /**
+     * Reads a 2048-bit RSA public key saved in mincrypt format, and returns
+     * a Java PublicKey for it.
+     * @param fname Name of the mincrypt public key file
+     */
+    private static PublicKey getMincryptPublicKey(String fname) throws Exception {
+        try (RandomAccessFile key = new RandomAccessFile(fname, "r")) {
+            byte[] binaryMod = new byte[MINCRYPT_MODULUS_SIZE];
+            byte[] binaryExp = new byte[MINCRYPT_EXPONENT_SIZE];
+
+            key.seek(MINCRYPT_OFFSET_MODULUS);
+            key.readFully(binaryMod);
+
+            key.seek(MINCRYPT_OFFSET_EXPONENT);
+            key.readFully(binaryExp);
+
+            BigInteger modulus  = new BigInteger(1, reverse(binaryMod));
+            BigInteger exponent = new BigInteger(1, reverse(binaryExp));
+
+            RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
+            KeyFactory factory = KeyFactory.getInstance("RSA");
+            return factory.generatePublic(spec);
+        }
+    }
+
+    /**
      * Unsparses a sparse image into a temporary file and returns a
      * handle to the file
      * @param fname Path to a sparse image file
      */
-     public static RandomAccessFile openImage(String fname) throws Exception {
-        File tmp = File.createTempFile("system", ".raw");
-        tmp.deleteOnExit();
+     private void openImage(String fname) throws Exception {
+        image = File.createTempFile("system", ".raw");
+        image.deleteOnExit();
 
         Process p = Runtime.getRuntime().exec("simg2img " + fname +
-                            " " + tmp.getAbsoluteFile());
+                            " " + image.getAbsoluteFile());
 
         p.waitFor();
         if (p.exitValue() != 0) {
             throw new IllegalArgumentException("Invalid image: failed to unsparse");
         }
-
-        return new RandomAccessFile(tmp, "r");
     }
 
     /**
@@ -106,56 +181,234 @@
     }
 
     /**
-     * Reads and validates verity metadata, and check the signature against the
+     * Calculates the size of the verity hash tree based on the image size
+     */
+    private int calculateHashTreeSize() {
+        if (hashTreeSize > 0) {
+            return hashTreeSize;
+        }
+
+        int totalBlocks = 0;
+        int hashes = (int) (imageSize / blockSize);
+
+        hashBlocksLevel.clear();
+
+        do {
+            hashBlocksLevel.add(0, hashes);
+
+            int hashBlocks =
+                (int) Math.ceil((double) hashes * hashSize / hashBlockSize);
+
+            totalBlocks += hashBlocks;
+
+            hashes = hashBlocks;
+        } while (hashes > 1);
+
+        hashTreeSize = totalBlocks * hashBlockSize;
+        return hashTreeSize;
+    }
+
+    /**
+     * Parses the verity mapping table and reads the hash tree from
+     * the image file
+     * @param img Handle to the image file
+     * @param table Verity mapping table
+     */
+    private void readHashTree(RandomAccessFile img, byte[] table)
+            throws Exception {
+        String tableStr = new String(table);
+        String[] fields = tableStr.split(" ");
+
+        if (fields.length != VERITY_FIELDS) {
+            throw new IllegalArgumentException("Invalid image: unexpected number of fields "
+                    + "in verity mapping table (" + fields.length + ")");
+        }
+
+        String hashVersion = fields[0];
+
+        if (!"1".equals(hashVersion)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash format");
+        }
+
+        String alg = fields[7];
+
+        if (!"sha256".equals(alg)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash algorithm");
+        }
+
+        blockSize = Integer.parseInt(fields[3]);
+        hashBlockSize = Integer.parseInt(fields[4]);
+
+        int blocks = Integer.parseInt(fields[5]);
+        int start = Integer.parseInt(fields[6]);
+
+        if (imageSize != (long) blocks * blockSize) {
+            throw new IllegalArgumentException("Invalid image: size mismatch in mapping "
+                    + "table");
+        }
+
+        rootHash = DatatypeConverter.parseHexBinary(fields[8]);
+        salt = DatatypeConverter.parseHexBinary(fields[9]);
+
+        hashStart = (long) start * blockSize;
+        img.seek(hashStart);
+
+        int treeSize = calculateHashTreeSize();
+
+        hashTree = new byte[treeSize];
+        img.readFully(hashTree);
+    }
+
+    /**
+     * Reads verity data from the image file
+     */
+    private void readVerityData() throws Exception {
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            imageSize = getMetadataPosition(img);
+            img.seek(imageSize);
+
+            int magic = fromle(img.readInt());
+
+            if (magic != VERITY_MAGIC) {
+                throw new IllegalArgumentException("Invalid image: verity metadata not found");
+            }
+
+            int version = fromle(img.readInt());
+
+            if (version != VERITY_VERSION) {
+                throw new IllegalArgumentException("Invalid image: unknown metadata version");
+            }
+
+            signature = new byte[VERITY_SIGNATURE_SIZE];
+            img.readFully(signature);
+
+            int tableSize = fromle(img.readInt());
+
+            table = new byte[tableSize];
+            img.readFully(table);
+
+            readHashTree(img, table);
+        }
+    }
+
+    /**
+     * Reads and validates verity metadata, and checks the signature against the
      * given public key
-     * @param img File handle to the image file
      * @param key Public key to use for signature verification
      */
-    public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+    public boolean verifyMetaData(PublicKey key)
             throws Exception {
-        img.seek(getMetadataPosition(img));
-        int magic = fromle(img.readInt());
-
-        if (magic != VERITY_MAGIC) {
-            throw new IllegalArgumentException("Invalid image: verity metadata not found");
-        }
-
-        int version = fromle(img.readInt());
-
-        if (version != VERITY_VERSION) {
-            throw new IllegalArgumentException("Invalid image: unknown metadata version");
-        }
-
-        byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
-        img.readFully(signature);
-
-        int tableSize = fromle(img.readInt());
-
-        byte[] table = new byte[tableSize];
-        img.readFully(table);
-
-        return Utils.verify(key, table, signature,
+       return Utils.verify(key, table, signature,
                    Utils.getSignatureAlgorithmIdentifier(key));
     }
 
+    /**
+     * Hashes a block of data using a salt and checks of the results are expected
+     * @param hash The expected hash value
+     * @param data The data block to check
+     */
+    private boolean checkBlock(byte[] hash, byte[] data) {
+        digest.reset();
+        digest.update(salt);
+        digest.update(data);
+        return Arrays.equals(hash, digest.digest());
+    }
+
+    /**
+     * Verifies the root hash and the first N-1 levels of the hash tree
+     */
+    private boolean verifyHashTree() throws Exception {
+        int hashOffset = 0;
+        int dataOffset = hashBlockSize;
+
+        if (!checkBlock(rootHash, Arrays.copyOfRange(hashTree, 0, hashBlockSize))) {
+            System.err.println("Root hash mismatch");
+            return false;
+        }
+
+        for (int level = 0; level < hashBlocksLevel.size() - 1; level++) {
+            int blocks = hashBlocksLevel.get(level);
+
+            for (int i = 0; i < blocks; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                byte[] dataBlock = Arrays.copyOfRange(hashTree,
+                        dataOffset + i * hashBlockSize,
+                        dataOffset + i * hashBlockSize + hashBlockSize);
+
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at tree level %d, block %d\n", level, i);
+                    return false;
+                }
+            }
+
+            hashOffset = dataOffset;
+            hashOffsetForData = dataOffset;
+            dataOffset += blocks * hashBlockSize;
+        }
+
+        return true;
+    }
+
+    /**
+     * Validates the image against the hash tree
+     */
+    public boolean verifyData() throws Exception {
+        if (!verifyHashTree()) {
+            return false;
+        }
+
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            byte[] dataBlock = new byte[blockSize];
+            int hashOffset = hashOffsetForData;
+
+            for (int i = 0; (long) i * blockSize < imageSize; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                img.readFully(dataBlock);
+
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at block %d\n", i);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Verifies the integrity of the image and the verity metadata
+     * @param key Public key to use for signature verification
+     */
+    public boolean verify(PublicKey key) throws Exception {
+        return (verifyMetaData(key) && verifyData());
+    }
+
     public static void main(String[] args) throws Exception {
-        if (args.length != 2) {
-            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+        Security.addProvider(new BouncyCastleProvider());
+        PublicKey key = null;
+
+        if (args.length == 3 && "-mincrypt".equals(args[1])) {
+            key = getMincryptPublicKey(args[2]);
+        } else if (args.length == 2) {
+            X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+            key = cert.getPublicKey();
+        } else {
+            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem> | -mincrypt <mincrypt_key>");
             System.exit(1);
         }
 
-        Security.addProvider(new BouncyCastleProvider());
-
-        X509Certificate cert = Utils.loadPEMCertificate(args[1]);
-        PublicKey key = cert.getPublicKey();
-        RandomAccessFile img = openImage(args[0]);
+        VerityVerifier verifier = new VerityVerifier(args[0]);
 
         try {
-            if (verifyMetaData(img, key)) {
+            if (verifier.verify(key)) {
                 System.err.println("Signature is VALID");
                 System.exit(0);
-            } else {
-                System.err.println("Signature is INVALID");
             }
         } catch (Exception e) {
             e.printStackTrace(System.err);