Merge changes from topic "statsd-allow-bytes-type"
am: 783ec04909

Change-Id: Ic27a992cdacda04a5b62d01de44cf1b92164e58e
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index a2a03b1..7dfe7d6 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -64,10 +64,22 @@
     optional StateField option = 1 [default = STATE_FIELD_UNSET];
 }
 
+// Used to generate StatsLog.write APIs.
+enum LogMode {
+    MODE_UNSET = 0;
+    // Log fields as their actual types e.g., all primary data types.
+    // Or fields that are hardcoded in stats_log_api_gen tool e.g., AttributionNode
+    MODE_AUTOMATIC = 1;
+    // Log fields in their proto binary format. These fields will not be parsed in statsd
+    MODE_BYTES = 2;
+}
+
 extend google.protobuf.FieldOptions {
     // Flags to decorate an atom that presents a state change.
     optional StateAtomFieldOption stateFieldOption = 50000;
 
     // Flags to decorate the uid fields in an atom.
     optional bool is_uid = 50001 [default = false];
+
+    optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC];
 }
\ No newline at end of file
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 281f900..d9fa0f1 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -26,6 +26,7 @@
 import "frameworks/base/core/proto/android/bluetooth/enums.proto";
 import "frameworks/base/core/proto/android/os/enums.proto";
 import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
 import "frameworks/base/core/proto/android/telecomm/enums.proto";
 import "frameworks/base/core/proto/android/telephony/enums.proto";
 import "frameworks/base/core/proto/android/view/enums.proto";
@@ -59,7 +60,8 @@
         LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 11;
         MobileRadioPowerStateChanged mobile_radio_power_state_changed = 12;
         WifiRadioPowerStateChanged wifi_radio_power_state_changed = 13;
-        // 14 - 19 are available
+        // 14 - 18 are available
+        LauncherUIChanged launcher_event = 19;
         BatterySaverModeStateChanged battery_saver_mode_state_changed = 20;
         DeviceIdleModeStateChanged device_idle_mode_state_changed = 21;
         DeviceIdlingModeStateChanged device_idling_mode_state_changed = 22;
@@ -1166,6 +1168,14 @@
     optional State state = 1;
 }
 
+message LauncherUIChanged {
+    optional android.stats.launcher.LauncherAction action = 1;
+    optional android.stats.launcher.LauncherState src_state = 2;
+    optional android.stats.launcher.LauncherState dst_state = 3;
+    optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES];
+    optional bool is_swipe_up_enabled = 5;
+}
+
 /**
  * Logs that a setting was updated.
  * Logged from:
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 4e4f146..5d6d02b 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -379,7 +379,7 @@
 
 string LogEvent::ToString() const {
     string result;
-    result += StringPrintf("{ %lld %lld (%d)", (long long)mLogdTimestampNs,
+    result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
                            (long long)mElapsedTimestampNs, mTagId);
     for (const auto& value : mValues) {
         result +=
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index a0ab3e4..11ce717 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -25,15 +25,16 @@
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
 
+using android::util::AtomsInfo;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FIXED64;
 using android::util::FIELD_TYPE_FLOAT;
 using android::util::FIELD_TYPE_INT32;
 using android::util::FIELD_TYPE_INT64;
-using android::util::FIELD_TYPE_UINT64;
-using android::util::FIELD_TYPE_FIXED64;
 using android::util::FIELD_TYPE_MESSAGE;
 using android::util::FIELD_TYPE_STRING;
+using android::util::FIELD_TYPE_UINT64;
 using android::util::ProtoOutputStream;
 
 namespace android {
@@ -294,8 +295,9 @@
 // }
 //
 //
-void writeFieldValueTreeToStreamHelper(const std::vector<FieldValue>& dims, size_t* index,
-                                       int depth, int prefix, ProtoOutputStream* protoOutput) {
+void writeFieldValueTreeToStreamHelper(int tagId, const std::vector<FieldValue>& dims,
+                                       size_t* index, int depth, int prefix,
+                                       ProtoOutputStream* protoOutput) {
     size_t count = dims.size();
     while (*index < count) {
         const auto& dim = dims[*index];
@@ -319,9 +321,33 @@
                 case FLOAT:
                     protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value);
                     break;
-                case STRING:
-                    protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
+                case STRING: {
+                    bool isBytesField = false;
+                    // Bytes field is logged via string format in log_msg format. So here we check
+                    // if this string field is a byte field.
+                    std::map<int, std::vector<int>>::const_iterator itr;
+                    if (depth == 0 && (itr = AtomsInfo::kBytesFieldAtoms.find(tagId)) !=
+                                              AtomsInfo::kBytesFieldAtoms.end()) {
+                        const std::vector<int>& bytesFields = itr->second;
+                        for (int bytesField : bytesFields) {
+                            if (bytesField == fieldNum) {
+                                // This is a bytes field
+                                isBytesField = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (isBytesField) {
+                        if (dim.mValue.str_value.length() > 0) {
+                            protoOutput->write(FIELD_TYPE_MESSAGE | fieldNum,
+                                               (const char*)dim.mValue.str_value.c_str(),
+                                               dim.mValue.str_value.length());
+                        }
+                    } else {
+                        protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
+                    }
                     break;
+                }
                 default:
                     break;
             }
@@ -337,7 +363,7 @@
             }
             // Directly jump to the leaf value because the repeated position field is implied
             // by the position of the sub msg in the parent field.
-            writeFieldValueTreeToStreamHelper(dims, index, valueDepth,
+            writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth,
                                               dim.mField.getPrefix(valueDepth), protoOutput);
             if (msg_token != 0) {
                 protoOutput->end(msg_token);
@@ -354,7 +380,7 @@
     uint64_t atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | tagId);
 
     size_t index = 0;
-    writeFieldValueTreeToStreamHelper(values, &index, 0, 0, protoOutput);
+    writeFieldValueTreeToStreamHelper(tagId, values, &index, 0, 0, protoOutput);
     protoOutput->end(atomToken);
 }
 
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 2fcde29..b29de53 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/logd/LogEvent.h"
 #include <gtest/gtest.h>
 #include <log/log_event_list.h>
-#include "src/logd/LogEvent.h"
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
+#include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
 
 #ifdef __ANDROID__
 
@@ -22,6 +24,9 @@
 namespace os {
 namespace statsd {
 
+using std::string;
+using util::ProtoOutputStream;
+
 TEST(LogEventTest, TestLogParsing) {
     LogEvent event1(1, 2000);
 
@@ -159,6 +164,94 @@
 }
 
 
+TEST(LogEventTest, TestBinaryFieldAtom) {
+    Atom launcherAtom;
+    auto launcher_event = launcherAtom.mutable_launcher_event();
+    launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS);
+    launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW);
+    launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS);
+
+    auto extension = launcher_event->mutable_extension();
+
+    auto src_target = extension->add_src_target();
+    src_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE);
+    src_target->set_item(stats::launcher::LauncherTarget_Item_FOLDER_ICON);
+
+    auto dst_target = extension->add_dst_target();
+    dst_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE);
+    dst_target->set_item(stats::launcher::LauncherTarget_Item_WIDGET);
+
+    string extension_str;
+    extension->SerializeToString(&extension_str);
+
+    LogEvent event1(Atom::kLauncherEventFieldNumber, 1000);
+
+    event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS);
+    event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW);
+    event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS);
+    event1.write(extension_str);
+    event1.init();
+
+    ProtoOutputStream proto;
+    event1.ToProto(proto);
+
+    std::vector<uint8_t> outData;
+    outData.resize(proto.size());
+    size_t pos = 0;
+    auto iter = proto.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+        std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
+        pos += toRead;
+        iter.rp()->move(toRead);
+    }
+
+    std::string result_str(outData.begin(), outData.end());
+    std::string orig_str;
+    launcherAtom.SerializeToString(&orig_str);
+
+    EXPECT_EQ(orig_str, result_str);
+}
+
+TEST(LogEventTest, TestBinaryFieldAtom_empty) {
+    Atom launcherAtom;
+    auto launcher_event = launcherAtom.mutable_launcher_event();
+    launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS);
+    launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW);
+    launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS);
+
+    // empty string.
+    string extension_str;
+
+    LogEvent event1(Atom::kLauncherEventFieldNumber, 1000);
+
+    event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS);
+    event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW);
+    event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS);
+    event1.write(extension_str);
+    event1.init();
+
+    ProtoOutputStream proto;
+    event1.ToProto(proto);
+
+    std::vector<uint8_t> outData;
+    outData.resize(proto.size());
+    size_t pos = 0;
+    auto iter = proto.data();
+    while (iter.readBuffer() != NULL) {
+        size_t toRead = iter.currentToRead();
+        std::memcpy(&(outData[pos]), iter.readBuffer(), toRead);
+        pos += toRead;
+        iter.rp()->move(toRead);
+    }
+
+    std::string result_str(outData.begin(), outData.end());
+    std::string orig_str;
+    launcherAtom.SerializeToString(&orig_str);
+
+    EXPECT_EQ(orig_str, result_str);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/core/proto/android/stats/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto
new file mode 100644
index 0000000..dbd0e03
--- /dev/null
+++ b/core/proto/android/stats/launcher/launcher.proto
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 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 = "proto2";
+package android.stats.launcher;
+option java_multiple_files = true;
+
+enum LauncherAction {
+    DEFAULT_ACTION = 0;
+    LAUNCH_APP = 1;
+    LAUNCH_TASK = 2;
+    DISMISS_TASK = 3;
+    LONGPRESS = 4;
+    DRAGDROP = 5;
+    SWIPE_UP = 6;
+    SWIPE_DOWN = 7;
+    SWIPE_LEFT = 8;
+    SWIPE_RIGHT = 9;
+}
+
+enum LauncherState {
+    BACKGROUND = 0;
+    HOME = 1;
+    OVERVIEW = 2;
+    ALLAPPS = 3;
+}
+
+message LauncherTarget {
+    enum Type {
+        NONE = 0;
+        ITEM_TYPE = 1;
+        CONTROL_TYPE = 2;
+        CONTAINER_TYPE = 3;
+    }
+    enum Item {
+        DEFAULT_ITEM = 0;
+        APP_ICON = 1;
+        SHORTCUT = 2;
+        WIDGET = 3;
+        FOLDER_ICON = 4;
+        DEEPSHORTCUT = 5;
+        SEARCHBOX = 6;
+        EDITTEXT = 7;
+        NOTIFICATION = 8;
+        TASK = 9;
+    }
+    enum Container {
+        DEFAULT_CONTAINER = 0;
+        HOTSEAT = 1;
+        FOLDER = 2;
+        PREDICTION = 3;
+        SEARCHRESULT = 4;
+    }
+    enum Control {
+        DEFAULT_CONTROL = 0;
+        MENU = 1;
+        UNINSTALL = 2;
+        REMOVE = 3;
+    }
+    optional Type type = 1;
+    optional Item item = 2;
+    optional Container container = 3;
+    optional Control control = 4;
+    optional string launch_component = 5;
+    optional int32 page_id = 6;
+    optional int32 grid_x = 7;
+    optional int32 grid_y = 8;
+}
+
+message LauncherExtension {
+    repeated LauncherTarget src_target = 1;
+    repeated LauncherTarget dst_target = 2;
+}
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index ebdcdfd..61174d9 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -47,7 +47,8 @@
       fields(that.fields),
       primaryFields(that.primaryFields),
       exclusiveField(that.exclusiveField),
-      uidField(that.uidField) {}
+      uidField(that.uidField),
+      binaryFields(that.binaryFields) {}
 
 AtomDecl::AtomDecl(int c, const string& n, const string& m)
     :code(c),
@@ -116,6 +117,9 @@
             if (field->message_type()->full_name() ==
                 "android.os.statsd.AttributionNode") {
               return JAVA_TYPE_ATTRIBUTION_CHAIN;
+            } else if (field->options().GetExtension(os::statsd::log_mode) ==
+                       os::statsd::LogMode::MODE_BYTES) {
+                return JAVA_TYPE_BYTE_ARRAY;
             } else {
                 return JAVA_TYPE_OBJECT;
             }
@@ -185,6 +189,8 @@
   for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
        it != fields.end(); it++) {
     const FieldDescriptor *field = it->second;
+    bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+                         os::statsd::LogMode::MODE_BYTES;
 
     java_type_t javaType = java_type(field);
 
@@ -198,12 +204,19 @@
                   field->name().c_str());
       errorCount++;
       continue;
-    } else if (javaType == JAVA_TYPE_BYTE_ARRAY) {
+    } else if (javaType == JAVA_TYPE_BYTE_ARRAY && !isBinaryField) {
       print_error(field, "Raw bytes type not allowed for field: %s\n",
                   field->name().c_str());
       errorCount++;
       continue;
     }
+
+    if (isBinaryField && javaType != JAVA_TYPE_BYTE_ARRAY) {
+      print_error(field, "Cannot mark field %s as bytes.\n",
+                  field->name().c_str());
+      errorCount++;
+      continue;
+    }
   }
 
   // Check that if there's an attribution chain, it's at position 1.
@@ -228,12 +241,16 @@
        it != fields.end(); it++) {
     const FieldDescriptor *field = it->second;
     java_type_t javaType = java_type(field);
+    bool isBinaryField = field->options().GetExtension(os::statsd::log_mode) ==
+                         os::statsd::LogMode::MODE_BYTES;
 
     AtomField atField(field->name(), javaType);
     if (javaType == JAVA_TYPE_ENUM) {
       // All enums are treated as ints when it comes to function signatures.
       signature->push_back(JAVA_TYPE_INT);
       collate_enums(*field->enum_type(), &atField);
+    } else if (javaType == JAVA_TYPE_OBJECT && isBinaryField) {
+      signature->push_back(JAVA_TYPE_BYTE_ARRAY);
     } else {
       signature->push_back(javaType);
     }
@@ -275,6 +292,10 @@
             errorCount++;
         }
     }
+    // Binary field validity is already checked above.
+    if (isBinaryField) {
+        atomDecl->binaryFields.push_back(it->first);
+    }
   }
 
   return errorCount;
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 5d2c302..a8b270c 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -86,6 +86,8 @@
 
     int uidField = 0;
 
+    vector<int> binaryFields;
+
     AtomDecl();
     AtomDecl(const AtomDecl& that);
     AtomDecl(int code, const string& name, const string& message);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index e519909..485fc46 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -68,6 +68,8 @@
             return "double";
         case JAVA_TYPE_STRING:
             return "char const*";
+        case JAVA_TYPE_BYTE_ARRAY:
+            return "char const*";
         default:
             return "UNKNOWN";
     }
@@ -90,6 +92,8 @@
             return "double";
         case JAVA_TYPE_STRING:
             return "java.lang.String";
+        case JAVA_TYPE_BYTE_ARRAY:
+            return "byte[]";
         default:
             return "UNKNOWN";
     }
@@ -200,13 +204,40 @@
     }
 
     fprintf(out, "    return options;\n");
-    fprintf(out, "  }\n");
+    fprintf(out, "}\n");
 
     fprintf(out,
             "const std::map<int, StateAtomFieldOptions> "
             "AtomsInfo::kStateAtomsFieldOptions = "
             "getStateAtomFieldOptions();\n");
 
+    fprintf(out,
+            "static std::map<int, std::vector<int>> "
+            "getBinaryFieldAtoms() {\n");
+    fprintf(out, "    std::map<int, std::vector<int>> options;\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        if (atom->binaryFields.size() == 0) {
+            continue;
+        }
+        fprintf(out,
+                "\n    // Adding binary fields for atom "
+                "(%d)%s\n",
+                atom->code, atom->name.c_str());
+
+        for (const auto& field : atom->binaryFields) {
+            fprintf(out, "    options[static_cast<int>(%s)].push_back(%d);\n",
+                    make_constant_name(atom->name).c_str(), field);
+        }
+    }
+
+    fprintf(out, "    return options;\n");
+    fprintf(out, "}\n");
+
+    fprintf(out,
+            "const std::map<int, std::vector<int>> "
+            "AtomsInfo::kBytesFieldAtoms = "
+            "getBinaryFieldAtoms();\n");
 
     fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
     fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
@@ -235,6 +266,9 @@
                                  chainField.name.c_str(), chainField.name.c_str());
                     }
                 }
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out, ", %s arg%d, size_t arg%d_length",
+                        cpp_type_name(*arg), argIndex, argIndex);
             } else {
                 fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
             }
@@ -277,6 +311,10 @@
                 fprintf(out, "        event.end();\n");
                 fprintf(out, "    }\n");
                 fprintf(out, "    event.end();\n\n");
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out,
+                        "    event.AppendCharArray(arg%d, arg%d_length);\n",
+                        argIndex, argIndex);
             } else {
                 if (*arg == JAVA_TYPE_STRING) {
                     fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
@@ -317,6 +355,9 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
+           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+               fprintf(out, ", %s arg%d, size_t arg%d_length",
+                       cpp_type_name(*arg), argIndex, argIndex);
            } else {
                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
            }
@@ -343,6 +384,8 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
+           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+               fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex);
            } else {
                fprintf(out, ", arg%d", argIndex);
            }
@@ -491,6 +534,10 @@
                          chainField.name.c_str(), chainField.name.c_str());
                 }
             }
+        } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
+            fprintf(out, ", %s %s, size_t %s_length",
+                    cpp_type_name(field->javaType), field->name.c_str(),
+                    field->name.c_str());
         } else {
             fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
         }
@@ -518,6 +565,9 @@
                             chainField.name.c_str(), chainField.name.c_str());
                     }
                 }
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out, ", %s arg%d, size_t arg%d_length",
+                        cpp_type_name(*arg), argIndex, argIndex);
             } else {
                 fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
             }
@@ -600,6 +650,9 @@
     fprintf(out,
             "  const static std::map<int, StateAtomFieldOptions> "
             "kStateAtomsFieldOptions;\n");
+    fprintf(out,
+            "  const static std::map<int, std::vector<int>> "
+            "kBytesFieldAtoms;");
     fprintf(out, "};\n");
 
     fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
@@ -632,6 +685,8 @@
         field != atom.fields.end(); field++) {
         if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
             fprintf(out, ", android.os.WorkSource workSource");
+        } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
+            fprintf(out, ", byte[] %s", field->name.c_str());
         } else {
             fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
         }
@@ -821,6 +876,8 @@
             return "jdouble";
         case JAVA_TYPE_STRING:
             return "jstring";
+        case JAVA_TYPE_BYTE_ARRAY:
+            return "jbyteArray";
         default:
             return "UNKNOWN";
     }
@@ -868,6 +925,9 @@
             case JAVA_TYPE_ATTRIBUTION_CHAIN:
               result += "_AttributionChain";
               break;
+            case JAVA_TYPE_BYTE_ARRAY:
+                result += "_bytes";
+                break;
             default:
                 result += "_UNKNOWN";
                 break;
@@ -893,6 +953,8 @@
             return "D";
         case JAVA_TYPE_STRING:
             return "Ljava/lang/String;";
+        case JAVA_TYPE_BYTE_ARRAY:
+            return "[B";
         default:
             return "UNKNOWN";
     }
@@ -960,6 +1022,32 @@
                 fprintf(out, "    } else {\n");
                 fprintf(out, "        str%d = NULL;\n", argIndex);
                 fprintf(out, "    }\n");
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                hadStringOrChain = true;
+                fprintf(out, "    jbyte* jbyte_array%d;\n", argIndex);
+                fprintf(out, "    const char* str%d;\n", argIndex);
+                fprintf(out, "    int str%d_length = 0;\n", argIndex);
+                fprintf(out,
+                        "    if (arg%d != NULL && env->GetArrayLength(arg%d) > "
+                        "0) {\n",
+                        argIndex, argIndex);
+                fprintf(out,
+                        "        jbyte_array%d = "
+                        "env->GetByteArrayElements(arg%d, NULL);\n",
+                        argIndex, argIndex);
+                fprintf(out,
+                        "        str%d_length = env->GetArrayLength(arg%d);\n",
+                        argIndex, argIndex);
+                fprintf(out,
+                        "        str%d = "
+                        "reinterpret_cast<char*>(env->GetByteArrayElements(arg%"
+                        "d, NULL));\n",
+                        argIndex, argIndex);
+                fprintf(out, "    } else {\n");
+                fprintf(out, "        jbyte_array%d = NULL;\n", argIndex);
+                fprintf(out, "        str%d = NULL;\n", argIndex);
+                fprintf(out, "    }\n");
+
             } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
                 hadStringOrChain = true;
                 for (auto chainField : attributionDecl.fields) {
@@ -1026,8 +1114,15 @@
                     }
                 }
             } else {
-                const char *argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
+                const char* argName = (*arg == JAVA_TYPE_STRING ||
+                                       *arg == JAVA_TYPE_BYTE_ARRAY)
+                                              ? "str"
+                                              : "arg";
                 fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
+
+                if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                    fprintf(out, ", %s%d_length", argName, argIndex);
+                }
             }
             argIndex++;
         }
@@ -1043,6 +1138,13 @@
                 fprintf(out, "        env->ReleaseStringUTFChars(arg%d, str%d);\n",
                         argIndex, argIndex);
                 fprintf(out, "    }\n");
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out, "    if (str%d != NULL) { \n", argIndex);
+                fprintf(out,
+                        "        env->ReleaseByteArrayElements(arg%d, "
+                        "jbyte_array%d, 0);\n",
+                        argIndex, argIndex);
+                fprintf(out, "    }\n");
             } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
                 for (auto chainField : attributionDecl.fields) {
                     if (chainField.javaType == JAVA_TYPE_INT) {
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
index 264a865..188b765 100644
--- a/tools/stats_log_api_gen/test.proto
+++ b/tools/stats_log_api_gen/test.proto
@@ -109,6 +109,28 @@
   oneof event { BadAttributionNodePositionAtom bad = 1; }
 }
 
+message GoodEventWithBinaryFieldAtom {
+    oneof event { GoodBinaryFieldAtom field1 = 1; }
+}
+
+message ComplexField {
+    optional string str = 1;
+}
+
+message GoodBinaryFieldAtom {
+    optional int32 field1 = 1;
+    optional ComplexField bf = 2 [(android.os.statsd.log_mode) = MODE_BYTES];
+}
+
+message BadEventWithBinaryFieldAtom {
+    oneof event { BadBinaryFieldAtom field1 = 1; }
+}
+
+message BadBinaryFieldAtom {
+    optional int32 field1 = 1;
+    optional ComplexField bf = 2;
+}
+
 message BadStateAtoms {
     oneof event {
         BadStateAtom1 bad1 = 1;
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
index 1936d96..ad3bffac 100644
--- a/tools/stats_log_api_gen/test_collation.cpp
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -212,5 +212,19 @@
     EXPECT_EQ(0, errorCount);
 }
 
+TEST(CollationTest, PassOnGoodBinaryFieldAtom) {
+    Atoms atoms;
+    int errorCount =
+            collate_atoms(GoodEventWithBinaryFieldAtom::descriptor(), &atoms);
+    EXPECT_EQ(0, errorCount);
+}
+
+TEST(CollationTest, FailOnBadBinaryFieldAtom) {
+    Atoms atoms;
+    int errorCount =
+            collate_atoms(BadEventWithBinaryFieldAtom::descriptor(), &atoms);
+    EXPECT_TRUE(errorCount > 0);
+}
+
 }  // namespace stats_log_api_gen
 }  // namespace android
\ No newline at end of file