Fixes the bug that can occur when StatsCompanionService calls
StatsService to update UID data and overflows kernel transfer buffer.

In this case, the IPC call silently fails. The issue was discovered in Android Automotive Embedded use case that employs multiuser setup. This causes more uid data being sent via one-way StatsCompanionService::informAllUidData call than usual and can trigger the issue. As the result, uid map on statsd side is empty and many metrics are not captured.

Bug: b/132444397
Fixes: b/132444397
Test: Did a clean build of master branch and flashed the device. adb
shell cmd stats print-uid-map returned without any result. Repeated the
steps after implementing the fix, print-uid-map returns the results now.

Change-Id: I1451c13b36696449c145c51618c68d10e29a596a
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 15d248f..d59d0e2 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -52,6 +52,7 @@
         ":statsd_aidl",
         "src/active_config_list.proto",
         "src/statsd_config.proto",
+        "src/uid_data.proto",
         "src/FieldValue.cpp",
         "src/hash.cpp",
         "src/stats_log_util.cpp",
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 8191d37..f646f19 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -35,6 +35,7 @@
 #include <cutils/multiuser.h>
 #include <dirent.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
+#include <frameworks/base/cmds/statsd/src/uid_data.pb.h>
 #include <private/android_filesystem_config.h>
 #include <statslog.h>
 #include <stdio.h>
@@ -953,16 +954,49 @@
             || (callingUid == AID_ROOT && goodUid == AID_SHELL); // ROOT can impersonate SHELL.
 }
 
-Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
-                                      const vector<String16>& version_string,
-                                      const vector<String16>& app,
-                                      const vector<String16>& installer) {
+Status StatsService::informAllUidData(const ParcelFileDescriptor& fd) {
     ENFORCE_UID(AID_SYSTEM);
+    // Read stream into buffer.
+    string buffer;
+    if (!android::base::ReadFdToString(fd.get(), &buffer)) {
+        return exception(Status::EX_ILLEGAL_ARGUMENT, "Failed to read all data from the pipe.");
+    }
 
-    VLOG("StatsService::informAllUidData was called");
-    mUidMap->updateMap(getElapsedRealtimeNs(), uid, version, version_string, app, installer);
-    VLOG("StatsService::informAllUidData succeeded");
+    // Parse buffer.
+    UidData uidData;
+    if (!uidData.ParseFromString(buffer)) {
+        return exception(Status::EX_ILLEGAL_ARGUMENT, "Error parsing proto stream for UidData.");
+    }
 
+    vector<String16> versionStrings;
+    vector<String16> installers;
+    vector<String16> packageNames;
+    vector<int32_t> uids;
+    vector<int64_t> versions;
+
+    const auto numEntries = uidData.app_info_size();
+    versionStrings.reserve(numEntries);
+    installers.reserve(numEntries);
+    packageNames.reserve(numEntries);
+    uids.reserve(numEntries);
+    versions.reserve(numEntries);
+
+    for (const auto& appInfo: uidData.app_info()) {
+        packageNames.emplace_back(String16(appInfo.package_name().c_str()));
+        uids.push_back(appInfo.uid());
+        versions.push_back(appInfo.version());
+        versionStrings.emplace_back(String16(appInfo.version_string().c_str()));
+        installers.emplace_back(String16(appInfo.installer().c_str()));
+    }
+
+    mUidMap->updateMap(getElapsedRealtimeNs(),
+                       uids,
+                       versions,
+                       versionStrings,
+                       packageNames,
+                       installers);
+
+    VLOG("StatsService::informAllUidData UidData proto parsed successfully.");
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index a4e6d7f..9d50a003 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -33,6 +33,7 @@
 #include <android/os/IStatsCompanionService.h>
 #include <android/os/IStatsManager.h>
 #include <binder/IResultReceiver.h>
+#include <binder/ParcelFileDescriptor.h>
 #include <utils/Looper.h>
 
 #include <deque>
@@ -72,9 +73,7 @@
     virtual Status informPollAlarmFired();
     virtual Status informAlarmForSubscriberTriggeringFired();
 
-    virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
-                                    const vector<String16>& version_string,
-                                    const vector<String16>& app, const vector<String16>& installer);
+    virtual Status informAllUidData(const ParcelFileDescriptor& fd);
     virtual Status informOnePackage(const String16& app, int32_t uid, int64_t version,
                                     const String16& version_string, const String16& installer);
     virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
diff --git a/cmds/statsd/src/uid_data.proto b/cmds/statsd/src/uid_data.proto
new file mode 100644
index 0000000..a6fa26c
--- /dev/null
+++ b/cmds/statsd/src/uid_data.proto
@@ -0,0 +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.
+ */
+
+syntax = "proto2";
+
+package android.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "UidDataProto";
+
+message ApplicationInfo {
+  optional int32 uid = 1;
+  optional int64 version = 2;
+  optional string version_string = 3;
+  optional string package_name = 4;
+  optional string installer = 5;
+}
+
+// StatsServiceCompanion uses the proto to supply statsd with uid-package
+// mapping updates.
+message UidData {
+  repeated ApplicationInfo app_info = 1;
+}
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 311c86d..df0363c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.os.IStatsPullerCallback;
+import android.os.ParcelFileDescriptor;
 
 /**
   * Binder interface to communicate with the statistics management service.
@@ -61,11 +62,12 @@
     void informDeviceShutdown();
 
     /**
-     * Inform statsd what the version and package are for each uid. Note that each array should
-     * have the same number of elements, and version[i] and package[i] correspond to uid[i].
+     * Inform statsd about a file descriptor for a pipe through which we will pipe version
+     * and package information for each uid.
+     * Versions and package information are supplied via UidData proto where info for each app
+     * is captured in its own element of a repeated ApplicationInfo message.
      */
-    oneway void informAllUidData(in int[] uid, in long[] version, in String[] version_string,
-        in String[] app, in String[] installer);
+    oneway void informAllUidData(in ParcelFileDescriptor fd);
 
     /**
      * Inform statsd what the uid, version, version_string, and installer are for one app that was
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 669656d..de03c40 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -109,10 +109,12 @@
 import android.util.Slog;
 import android.util.StatsLog;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.IProcessStats;
 import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
@@ -184,6 +186,16 @@
 
     static final String TAG = "StatsCompanionService";
     static final boolean DEBUG = false;
+    /**
+     * Hard coded field ids of frameworks/base/cmds/statsd/src/uid_data.proto
+     * to be used in ProtoOutputStream.
+     */
+    private static final int APPLICATION_INFO_FIELD_ID = 1;
+    private static final int UID_FIELD_ID = 1;
+    private static final int VERSION_FIELD_ID = 2;
+    private static final int VERSION_STRING_FIELD_ID = 3;
+    private static final int PACKAGE_NAME_FIELD_ID = 4;
+    private static final int INSTALLER_FIELD_ID = 5;
 
     public static final int CODE_DATA_BROADCAST = 1;
     public static final int CODE_SUBSCRIBER_BROADCAST = 1;
@@ -455,39 +467,71 @@
             Slog.d(TAG, "Iterating over " + users.size() + " profiles.");
         }
 
-        List<Integer> uids = new ArrayList<>();
-        List<Long> versions = new ArrayList<>();
-        List<String> apps = new ArrayList<>();
-        List<String> versionStrings = new ArrayList<>();
-        List<String> installers = new ArrayList<>();
-
-        // Add in all the apps for every user/profile.
-        for (UserInfo profile : users) {
-            List<PackageInfo> pi =
-                    pm.getInstalledPackagesAsUser(PackageManager.MATCH_KNOWN_PACKAGES, profile.id);
-            for (int j = 0; j < pi.size(); j++) {
-                if (pi.get(j).applicationInfo != null) {
-                    String installer;
-                    try {
-                        installer = pm.getInstallerPackageName(pi.get(j).packageName);
-                    } catch (IllegalArgumentException e) {
-                        installer = "";
+        ParcelFileDescriptor[] fds;
+        try {
+            fds = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to create a pipe to send uid map data.", e);
+            return;
+        }
+        sStatsd.informAllUidData(fds[0]);
+        try {
+            fds[0].close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to close the read side of the pipe.", e);
+        }
+        final ParcelFileDescriptor writeFd = fds[1];
+        BackgroundThread.getHandler().post(() -> {
+            FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd);
+            try {
+                ProtoOutputStream output = new ProtoOutputStream(fout);
+                int numRecords = 0;
+                // Add in all the apps for every user/profile.
+                for (UserInfo profile : users) {
+                    List<PackageInfo> pi =
+                            pm.getInstalledPackagesAsUser(PackageManager.MATCH_KNOWN_PACKAGES,
+                                    profile.id);
+                    for (int j = 0; j < pi.size(); j++) {
+                        if (pi.get(j).applicationInfo != null) {
+                            String installer;
+                            try {
+                                installer = pm.getInstallerPackageName(pi.get(j).packageName);
+                            } catch (IllegalArgumentException e) {
+                                installer = "";
+                            }
+                            long applicationInfoToken =
+                                    output.start(ProtoStream.FIELD_TYPE_MESSAGE
+                                            | ProtoStream.FIELD_COUNT_REPEATED
+                                                    | APPLICATION_INFO_FIELD_ID);
+                            output.write(ProtoStream.FIELD_TYPE_INT32
+                                    | ProtoStream.FIELD_COUNT_SINGLE | UID_FIELD_ID,
+                                            pi.get(j).applicationInfo.uid);
+                            output.write(ProtoStream.FIELD_TYPE_INT64
+                                    | ProtoStream.FIELD_COUNT_SINGLE
+                                            | VERSION_FIELD_ID, pi.get(j).getLongVersionCode());
+                            output.write(ProtoStream.FIELD_TYPE_STRING
+                                    | ProtoStream.FIELD_COUNT_SINGLE | VERSION_STRING_FIELD_ID,
+                                            pi.get(j).versionName);
+                            output.write(ProtoStream.FIELD_TYPE_STRING
+                                    | ProtoStream.FIELD_COUNT_SINGLE
+                                            | PACKAGE_NAME_FIELD_ID, pi.get(j).packageName);
+                            output.write(ProtoStream.FIELD_TYPE_STRING
+                                    | ProtoStream.FIELD_COUNT_SINGLE
+                                            | INSTALLER_FIELD_ID,
+                                                    installer == null ? "" : installer);
+                            numRecords++;
+                            output.end(applicationInfoToken);
+                        }
                     }
-                    installers.add(installer == null ? "" : installer);
-                    uids.add(pi.get(j).applicationInfo.uid);
-                    versions.add(pi.get(j).getLongVersionCode());
-                    versionStrings.add(pi.get(j).versionName);
-                    apps.add(pi.get(j).packageName);
                 }
+                output.flush();
+                if (DEBUG) {
+                    Slog.d(TAG, "Sent data for " + numRecords + " apps");
+                }
+            } finally {
+                IoUtils.closeQuietly(fout);
             }
-        }
-        sStatsd.informAllUidData(toIntArray(uids), toLongArray(versions),
-                versionStrings.toArray(new String[versionStrings.size()]),
-                apps.toArray(new String[apps.size()]),
-                installers.toArray(new String[installers.size()]));
-        if (DEBUG) {
-            Slog.d(TAG, "Sent data for " + uids.size() + " apps");
-        }
+        });
     }
 
     private final static class AppUpdateReceiver extends BroadcastReceiver {