Merge master@5406228 into git_qt-dev-plus-aosp.
am: fcebb73df9

Change-Id: I407757aa7705628164700aca9266057430b422e6
diff --git a/include/tuningfork/tuningfork.h b/include/tuningfork/tuningfork.h
index 4b26fa1..24ff52f 100644
--- a/include/tuningfork/tuningfork.h
+++ b/include/tuningfork/tuningfork.h
@@ -19,6 +19,10 @@
 #include <stdint.h>
 #include <jni.h>
 
+#define TUNINGFORK_MAJOR_VERSION 0
+#define TUNINGFORK_MINOR_VERSION 1
+#define TUNINGFORK_PACKED_VERSION ((TUNINGFORK_MAJOR_VERSION<<16)|(TUNINGFORK_MINOR_VERSION))
+
 // These are reserved instrumentation keys
 enum {
     TFTICK_SYSCPU = 0,
diff --git a/samples/bouncyball/build.gradle b/samples/bouncyball/build.gradle
index cf1d23d..4699ea7 100644
--- a/samples/bouncyball/build.gradle
+++ b/samples/bouncyball/build.gradle
@@ -6,7 +6,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.0-beta05'
+        classpath 'com.android.tools.build:gradle:3.3.2'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
diff --git a/samples/cube/build.gradle b/samples/cube/build.gradle
index cf1d23d..4699ea7 100644
--- a/samples/cube/build.gradle
+++ b/samples/cube/build.gradle
@@ -6,7 +6,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.0-beta05'
+        classpath 'com.android.tools.build:gradle:3.3.2'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }
diff --git a/samples/tuningfork/tftestapp/app/src/main/cpp/Renderer.cpp b/samples/tuningfork/tftestapp/app/src/main/cpp/Renderer.cpp
index e1c0197..ddf8dac 100644
--- a/samples/tuningfork/tftestapp/app/src/main/cpp/Renderer.cpp
+++ b/samples/tuningfork/tftestapp/app/src/main/cpp/Renderer.cpp
@@ -32,9 +32,12 @@
 
 #include "swappy/swappy.h"
 #include "swappy/swappy_extra.h"
+#include "tuningfork/tuningfork.h"
 
 #include "Scene.h"
 
+extern bool swappy_enabled;
+
 using namespace std::chrono_literals;
 
 namespace samples {
@@ -48,7 +51,7 @@
     enqueue([=](State* state) {
         state->clearSurface();
 
-        ALOGE("Creating window surface %dx%d", width, height);
+        ALOGI("Creating window surface %dx%d", width, height);
 
         if (!window) return;
 
@@ -227,7 +230,8 @@
         return;
     }
 
-    Swappy_recordFrameStart(state->display, state->surface);
+    if (swappy_enabled)
+        Swappy_recordFrameStart(state->display, state->surface);
 
     calculateFps();
 
@@ -252,7 +256,12 @@
 
     state->scene.draw(aspectRatio, mTesselation);
 
-    Swappy_swap(state->display, state->surface);
+    if (swappy_enabled)
+        Swappy_swap(state->display, state->surface);
+    else {
+        TuningFork_frameTick(TFTICK_SYSCPU);
+        eglSwapBuffers(state->display, state->surface);
+    }
 
     // If we're still started, request another frame
     requestDraw();
diff --git a/samples/tuningfork/tftestapp/app/src/main/cpp/tftestapp.cpp b/samples/tuningfork/tftestapp/app/src/main/cpp/tftestapp.cpp
index e160f12..7ec2737 100644
--- a/samples/tuningfork/tftestapp/app/src/main/cpp/tftestapp.cpp
+++ b/samples/tuningfork/tftestapp/app/src/main/cpp/tftestapp.cpp
@@ -36,6 +36,8 @@
 namespace tf = tuningfork;
 using namespace samples;
 
+bool swappy_enabled = false;
+
 namespace {
 
 constexpr TFInstrumentKey TFTICK_CHOREOGRAPHER = 4;
@@ -116,6 +118,9 @@
     if (evt.has_apk_version_code()) {
         eventStr << "  apk_version_code : " << evt.apk_version_code() << "\n";
     }
+    if (evt.has_tuningfork_version()) {
+        eventStr << "  tuningfork_version : " << evt.tuningfork_version() << "\n";
+    }
     eventStr << "}";
     return eventStr.str();
 }
@@ -181,7 +186,8 @@
 JNIEXPORT void JNICALL
 Java_com_google_tuningfork_TFTestActivity_initTuningFork(JNIEnv *env, jobject activity) {
     Swappy_init(env, activity);
-    if (Swappy_isEnabled()) {
+    swappy_enabled = Swappy_isEnabled();
+    if (swappy_enabled) {
         int defaultFPIndex = 3; // i.e. dev_tuningfork_fidelityparams_3.bin
         int initialTimeoutMs = 1000;
         int ultimateTimeoutMs = 100000;
@@ -196,7 +202,29 @@
             ALOGW("Error initializing TuningFork: %d", c);
         }
     } else {
-        ALOGW("Couldn't enable Swappy. Tuning Fork is not enabled either");
+        ALOGW("Couldn't enable Swappy.");
+        CProtobufSerialization settings = {};
+        TuningFork_findSettingsInAPK(env, activity, &settings);
+        TuningFork_init(&settings, env, activity);
+        tuningfork::CProtobufSerialization_Free(&settings);
+        int fp_count;
+        TuningFork_findFidelityParamsInAPK(env, activity, NULL, &fp_count);
+        CProtobufSerialization fps = {};
+        std::vector<CProtobufSerialization> defaultFPs(fp_count);
+        TuningFork_findFidelityParamsInAPK(env, activity, defaultFPs.data(), &fp_count);
+        CProtobufSerialization* defaultFP = &defaultFPs[fp_count/2-1]; // Middle settings level
+        if (TuningFork_getFidelityParameters(defaultFP, &fps, 1000)) {
+            SetFidelityParams(&fps);
+            tuningfork::CProtobufSerialization_Free(&fps);
+        }
+        else {
+            SetFidelityParams(defaultFP);
+        }
+        for(auto& a: defaultFPs) {
+            tuningfork::CProtobufSerialization_Free(&a);
+        }
+        TuningFork_setUploadCallback(UploadCallback);
+        SetAnnotations();
     }
 }
 
diff --git a/samples/tuningfork/tftestapp/build.gradle b/samples/tuningfork/tftestapp/build.gradle
index b9017fb..798ee14 100644
--- a/samples/tuningfork/tftestapp/build.gradle
+++ b/samples/tuningfork/tftestapp/build.gradle
@@ -7,7 +7,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.0-beta05'
+        classpath 'com.android.tools.build:gradle:3.3.2'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/src/swappy/Swappy.cpp b/src/swappy/Swappy.cpp
index fa4ea2b..d4a32d3 100644
--- a/src/swappy/Swappy.cpp
+++ b/src/swappy/Swappy.cpp
@@ -78,6 +78,12 @@
             displayClass,
             "getAppVsyncOffsetNanos", "()J");
 
+    // getAppVsyncOffsetNanos was only added in API 21.
+    // Return gracefully if this device doesn't support it.
+    if (getAppVsyncOffsetNanos == 0 || env->ExceptionOccurred()) {
+        env->ExceptionClear();
+        return;
+    }
     const long appVsyncOffsetNanos = env->CallLongMethod(display, getAppVsyncOffsetNanos);
 
     jmethodID getPresentationDeadlineNanos = env->GetMethodID(
diff --git a/src/swappyVk/SwappyVk.cpp b/src/swappyVk/SwappyVk.cpp
index cc6877d..9c0a5a3 100644
--- a/src/swappyVk/SwappyVk.cpp
+++ b/src/swappyVk/SwappyVk.cpp
@@ -199,12 +199,14 @@
     PFN_AChoreographer_postFrameCallbackDelayed mAChoreographer_postFrameCallbackDelayed = nullptr;
 
     long mFrameID = 0;
-    long mLastframeTimeNanos = 0;
+    long mTargetFrameID = 0;
+    uint64_t mLastframeTimeNanos = 0;
     long mSumRefreshTime = 0;
     long mSamples = 0;
+    long mCallbacksBeforeIdle = 0;
 
-    static constexpr int CHOREOGRAPHER_THRESH = 1000;
     static constexpr int MAX_SAMPLES = 5;
+    static constexpr int MAX_CALLBACKS_BEFORE_IDLE = 10;
 
     void initGoogExtention()
     {
@@ -222,7 +224,8 @@
     void *looperThread();
     static void frameCallback(long frameTimeNanos, void *data);
     void onDisplayRefresh(long frameTimeNanos);
-    void calcRefreshRate(long frameTimeNanos);
+    void calcRefreshRate(uint64_t currentTime);
+    void postChoreographerCallback();
 };
 
 void SwappyVkBase::startChoreographerThread() {
@@ -279,24 +282,37 @@
 
 void SwappyVkBase::onDisplayRefresh(long frameTimeNanos) {
     std::lock_guard<std::mutex> lock(mWaitingMutex);
-    calcRefreshRate(frameTimeNanos);
-    mLastframeTimeNanos = frameTimeNanos;
+    struct timespec currTime;
+    clock_gettime(CLOCK_MONOTONIC, &currTime);
+    uint64_t currentTime =
+            ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec;
+
+    calcRefreshRate(currentTime);
+    mLastframeTimeNanos = currentTime;
     mFrameID++;
     mWaitingCondition.notify_all();
+
+    // queue the next frame callback
+    if (mCallbacksBeforeIdle > 0) {
+        mCallbacksBeforeIdle--;
+        mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+    }
 }
 
-void SwappyVkBase::calcRefreshRate(long frameTimeNanos) {
-  long refresh_nano = std::abs(frameTimeNanos - mLastframeTimeNanos);
+void SwappyVkBase::postChoreographerCallback() {
+    if (mCallbacksBeforeIdle == 0) {
+        mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+    }
+    mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE;
+}
+
+void SwappyVkBase::calcRefreshRate(uint64_t currentTime) {
+    long refresh_nano = currentTime - mLastframeTimeNanos;
 
     if (mRefreshDur != 0 || mLastframeTimeNanos == 0) {
         return;
     }
 
-    // ignore wrap around
-    if (mLastframeTimeNanos > frameTimeNanos) {
-        return;
-    }
-
     mSumRefreshTime += refresh_nano;
     mSamples++;
 
@@ -662,7 +678,10 @@
     mPendingSync[queue].pop_front();
     mWaitingCondition.wait(lock, [&]() {
         if (vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, 0) == VK_TIMEOUT) {
-            mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+            postChoreographerCallback();
+
+            // adjust the target frame here as we are waiting additional frame for the fence
+            mTargetFrameID++;
             return false;
         }
         return true;
@@ -681,19 +700,11 @@
         return ret;
     }
 
-    struct timespec currTime;
-    clock_gettime(CLOCK_MONOTONIC, &currTime);
-    uint64_t currentTime =
-            ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec;
-
-    // do we have something in the queue ?
-    if (mNextDesiredPresentTime > currentTime) {
+    {
         std::unique_lock<std::mutex> lock(mWaitingMutex);
-        long target = mFrameID + mInterval;
         mWaitingCondition.wait(lock, [&]() {
-            if (mFrameID < target) {
-                // wait for the next frame as this frame is too soon
-                mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+            if (mFrameID < mTargetFrameID) {
+                postChoreographerCallback();
                 return false;
             }
             return true;
@@ -704,10 +715,12 @@
         waitForFenceChoreographer(queue);
     }
 
-    clock_gettime(CLOCK_MONOTONIC, &currTime);
-    currentTime =
-            ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec;
-    mNextDesiredPresentTime = currentTime + mRefreshDur * mInterval;
+    // Adjust the presentation time based on the current frameID we are at.
+    if(mFrameID < mTargetFrameID) {
+        ALOGE("Bad frame ID %ld < target %ld", mFrameID, mTargetFrameID);
+        mTargetFrameID = mFrameID;
+    }
+    mNextDesiredPresentTime += (mFrameID - mTargetFrameID) * mRefreshDur;
 
     // Setup the new structures to pass:
     VkPresentTimeGOOGLE pPresentTimes[pPresentInfo->swapchainCount];
@@ -750,6 +763,11 @@
                                                pPresentInfo->pImageIndices, pPresentInfo->pResults};
     ret = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
 
+    // next present time is going to be 2 intervals from now, leaving 1 interval for cpu work
+    // and 1 interval for gpu work
+    mNextDesiredPresentTime = mLastframeTimeNanos + 2 * mRefreshDur * mInterval;
+    mTargetFrameID = mFrameID + mInterval;
+
     return ret;
 }
 
@@ -785,7 +803,7 @@
         std::unique_lock<std::mutex> lock(mWaitingMutex);
         mWaitingCondition.wait(lock, [&]() {
             if (mRefreshDur == 0) {
-                mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+                postChoreographerCallback();
                 return false;
             }
             return true;
@@ -804,19 +822,17 @@
                                     const VkPresentInfoKHR* pPresentInfo) override
     {
         {
-            const long target = mFrameID + mInterval;
             std::unique_lock<std::mutex> lock(mWaitingMutex);
 
-
             mWaitingCondition.wait(lock, [&]() {
-                if (mFrameID < target) {
-                    // wait for the next frame as this frame is too soon
-                    mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
+                if (mFrameID < mTargetFrameID) {
+                    postChoreographerCallback();
                     return false;
                 }
                 return true;
             });
         }
+        mTargetFrameID = mFrameID + mInterval;
         return mpfnQueuePresentKHR(queue, pPresentInfo);
     }
 };
diff --git a/src/tuningfork/clearcutserializer.cpp b/src/tuningfork/clearcutserializer.cpp
index 481e042..fc11b30 100644
--- a/src/tuningfork/clearcutserializer.cpp
+++ b/src/tuningfork/clearcutserializer.cpp
@@ -45,11 +45,8 @@
     std::vector<uint64_t>* v = static_cast<std::vector<uint64_t>*>(*arg);
     // Encode each item
     for (int i = 0; i < v->size(); ++i) {
-        if(!pb_encode_tag(stream, PB_WT_STRING,
-                          logs_proto_tuningfork_DeviceInfo_cpu_max_freq_hz_tag))
-            return false;
-        if(!pb_encode_varint(stream, (*v)[i]))
-            return false;
+        pb_encode_tag_for_field(stream, field);
+        pb_encode_varint(stream, (*v)[i]);
     }
     return true;
 }
@@ -136,6 +133,8 @@
     evt.apk_package_name.arg = (void*)&info.apk_package_name;
     evt.has_apk_version_code = true;
     evt.apk_version_code = info.apk_version_code;
+    evt.has_tuningfork_version = true;
+    evt.tuningfork_version = info.tuningfork_version;
 }
 
 void ClearcutSerializer::FillHistograms(const ProngCache& pc, TuningForkLogEvent &evt) {
diff --git a/src/tuningfork/proto/tuningfork_clearcut_log.proto b/src/tuningfork/proto/tuningfork_clearcut_log.proto
index 648bdd5..813914c 100644
--- a/src/tuningfork/proto/tuningfork_clearcut_log.proto
+++ b/src/tuningfork/proto/tuningfork_clearcut_log.proto
@@ -48,6 +48,9 @@
 
   // Version code from APK manifest.
   optional int32 apk_version_code = 7;
+
+  // Tuning fork version (upper 16 bits: major, lower 16 bits minor)
+  optional int32 tuningfork_version = 8;
 }
 
 message TuningForkHistogram {
diff --git a/src/tuningfork/tools/validation/build.gradle b/src/tuningfork/tools/validation/build.gradle
new file mode 100644
index 0000000..21b69de
--- /dev/null
+++ b/src/tuningfork/tools/validation/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'java'
+apply plugin: 'com.google.protobuf'
+
+repositories {
+    mavenCentral()
+    google()
+    jcenter()
+}
+
+sourceSets {
+    main {
+        proto {
+            include '../../proto/tuningfork.proto'
+        }
+    }
+    test {
+        java {
+            exclude '**'
+        }
+    }
+}
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
+    }
+}
+
+dependencies {
+    compile 'com.google.guava:guava:26.0-jre'
+    compile 'com.google.protobuf:protobuf-java:3.5.1'
+    compile 'com.google.flogger:flogger:0.3.1'
+    compile 'com.google.flogger:flogger-system-backend:0.3.1'
+    compile 'com.beust:jcommander:1.7'
+
+    testCompile 'junit:junit:4.12'
+    testCompile 'com.google.truth:truth:0.43'
+    testCompile 'org.junit.jupiter:junit-jupiter:5.4.1'
+
+    protobuf files('../../proto/tuningfork.proto')
+}
+
+protobuf {
+    protoc {
+        //Download from repo
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+    }
+}
+
+task createJar(type: Jar) {
+    manifest {
+        attributes 'Implementation-Title': 'Gradle Jar',
+            'Implementation-Version': 1.0,
+            'Main-Class': 'com.google.tuningfork.validation.TuningforkApkValidationTool'
+    }
+    baseName = 'TuningforkApkValidationTool'
+    from {
+        configurations.compile.collect {
+            it.isDirectory()? it:zipTree(it)
+        }
+    }
+    with jar
+}
\ No newline at end of file
diff --git a/src/tuningfork/tools/validation/gradlew b/src/tuningfork/tools/validation/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/src/tuningfork/tools/validation/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/src/tuningfork/tools/validation/gradlew.bat b/src/tuningfork/tools/validation/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/src/tuningfork/tools/validation/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ApkConfig.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ApkConfig.java
index fd35f3b..347fa76 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ApkConfig.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ApkConfig.java
@@ -25,6 +25,8 @@
   public static final String ASSETS_DIRECTORY = "assets/tuningfork/";
 
   public static final String DEV_TUNINGFORK_PROTO = ASSETS_DIRECTORY + "dev_tuningfork.proto";
+  public static final String DEV_TUNINGFORK_PROTO_DESCRIPTOR =
+      ASSETS_DIRECTORY + "dev_tuningfork.descriptor";
   public static final String TUNINGFORK_SETTINGS = ASSETS_DIRECTORY + "tuningfork_settings.bin";
   public static final Pattern DEV_FIDELITY_PATTERN =
       Pattern.compile(ASSETS_DIRECTORY + "dev_tuningfork_fidelityparams_.{1,15}.bin");
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/DeveloperTuningforkParser.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/DeveloperTuningforkParser.java
index dab5889..d1c447e 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/DeveloperTuningforkParser.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/DeveloperTuningforkParser.java
@@ -19,10 +19,8 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.flogger.FluentLogger;
-import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FileDescriptor;
@@ -30,45 +28,44 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Optional;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
+import java.util.regex.Pattern;
 
-/** Tuningfork validation tool.
- * Parses proto and settings files and validates them. */
+/** Tuningfork validation tool. Parses proto and settings files and validates them. */
 public class DeveloperTuningforkParser {
 
-  private Optional<String> devTuningfork = Optional.empty();
-  private Optional<ByteString> tuningforkSettings = Optional.empty();
-  private final ListMultimap<String, ByteString> devFidelityParams = LinkedListMultimap.create();
+  private Optional<File> devTuningforkProto = Optional.empty();
+  private Optional<String> tuningforkSettings = Optional.empty();
+  private ImmutableList<File> devFidelityFiles;
 
   private final ErrorCollector errors;
-  private final File protocBinary;
+  private final ExternalProtoCompiler compiler;
+  private final File folder;
 
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  public DeveloperTuningforkParser(ErrorCollector errors, File protocBinary) {
+  public DeveloperTuningforkParser(ErrorCollector errors, File folder, File protocBinary) {
     this.errors = errors;
-    this.protocBinary = protocBinary;
+    this.folder = folder;
+    this.compiler = new ExternalProtoCompiler(protocBinary);
   }
 
-  public void parseJarEntry(JarFile apk, JarEntry file) throws IOException {
-    if (file.getName().equals(ApkConfig.DEV_TUNINGFORK_PROTO)) {
-      String content = new String(ByteStreams.toByteArray(apk.getInputStream(file)), UTF_8);
-      devTuningfork = Optional.of(content);
-    } else if (file.getName().equals(ApkConfig.TUNINGFORK_SETTINGS)) {
-      ByteString content = ByteString.readFrom(apk.getInputStream(file));
-      tuningforkSettings = Optional.of(content);
-    } else if (ApkConfig.DEV_FIDELITY_PATTERN.matcher(file.getName()).find()) {
-      ByteString content = ByteString.readFrom(apk.getInputStream(file));
-      devFidelityParams.put(file.getName(), content);
-    }
+  public void parseFilesInFolder() throws IOException {
+    devTuningforkProto = findDevTuningforkProto(folder);
+    tuningforkSettings = findTuningforkSettings(folder);
+    devFidelityFiles = findDevFidelityParams(folder);
   }
 
-  /** Validate protos and settings.*/
   public void validate() throws IOException, CompilationException {
+    if (devTuningforkProto.isPresent()) {
+      logger.atInfo().log("File %s exists: OK", FolderConfig.DEV_TUNINGFORK_PROTO);
+    } else {
+      logger.atSevere().log("File %s exists: FAIL", FolderConfig.DEV_TUNINGFORK_PROTO);
+    }
+
+    File descriptorFile = new File(folder, FolderConfig.DEV_TUNINGFORK_DESCRIPTOR);
 
     FileDescriptor devTuningforkFileDesc =
-        ProtoHelper.compileContent(devTuningfork.get(), protocBinary);
+        compiler.compile(devTuningforkProto.get(), Optional.of(descriptorFile));
     Descriptor annotationField = devTuningforkFileDesc.findMessageTypeByName("Annotation");
     Descriptor fidelityField = devTuningforkFileDesc.findMessageTypeByName("FidelityParams");
 
@@ -79,16 +76,118 @@
     logger.atInfo().log("Loaded Annotation message:\n" + annotationField.toProto());
     logger.atInfo().log("Loaded FidelityParams message:\n" + fidelityField.toProto());
 
-    // Validate settings only if annotations are valid
-    if (!errors.hasAnnotationErrors()) {
-      ValidationUtil.validateSettings(enumSizes, tuningforkSettings.get(), errors);
-      Settings settings = Settings.parseFrom(tuningforkSettings.get());
-      logger.atInfo().log("Loaded settings:\n" + settings);
+    validateAndSaveBinarySettings(enumSizes);
+    encodeBinaryAndValidateDevFidelity(fidelityField);
+  }
+
+  private void validateAndSaveBinarySettings(ImmutableList<Integer> enumSizes) {
+    if (tuningforkSettings.isPresent()) {
+      logger.atInfo().log("File %s exists: OK", FolderConfig.TUNINGFORK_SETTINGS_TEXTPROTO);
+    } else {
+      logger.atSevere().log("File %s exists: FAIL", FolderConfig.TUNINGFORK_SETTINGS_TEXTPROTO);
     }
 
-    // Validate devFidelityOnly only if fidelity parameters are valid
-    if (!errors.hasFidelityParamsErrors()) {
-      ValidationUtil.validateDevFidelityParams(devFidelityParams.values(), fidelityField, errors);
+    if (errors.hasAnnotationErrors()) {
+      logger.atSevere().log(
+          "Skip %s file check as Annotation is not valid",
+          FolderConfig.TUNINGFORK_SETTINGS_TEXTPROTO);
     }
+
+    Optional<Settings> settings =
+        ValidationUtil.validateSettings(enumSizes, tuningforkSettings.get(), errors);
+
+    if (errors.hasSettingsErrors() || !settings.isPresent()) {
+      logger.atSevere().log(
+          "Skip saving %s file as %s contains errors",
+          FolderConfig.TUNINGFORK_SETTINGS_BINARY, FolderConfig.TUNINGFORK_SETTINGS_TEXTPROTO);
+    }
+
+    logger.atInfo().log("Loaded settings:\n" + settings.get());
+
+    saveBinarySettings(settings.get());
+  }
+
+  private void saveBinarySettings(Settings settings) {
+    File outFile = new File(folder, FolderConfig.TUNINGFORK_SETTINGS_BINARY);
+    try {
+      Files.write(settings.toByteArray(), outFile);
+    } catch (IOException e) {
+      logger.atSevere().withCause(e).log(
+          "Error writing settings to %s file", FolderConfig.TUNINGFORK_SETTINGS_BINARY);
+    }
+  }
+
+  private void encodeBinaryAndValidateDevFidelity(Descriptor fidelityField) {
+    if (!devFidelityFiles.isEmpty()) {
+      logger.atInfo().log(
+          "%d %s files found: OK\n %s",
+          devFidelityFiles.size(),
+          FolderConfig.DEV_FIDELITY_TEXTPROTO,
+          devFidelityFiles.toString());
+    } else {
+      logger.atSevere().log("%s files found: FAIL", FolderConfig.DEV_FIDELITY_TEXTPROTO);
+    }
+
+    if (errors.hasFidelityParamsErrors()) {
+      logger.atSevere().log(
+          "Skip %s files check as FidelityParams message is not valid",
+          FolderConfig.DEV_FIDELITY_TEXTPROTO);
+    }
+
+    devFidelityFiles.forEach(
+        textprotoFile -> encodeBinaryAndValidateDevFidelity(fidelityField, textprotoFile));
+  }
+
+  private void encodeBinaryAndValidateDevFidelity(Descriptor fidelityField, File textprotoFile) {
+    File binaryFile;
+    try {
+      binaryFile =
+          compiler.encodeFromTextprotoFile(
+              fidelityField.getFullName(),
+              devTuningforkProto.get(),
+              textprotoFile,
+              getBinaryPathForTextprotoPath(textprotoFile),
+              Optional.empty());
+    } catch (IOException | CompilationException e) {
+      errors.addError(
+          ErrorType.DEV_FIDELITY_PARAMETERS_ENCODING,
+          String.format("Encoding %s file", textprotoFile.getName()),
+          e);
+      return;
+    }
+    ByteString content;
+    try {
+      content = ByteString.copyFrom(Files.toByteArray(binaryFile));
+    } catch (IOException e) {
+      errors.addError(
+          ErrorType.DEV_FIDELITY_PARAMETERS_READING,
+          String.format("Reading %s file", binaryFile.getName()),
+          e);
+      return;
+    }
+    ValidationUtil.validateDevFidelityParams(content, fidelityField, errors);
+  }
+
+  private static String getBinaryPathForTextprotoPath(File textprotoFile) {
+    return textprotoFile.getParentFile().getAbsolutePath()
+        + "/"
+        + textprotoFile.getName().replace(".txt", ".bin");
+  }
+
+  private static Optional<File> findDevTuningforkProto(File folder) throws IOException {
+    File file = new File(folder, FolderConfig.DEV_TUNINGFORK_PROTO);
+    return Optional.of(file);
+  }
+
+  private static Optional<String> findTuningforkSettings(File folder) throws IOException {
+    File file = new File(folder, FolderConfig.TUNINGFORK_SETTINGS_TEXTPROTO);
+    String content = Files.asCharSource(file, UTF_8).read();
+    return Optional.of(content);
+  }
+
+  private static ImmutableList<File> findDevFidelityParams(File folder) throws IOException {
+    Pattern devFidelityPattern = Pattern.compile(FolderConfig.DEV_FIDELITY_TEXTPROTO);
+    return ImmutableList.copyOf(
+        folder.listFiles((dir, filename) -> devFidelityPattern.matcher(filename).find()));
   }
 }
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorCollector.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorCollector.java
index 95cafb8..05f1a44 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorCollector.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorCollector.java
@@ -30,9 +30,13 @@
 
   void printStatus();
 
+  Boolean hasErrors(ErrorType.ErrorGroup group);
+
   Boolean hasAnnotationErrors();
 
   Boolean hasFidelityParamsErrors();
 
+  Boolean hasSettingsErrors();
+
   Multimap<ErrorType, String> getErrors();
 };
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorType.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorType.java
index ca7f8ec..c72dd27 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorType.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ErrorType.java
@@ -18,23 +18,52 @@
 
 /** Validation errors */
 public enum ErrorType {
-  ANNOTATION_EMPTY, // Annotation field is empty
-  ANNOTATION_COMPLEX, // Annotation field is too complex - contains oneofs/nestedtypes/extensions
-  ANNOTATION_TYPE, // Annotation must contains enums only
+  // Annotation field is empty
+  ANNOTATION_EMPTY(ErrorGroup.ANNOTATION),
+  // Annotation field is too complex - contains oneofs/nestedtypes/extensions
+  ANNOTATION_COMPLEX(ErrorGroup.ANNOTATION),
+  // Annotation must contains enums only
+  ANNOTATION_TYPE(ErrorGroup.ANNOTATION),
+  // FidelityParams fied is empty
+  FIDELITY_PARAMS_EMPTY(ErrorGroup.FIDELITY),
+  // FidelityParams field is complex - contains  oneof/nestedtypes/extensions
+  FIDELITY_PARAMS_COMPLEX(ErrorGroup.FIDELITY),
+  // FidelityParams can only contains float, int32 or enum
+  FIDELITY_PARAMS_TYPE(ErrorGroup.FIDELITY),
+  // Fidelity parameters are empty
+  DEV_FIDELITY_PARAMETERS_EMPTY(ErrorGroup.DEV_FIDELITY),
+  // Fidelity parameters parsing error
+  DEV_FIDELITY_PARAMETERS_PARSING(ErrorGroup.DEV_FIDELITY),
+  // Fidelity parameters encoding textproto file
+  DEV_FIDELITY_PARAMETERS_ENCODING(ErrorGroup.DEV_FIDELITY),
+  // Fidelity parameters reading file
+  DEV_FIDELITY_PARAMETERS_READING(ErrorGroup.DEV_FIDELITY),
+  // Parsing error
+  SETTINGS_PARSING(ErrorGroup.SETTINGS),
+  // Histogram field is empty
+  HISTOGRAM_EMPTY(ErrorGroup.SETTINGS),
+  // Aggreagtion field is empty
+  AGGREGATION_EMPTY(ErrorGroup.SETTINGS),
+  // Aggregation contains incorrect  max_instrumentation_keys field
+  AGGREGATION_INSTRUMENTATION_KEY(ErrorGroup.SETTINGS),
+  // Aggregation contains incorrect annotation_enum_sizes
+  AGGREGATION_ANNOTATIONS(ErrorGroup.SETTINGS);
 
-  FIDELITY_PARAMS_EMPTY, // FidelityParams fied is empty
-  FIDELITY_PARAMS_COMPLEX, // FidelityParams field is complex - contains
-                           // oneof/nestedtypes/extensions
-  FIDELITY_PARAMS_TYPE, // FidelityParams can only contains float, int32 or enum
+  private final ErrorGroup group;
 
-  DEV_FIDELITY_PARAMETERS_EMPTY, // Fidelity parameters are empty
-  DEV_FIDELITY_PARAMETERS_PARSING, // Fidelity parameters parsing error
+  public ErrorGroup getGroup() {
+    return group;
+  }
 
-  SETTINGS_PARSING, // Parsing error
+  ErrorType(ErrorGroup group) {
+    this.group = group;
+  }
 
-  HISTOGRAM_EMPTY, // Histogram field is empty
-
-  AGGREGATION_EMPTY, // Aggreagtion field is empty
-  AGGREGATION_INSTRUMENTATION_KEY, // Aggregation contains incorrect max_instrumentation_keys field
-  AGGREGATION_ANNOTATIONS, // Aggregation contains incorrect annotation_enum_sizes
+  /** Validation group of errors */
+  public enum ErrorGroup {
+    ANNOTATION,
+    FIDELITY,
+    DEV_FIDELITY,
+    SETTINGS,
+  }
 };
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ExternalProtoCompiler.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ExternalProtoCompiler.java
index b35e667..d8f8c84 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ExternalProtoCompiler.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ExternalProtoCompiler.java
@@ -1,9 +1,24 @@
-// Copyright 2009 Google Inc. All Rights Reserved.
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
 
 package com.google.tuningfork.validation;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
 import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
 import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
 import com.google.protobuf.Descriptors.DescriptorValidationException;
@@ -12,7 +27,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.List;
+import java.util.Optional;
 
 /** Compiles .proto file into a {@link FileDescriptor} */
 public class ExternalProtoCompiler {
@@ -25,14 +40,15 @@
     protoPath = protoBinary.getAbsolutePath();
   }
 
-  public FileDescriptor compile(File file) throws IOException, CompilationException {
+  public FileDescriptor compile(File file, Optional<File> outFile)
+      throws IOException, CompilationException {
     Preconditions.checkNotNull(file, "file");
-    FileDescriptor descriptor = buildAndRunCompilerProcess(file);
+    FileDescriptor descriptor = buildAndRunCompilerProcess(file, outFile);
     return descriptor;
   }
 
-  public byte[] runCommand(List<String> commandLine) throws IOException, CompilationException {
-    Process process = new ProcessBuilder(commandLine).start();
+  public byte[] runCommand(ProcessBuilder processBuilder) throws IOException, CompilationException {
+    Process process = processBuilder.start();
     try {
       process.waitFor();
     } catch (InterruptedException e) {
@@ -44,13 +60,16 @@
     return result;
   }
 
-  private FileDescriptor buildAndRunCompilerProcess(File file)
+  private FileDescriptor buildAndRunCompilerProcess(File file, Optional<File> outFile)
       throws IOException, CompilationException {
-    List<String> commandLine = createCommandLine(file);
-    byte[] result = runCommand(commandLine);
+    ImmutableList<String> commandLine = createCommandLine(file);
+    byte[] result = runCommand(new ProcessBuilder(commandLine));
 
     try {
       FileDescriptorSet fileSet = FileDescriptorSet.parseFrom(result);
+      if (outFile.isPresent()) {
+        Files.write(fileSet.toByteArray(), outFile.get());
+      }
       for (FileDescriptorProto descProto : fileSet.getFileList()) {
         if (descProto.getName().equals(file.getName())) {
           return FileDescriptor.buildFrom(descProto, emptyDeps);
@@ -63,7 +82,49 @@
         String.format("Descriptor for [%s] does not exist.", file.getName()));
   }
 
-  private List<String> createCommandLine(File file) {
+  /* Decode textproto message from text(textprotoFile) into binary(binFile) */
+  public File encodeFromTextprotoFile(
+      String message,
+      File protoFile,
+      File textprotoFile,
+      String binaryPath,
+      Optional<File> errorFile)
+      throws IOException, CompilationException {
+    ImmutableList<String> command = encodeCommandLine(message, protoFile);
+
+    File binFile = new File(binaryPath);
+
+    ProcessBuilder builder =
+        new ProcessBuilder(command).redirectInput(textprotoFile).redirectOutput(binFile);
+
+    if (errorFile.isPresent()) {
+      builder.redirectError(errorFile.get());
+    }
+
+    runCommand(builder);
+    return binFile;
+  }
+
+  /* Decode textproto message from binary(binFile) into text(textFile) */
+  public File decodeToTextprotoFile(
+      String message, File protoFile, String textprotoPath, File binFile, Optional<File> errorFile)
+      throws IOException, CompilationException {
+    ImmutableList<String> command = decodeCommandLine(message, protoFile);
+
+    File textprotoFile = new File(textprotoPath);
+
+    ProcessBuilder builder =
+        new ProcessBuilder(command).redirectInput(binFile).redirectOutput(textprotoFile);
+
+    if (errorFile.isPresent()) {
+      builder.redirectError(errorFile.get());
+    }
+
+    runCommand(builder);
+    return textprotoFile;
+  }
+
+  private ImmutableList<String> createCommandLine(File file) {
     return ImmutableList.of(
         protoPath,
         "-o",
@@ -72,4 +133,22 @@
         file.getName() + "=" + file.getAbsolutePath(), // That should be one line
         file.getName());
   }
+
+  private ImmutableList<String> encodeCommandLine(String message, File protoFile) {
+    return ImmutableList.of(
+        protoPath,
+        "--encode=" + message,
+        "-I",
+        protoFile.getName() + "=" + protoFile.getAbsolutePath(), // That should be one line
+        protoFile.getName());
+  }
+
+  private ImmutableList<String> decodeCommandLine(String message, File protoFile) {
+    return ImmutableList.of(
+        protoPath,
+        "--decode=" + message,
+        "-I",
+        protoFile.getName() + "=" + protoFile.getAbsolutePath(), // That should be one line
+        protoFile.getName());
+  }
 }
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/FolderConfig.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/FolderConfig.java
new file mode 100644
index 0000000..f3c2615
--- /dev/null
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/FolderConfig.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.tuningfork.validation;
+
+/** Settings and proto file names for assets/tuningfork folder */
+final class FolderConfig {
+
+  public static final String DEV_TUNINGFORK_PROTO = "dev_tuningfork.proto";
+  public static final String DEV_TUNINGFORK_DESCRIPTOR = "dev_tuningfork.descriptor";
+  public static final String TUNINGFORK_SETTINGS_TEXTPROTO = "tuningfork_settings.txt";
+  public static final String TUNINGFORK_SETTINGS_BINARY = "tuningfork_settings.bin";
+  public static final String DEV_FIDELITY_TEXTPROTO = "dev_tuningfork_fidelityparams_.{1,15}.txt";
+}
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ParserErrorCollector.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ParserErrorCollector.java
index 547e27f..4da322c 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ParserErrorCollector.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ParserErrorCollector.java
@@ -56,31 +56,42 @@
   public void printStatus() {
     StringBuilder builder = new StringBuilder();
     for (ErrorType errorType : ErrorType.values()) {
-      builder.append(errorType).append(" : ");
       int errorCount = errors.get(errorType).size();
-      if (errorCount == 0) {
-        builder.append("OK");
-      } else {
-        builder.append(errorCount);
-        builder.append(" ERRORS\n\t");
-        builder.append(errors.get(errorType));
+      if (errorCount != 0) {
+        builder
+            .append(errorType)
+            .append(" : ")
+            .append(errorCount)
+            .append(" ERRORS\n\t")
+            .append(errors.get(errorType))
+            .append("\n");
       }
-      builder.append("\n");
     }
-    logger.atInfo().log(builder.toString());
+    logger.atWarning().log(builder.toString());
+  }
+
+  @Override
+  public Boolean hasErrors(ErrorType.ErrorGroup group) {
+    for (ErrorType errorType : ErrorType.values()) {
+      if (errorType.getGroup() == group && errors.containsKey(errorType)) {
+        return true;
+      }
+    }
+    return false;
   }
 
   @Override
   public Boolean hasAnnotationErrors() {
-    return errors.containsKey(ErrorType.ANNOTATION_EMPTY)
-        || errors.containsKey(ErrorType.ANNOTATION_COMPLEX)
-        || errors.containsKey(ErrorType.ANNOTATION_TYPE);
+    return hasErrors(ErrorType.ErrorGroup.ANNOTATION);
   }
 
   @Override
   public Boolean hasFidelityParamsErrors() {
-    return errors.containsKey(ErrorType.FIDELITY_PARAMS_EMPTY)
-        || errors.containsKey(ErrorType.FIDELITY_PARAMS_COMPLEX)
-        || errors.containsKey(ErrorType.FIDELITY_PARAMS_TYPE);
+    return hasErrors(ErrorType.ErrorGroup.FIDELITY);
+  }
+
+  @Override
+  public Boolean hasSettingsErrors() {
+    return hasErrors(ErrorType.ErrorGroup.SETTINGS);
   }
 }
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ProtoHelper.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ProtoHelper.java
deleted file mode 100644
index 855944d..0000000
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ProtoHelper.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.google.tuningfork.validation;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.io.Files;
-import com.google.protobuf.Descriptors.FileDescriptor;
-import java.io.File;
-import java.io.IOException;
-
-/** Compile {@link FileDescriptor} from proto file content */
-final class ProtoHelper {
-
-  private static final String TEMP_FILE_NAME = "tempfile.proto";
-
-  private ProtoHelper() {}
-
-  public static FileDescriptor compileContent(String content, File protocBinary)
-      throws IOException, CompilationException {
-    File file = File.createTempFile(TEMP_FILE_NAME, null);
-    Files.asCharSink(file, UTF_8).write(content);
-    ExternalProtoCompiler compiler = new ExternalProtoCompiler(protocBinary);
-    FileDescriptor desc = compiler.compile(file);
-    file.delete();
-    return desc;
-  }
-}
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/TuningforkApkValidationTool.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/TuningforkApkValidationTool.java
index 3081147..b3bf50b 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/TuningforkApkValidationTool.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/TuningforkApkValidationTool.java
@@ -18,79 +18,76 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
 import com.google.common.base.Strings;
-import com.google.common.flags.Flag;
-import com.google.common.flags.FlagSpec;
-import com.google.common.flags.Flags;
 import com.google.common.flogger.FluentLogger;
 import java.io.File;
 import java.io.IOException;
-import java.util.Enumeration;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
 
 /** APK Validation tool for Tuningfork */
 final class TuningforkApkValidationTool {
+
+  private static class Parameters {
+    @Parameter(
+        names = {"--tuningforkPath"},
+        description = "Path to an assets/tuningfork folder")
+    public String tuningforkPath;
+
+    @Parameter(
+        names = {"--protoCompiler"},
+        description = "Path to protoc binary")
+    public String protoCompiler;
+  }
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  @FlagSpec(help = "Path to apk file")
-  public static final Flag<String> apkPath = Flag.nullString();
-
-  @FlagSpec(help = "Path to proto compiler")
-  public static final Flag<String> protoCompiler = Flag.nullString();
-
   public static void main(String[] args) {
-    Flags.parse(args);
+    Parameters parameters = new Parameters();
+    new JCommander(parameters, args);
 
     checkArgument(
-        !Strings.isNullOrEmpty(apkPath.get()),
-        "You need to specify path to your apk file --apkPath");
+        !Strings.isNullOrEmpty(parameters.tuningforkPath),
+        "You need to specify path to your tuningfork settings folder --tuningforkPath");
 
     checkArgument(
-        !Strings.isNullOrEmpty(protoCompiler.get()),
+        !Strings.isNullOrEmpty(parameters.protoCompiler),
         "You need to specify path to proto compiler --protoCompiler");
 
-    File apkFile = new File(apkPath.get());
-    if (!apkFile.exists()) {
-      logger.atSevere().log("APK File does not exist %s", apkPath.get());
-      return;
+    File tuningforkFolder = new File(parameters.tuningforkPath);
+    if (!tuningforkFolder.exists()) {
+      logger.atSevere().log(
+          "Tuningfork settings folder does not exist %s", parameters.tuningforkPath);
     }
 
-    File protoCompilerFile = new File(protoCompiler.get());
+    if (!tuningforkFolder.isDirectory()) {
+      logger.atSevere().log(
+          "--tuningforkPath=[%s] is not a path to a folder", parameters.tuningforkPath);
+    }
+
+    File protoCompilerFile = new File(parameters.protoCompiler);
     if (!protoCompilerFile.exists()) {
-      logger.atSevere().log("Proto compiler file does not exist %s", protoCompiler.get());
-      return;
+      logger.atSevere().log("Proto compiler file does not exist %s", parameters.protoCompiler);
     }
 
-    logger.atInfo().log("Start validation of %s...", apkFile.getName());
-
-    JarFile jarApk;
-
-    try {
-      jarApk = new JarFile(apkFile);
-    } catch (IOException e) {
-      logger.atSevere().withCause(e).log("Can not open apk file %s", apkFile.getName());
-      return;
+    if (!protoCompilerFile.isFile() || !protoCompilerFile.canExecute()) {
+      logger.atSevere().log(
+          "--protoCompiler=[%s] is not a path to an executable file", parameters.protoCompiler);
     }
 
+    logger.atInfo().log("Start validation of %s...", tuningforkFolder.getPath());
+
     ErrorCollector errors = new ParserErrorCollector();
-    DeveloperTuningforkParser parser = new DeveloperTuningforkParser(errors, protoCompilerFile);
-
-    Enumeration<JarEntry> apkFiles = jarApk.entries();
-    while (apkFiles.hasMoreElements()) {
-      JarEntry file = apkFiles.nextElement();
-      try {
-        parser.parseJarEntry(jarApk, file);
-      } catch (Exception e) {
-        logger.atWarning().withCause(e).log("Can not parse apk entry %s", file.getName());
-      }
-    }
+    DeveloperTuningforkParser parser =
+        new DeveloperTuningforkParser(errors, tuningforkFolder, protoCompilerFile);
 
     try {
+      parser.parseFilesInFolder();
       parser.validate();
       if (errors.getErrorCount() == 0) {
-        logger.atInfo().log("Apk %s is valid", apkFile.getName());
+        logger.atInfo().log("Tuning Fork settings are valid");
       } else {
+        logger.atWarning().log("Tuning Fork settings are invalid");
         errors.printStatus();
       }
     } catch (IOException | CompilationException e) {
diff --git a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ValidationUtil.java b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ValidationUtil.java
index b41982f..e2f5ce9 100644
--- a/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ValidationUtil.java
+++ b/src/tuningfork/tools/validation/src/main/java/com/google/tuningfork/validation/ValidationUtil.java
@@ -22,13 +22,16 @@
 import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.DynamicMessage;
 import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
 import com.google.tuningfork.Tuningfork.Settings;
 import com.google.tuningfork.Tuningfork.Settings.AggregationStrategy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 
-/** Utility methods for validating Tuning Fork protos and settings.*/
+/** Utility methods for validating Tuning Fork protos and settings. */
 final class ValidationUtil {
 
   private ValidationUtil() {}
@@ -40,6 +43,22 @@
           FieldDescriptor.Type.ENUM, FieldDescriptor.Type.FLOAT, FieldDescriptor.Type.INT32);
 
   /* Validate settings */
+  public static Optional<Settings> validateSettings(
+      List<Integer> enumSizes, String settingsTextProto, ErrorCollector errors) {
+    try {
+      Settings.Builder builder = Settings.newBuilder();
+      TextFormat.merge(settingsTextProto, builder);
+      Settings settings = builder.build();
+      validateSettingsAggregation(settings, enumSizes, errors);
+      validateSettingsHistograms(settings, errors);
+      return Optional.of(settings);
+    } catch (ParseException e) {
+      errors.addError(ErrorType.SETTINGS_PARSING, "Parsing tuningfork_settings.txt", e);
+      return Optional.empty();
+    }
+  }
+
+  /* Validate settings */
   public static final void validateSettings(
       List<Integer> enumSizes, ByteString settingsContent, ErrorCollector errors) {
     try {
diff --git a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/DeveloperTuningforkParserTest.java b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/DeveloperTuningforkParserTest.java
new file mode 100644
index 0000000..f57a849
--- /dev/null
+++ b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/DeveloperTuningforkParserTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.google.tuningfork.validation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.io.Files;
+import com.google.devtools.build.runtime.Runfiles;
+import com.google.protobuf.ByteString;
+import com.google.testing.testsize.MediumTest;
+import com.google.tuningfork.DevTuningfork.FidelityParams;
+import com.google.tuningfork.DevTuningfork.QualitySettings;
+import com.google.tuningfork.Tuningfork.Settings;
+import com.google.tuningfork.Tuningfork.Settings.AggregationStrategy;
+import com.google.tuningfork.Tuningfork.Settings.Histogram;
+import java.io.File;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public final class DeveloperTuningforkParserTest {
+
+  @Rule
+  // Override default behavior to allow overwriting files.
+  public TemporaryFolder tempFolder =
+      new TemporaryFolder() {
+        @Override
+        public File newFile(String filename) {
+          return new File(getRoot(), filename);
+        }
+      };
+
+  private final TestdataHelper helper = new TestdataHelper(tempFolder);
+  private final ErrorCollector errors = new ParserErrorCollector();
+
+  private DeveloperTuningforkParser parser;
+  private final Settings settings =
+      Settings.newBuilder()
+          .setAggregationStrategy(
+              AggregationStrategy.newBuilder()
+                  .addAllAnnotationEnumSize(Arrays.asList(2))
+                  .setMaxInstrumentationKeys(100))
+          .addHistograms(Histogram.getDefaultInstance())
+          .build();
+
+  private final FidelityParams devParameters1 =
+      FidelityParams.newBuilder()
+          .setIntField(10)
+          .setFloatField(1.5f)
+          .setQualitySettings(QualitySettings.FAST)
+          .build();
+
+  private final FidelityParams devParameters2 = FidelityParams.newBuilder().setIntField(10).build();
+  private final FidelityParams devParameters3 = FidelityParams.getDefaultInstance();
+
+  private static final File PROTOC_BINARY =
+      Runfiles.location("net/proto2/compiler/public/protocol_compiler");
+
+  @Before
+  public void setUp() throws Exception {
+    parser = new DeveloperTuningforkParser(errors, tempFolder.getRoot(), PROTOC_BINARY);
+    helper.getFile("dev_tuningfork.proto");
+    helper.createFile("tuningfork_settings.txt", settings.toString());
+    helper.createFile("dev_tuningfork_fidelityparams_1.txt", devParameters1.toString());
+    helper.createFile("dev_tuningfork_fidelityparams_2.txt", devParameters2.toString());
+    helper.createFile("dev_tuningfork_fidelityparams_3.txt", devParameters3.toString());
+    parser.parseFilesInFolder();
+  }
+
+  @Test
+  public void checkNoErrors() throws Exception {
+    parser.validate();
+    assertThat(errors.getErrorCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void checkSettings() throws Exception {
+    parser.validate();
+    File settingsFile = new File(tempFolder.getRoot(), "tuningfork_settings.bin");
+    ByteString settingsBinary = ByteString.copyFrom(Files.toByteArray(settingsFile));
+    Settings parsed = Settings.parseFrom(settingsBinary);
+
+    assertThat(parsed).isEqualTo(settings);
+    assertThat(errors.hasSettingsErrors()).isFalse();
+  }
+
+  @Test
+  public void checkFidelityParametersFiles() throws Exception {
+    parser.validate();
+    File file1 = new File(tempFolder.getRoot(), "dev_tuningfork_fidelityparams_1.bin");
+    File file2 = new File(tempFolder.getRoot(), "dev_tuningfork_fidelityparams_2.bin");
+    File file3 = new File(tempFolder.getRoot(), "dev_tuningfork_fidelityparams_3.bin");
+    File file4 = new File(tempFolder.getRoot(), "dev_tuningfork_fidelityparams_4.bin");
+
+    assertThat(file1.exists()).isTrue();
+    assertThat(file2.exists()).isTrue();
+    assertThat(file3.exists()).isTrue();
+    assertThat(file4.exists()).isFalse();
+  }
+
+  @Test
+  public void checkFidelityParameters() throws Exception {
+    parser.validate();
+    File devFidelityFile = new File(tempFolder.getRoot(), "dev_tuningfork_fidelityparams_1.bin");
+    ByteString devFidelityBinary = ByteString.copyFrom(Files.toByteArray(devFidelityFile));
+    FidelityParams parsed = FidelityParams.parseFrom(devFidelityBinary);
+
+    assertThat(parsed).isEqualTo(devParameters1);
+    assertThat(errors.hasFidelityParamsErrors()).isFalse();
+  }
+}
diff --git a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ExternalProtoCompilerTest.java b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ExternalProtoCompilerTest.java
index 72d9fc3..314cb40 100644
--- a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ExternalProtoCompilerTest.java
+++ b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ExternalProtoCompilerTest.java
@@ -1,16 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
 package com.google.tuningfork.validation;
 
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertThrows;
 
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.io.Files;
 import com.google.devtools.build.runtime.Runfiles;
+import com.google.protobuf.DescriptorProtos.FileDescriptorSet;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FileDescriptor;
 import java.io.File;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -49,7 +69,7 @@
   public void compileValid() throws Exception {
     File file = helper.getFile("compile_valid.proto");
 
-    FileDescriptor fDesc = compiler.compile(file);
+    FileDescriptor fDesc = compiler.compile(file, Optional.empty());
 
     Descriptor messageDesc = fDesc.findMessageTypeByName("Message");
     Descriptor anotherDesc = fDesc.findMessageTypeByName("AnotherMessage");
@@ -58,10 +78,30 @@
   }
 
   @Test
+  public void compareDescriptors() throws Exception {
+    File file = helper.getFile("compile_valid.proto");
+    File outFile = new File(tempFolder.getRoot(), "compile_valid.descriptor");
+
+    FileDescriptor stdoutDescriptor = compiler.compile(file, Optional.of(outFile));
+
+    Descriptor messageDesc = stdoutDescriptor.findMessageTypeByName("Message");
+    Descriptor anotherDesc = stdoutDescriptor.findMessageTypeByName("AnotherMessage");
+    FileDescriptorSet fileSet = FileDescriptorSet.parseFrom(Files.toByteArray(outFile));
+    FileDescriptor outFileDescriptor =
+        FileDescriptor.buildFrom(
+            Iterables.getOnlyElement(fileSet.getFileList()), new FileDescriptor[] {});
+
+    assertThat(messageDesc).isNotNull();
+    assertThat(anotherDesc).isNotNull();
+    assertThat(outFile).isNotNull();
+    assertThat(stdoutDescriptor.toProto()).isEqualTo(outFileDescriptor.toProto());
+  }
+
+  @Test
   public void compileInvalid() throws Exception {
     File file = helper.getFile("compile_invalid.proto");
     CompilationException expected =
-        assertThrows(CompilationException.class, () -> compiler.compile(file));
+        assertThrows(CompilationException.class, () -> compiler.compile(file, Optional.empty()));
 
     assertThat(expected)
         .hasMessageThat()
@@ -72,7 +112,7 @@
   public void compileWithDeps() throws Exception {
     File file = helper.getFile("compile_with_deps.proto");
     CompilationException expected =
-        assertThrows(CompilationException.class, () -> compiler.compile(file));
+        assertThrows(CompilationException.class, () -> compiler.compile(file, Optional.empty()));
 
     assertThat(expected)
         .hasMessageThat()
@@ -80,9 +120,55 @@
   }
 
   @Test
+  public void compileDevTuningfork() throws Exception {
+    File file = helper.getFile("dev_tuningfork.proto");
+
+    FileDescriptor fDesc = compiler.compile(file, Optional.empty());
+
+    Descriptor annotation = fDesc.findMessageTypeByName("Annotation");
+    Descriptor fidelityParams = fDesc.findMessageTypeByName("FidelityParams");
+    assertThat(annotation).isNotNull();
+    assertThat(fidelityParams).isNotNull();
+  }
+
+  @Test
+  public void encodeAndDecode() throws Exception {
+    String message = "com.google.tuningfork.FidelityParams";
+    File protoFile = helper.getFile("dev_tuningfork.proto");
+    File originalTextFile = helper.getFile("dev_tuningfork_fidelityparams_1.txt");
+    Optional<File> errorFile = Optional.of(tempFolder.newFile("errors.txt"));
+    String root = tempFolder.getRoot().getAbsolutePath();
+    File binaryFile =
+        compiler.encodeFromTextprotoFile(
+            message,
+            protoFile,
+            originalTextFile,
+            root + "/dev_tuningfork_fidelityparams_1.bin",
+            errorFile);
+
+    byte[] error = Files.toByteArray(errorFile.get());
+    assertThat(error).isEqualTo(new byte[0]);
+
+    File decodedTextFile =
+        compiler.decodeToTextprotoFile(
+            message,
+            protoFile,
+            root + "/dev_tuningfork_fidelityparams_decoded.txt",
+            binaryFile,
+            errorFile);
+
+    String originalMessage = Files.asCharSource(originalTextFile, UTF_8).read();
+    String decodedMessage = Files.asCharSource(decodedTextFile, UTF_8).read();
+    error = Files.toByteArray(errorFile.get());
+    assertThat(error).isEqualTo(new byte[0]);
+    assertThat(decodedMessage).isEqualTo(originalMessage);
+  }
+
+  @Test
   public void runEchoCommand() throws Exception {
     String expected = "Hello world";
-    String result = new String(compiler.runCommand(Arrays.asList("echo", expected)), UTF_8);
+    ProcessBuilder builder = new ProcessBuilder(Arrays.asList("echo", expected));
+    String result = new String(compiler.runCommand(builder), UTF_8);
     assertThat(result).startsWith(expected);
   }
 }
diff --git a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ProtoCompilerHelper.java b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ProtoCompilerHelper.java
index 2604120..d9ef5a9 100644
--- a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ProtoCompilerHelper.java
+++ b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ProtoCompilerHelper.java
@@ -22,6 +22,7 @@
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FileDescriptor;
 import java.io.File;
+import java.util.Optional;
 import org.junit.rules.TemporaryFolder;
 
 /** Base class for tests that need to create proto Descriptors */
@@ -39,7 +40,7 @@
   public Descriptor getDescriptor(String fileName, String message, String descName)
       throws Exception {
     File file = testdata.getFile(fileName);
-    FileDescriptor fDesc = compiler.compile(file);
+    FileDescriptor fDesc = compiler.compile(file, Optional.empty());
     assertThat(fDesc).isNotNull();
     Descriptor desc = fDesc.findMessageTypeByName(descName);
     return desc;
diff --git a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ValidationUtilTest.java b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ValidationUtilTest.java
index 767ab48..a2d915a 100644
--- a/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ValidationUtilTest.java
+++ b/src/tuningfork/tools/validation/src/test/java/com/google/tuningfork/validation/ValidationUtilTest.java
@@ -20,12 +20,13 @@
 
 import com.google.protobuf.ByteString;
 import com.google.protobuf.Descriptors.Descriptor;
+import com.google.testing.testsize.MediumTest;
 import com.google.tuningfork.Tuningfork.Settings;
 import com.google.tuningfork.Tuningfork.Settings.AggregationStrategy;
 import com.google.tuningfork.Tuningfork.Settings.Histogram;
-import com.google.testing.testsize.MediumTest;
 import java.io.File;
 import java.util.Arrays;
+import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -53,6 +54,26 @@
   private final ErrorCollector errors = new ParserErrorCollector();
 
   @Test
+  public void settingsValid() throws Exception {
+    AggregationStrategy aggregation =
+        AggregationStrategy.newBuilder()
+            .addAllAnnotationEnumSize(Arrays.asList(5, 10))
+            .setMaxInstrumentationKeys(100)
+            .build();
+    Settings settings =
+        Settings.newBuilder()
+            .setAggregationStrategy(aggregation)
+            .addHistograms(Histogram.getDefaultInstance())
+            .build();
+
+    Optional<Settings> parsedSettings =
+        ValidationUtil.validateSettings(Arrays.asList(5, 10), settings.toString(), errors);
+
+    assertThat(errors.getErrorCount()).isEqualTo(0);
+    assertThat(parsedSettings.get()).isEqualTo(settings);
+  }
+
+  @Test
   public void settingsHistogramsValid() throws Exception {
     Settings settings = Settings.newBuilder().addHistograms(Histogram.getDefaultInstance()).build();
 
diff --git a/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork.proto b/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork.proto
new file mode 100644
index 0000000..da618e2
--- /dev/null
+++ b/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork.proto
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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
+ */
+
+syntax = "proto3";
+
+package com.google.tuningfork;
+
+option java_package = "com.google.tuningfork";
+
+enum LoadingState {
+  UNKNOWN = 0;
+  NOT_LOADING = 1;
+}
+
+message Annotation {
+  LoadingState loading_state = 1;
+}
+
+enum QualitySettings {
+  UNKNOWN_QUALITY = 0;
+  FAST = 1;
+  SIMPLE = 2;
+  GOOD = 3;
+  BEAUTIFUL = 4;
+  FANTASTIC = 5;
+}
+
+message FidelityParams {
+  QualitySettings quality_settings = 1;
+  int32 int_field = 2;
+  float float_field = 3;
+}
diff --git a/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork_fidelityparams_1.txt b/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork_fidelityparams_1.txt
new file mode 100644
index 0000000..21152fc
--- /dev/null
+++ b/src/tuningfork/tools/validation/src/test/resources/testdata/dev_tuningfork_fidelityparams_1.txt
@@ -0,0 +1,2 @@
+quality_settings: FANTASTIC
+int_field: 10
diff --git a/src/tuningfork/tuningfork_internal.h b/src/tuningfork/tuningfork_internal.h
index 9ccc0ad..3282249 100644
--- a/src/tuningfork/tuningfork_internal.h
+++ b/src/tuningfork/tuningfork_internal.h
@@ -74,6 +74,7 @@
     std::vector<uint64_t> cpu_max_freq_hz;
     std::string apk_package_name;
     int apk_version_code;
+    int tuningfork_version;
 };
 
 class Backend {
diff --git a/src/tuningfork/tuningfork_utils.cpp b/src/tuningfork/tuningfork_utils.cpp
index 7894e48..7158d65 100644
--- a/src/tuningfork/tuningfork_utils.cpp
+++ b/src/tuningfork/tuningfork_utils.cpp
@@ -139,4 +139,17 @@
 
 } // namespace file_utils
 
+std::string UniqueId(JNIEnv* env) {
+    jclass uuid_class = env->FindClass("java/util/UUID");
+    jmethodID randomUUID = env->GetStaticMethodID( uuid_class, "randomUUID",
+            "()Ljava/util/UUID;");
+    jobject uuid = env->CallStaticObjectMethod(uuid_class, randomUUID);
+    jmethodID toString = env->GetMethodID( uuid_class, "toString", "()Ljava/lang/String;");
+    jstring uuid_string = (jstring)env->CallObjectMethod(uuid, toString);
+    const char *uuid_chars = env->GetStringUTFChars( uuid_string, NULL );
+    std::string temp_uuid( uuid_chars );
+    env->ReleaseStringUTFChars( uuid_string, uuid_chars );
+    return temp_uuid;
+}
+
 } // namespace tuningfork
diff --git a/src/tuningfork/tuningfork_utils.h b/src/tuningfork/tuningfork_utils.h
index ef16f84..63df285 100644
--- a/src/tuningfork/tuningfork_utils.h
+++ b/src/tuningfork/tuningfork_utils.h
@@ -49,4 +49,7 @@
 
 } // namespace file_utils
 
+// Get a unique identifier using java.util.UUID
+std::string UniqueId(JNIEnv* env);
+
 } // namespace tuningfork
diff --git a/src/tuningfork/uploadthread.cpp b/src/tuningfork/uploadthread.cpp
index 12d0c3d..244dc7a 100644
--- a/src/tuningfork/uploadthread.cpp
+++ b/src/tuningfork/uploadthread.cpp
@@ -186,22 +186,26 @@
     extra_info.build_version_sdk = getSystemPropViaGet("ro.build.version.sdk");
     extra_info.build_fingerprint = getSystemPropViaGet("ro.build.fingerprint");
 
+    extra_info.session_id = UniqueId(env);
+
     extra_info.cpu_max_freq_hz.clear();
     for(int index = 1;;++index) {
         std::stringstream str;
-        str << "/sys/devices/system/cpu/cpu/" << index << "/cpufreq/cpuinfo_max_freq";
+        str << "/sys/devices/system/cpu/cpu" << index << "/cpufreq/cpuinfo_max_freq";
         auto cpu_freq_file = slurpFile(str.str().c_str());
         if (cpu_freq_file.empty())
             break;
         uint64_t freq;
         std::istringstream cstr(cpu_freq_file);
         cstr >> freq;
-        // TODO check units
-        extra_info.cpu_max_freq_hz.push_back(freq);
+        extra_info.cpu_max_freq_hz.push_back(freq*1000); // File is in kHz
     }
 
     extra_info.apk_version_code = apk_utils::GetVersionCode(env, activity,
         &extra_info.apk_package_name);
+
+    extra_info.tuningfork_version = TUNINGFORK_PACKED_VERSION;
+
     return extra_info;
 }
 
diff --git a/third_party/cube/build.gradle b/third_party/cube/build.gradle
index 51b175b..4699ea7 100644
--- a/third_party/cube/build.gradle
+++ b/third_party/cube/build.gradle
@@ -6,7 +6,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.1'
+        classpath 'com.android.tools.build:gradle:3.3.2'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }