Merge "[statsd/tools] allow multiple connected devices" into rvc-dev
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
index a381f9c..2909048 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/Utils.java
@@ -17,16 +17,23 @@
 
 import com.android.os.StatsLog.ConfigMetricsReportList;
 
+import com.google.common.io.Files;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Utilities for local use of statsd.
@@ -80,7 +87,8 @@
      * @throws InterruptedException
      */
     public static ConfigMetricsReportList getReportList(long configId, boolean clearData,
-            boolean useShellUid, Logger logger) throws IOException, InterruptedException {
+            boolean useShellUid, Logger logger, String deviceSerial)
+            throws IOException, InterruptedException {
         try {
             File outputFile = File.createTempFile("statsdret", ".bin");
             outputFile.deleteOnExit();
@@ -88,6 +96,8 @@
                     outputFile,
                     logger,
                     "adb",
+                    "-s",
+                    deviceSerial,
                     "shell",
                     CMD_DUMP_REPORT,
                     useShellUid ? SHELL_UID : "",
@@ -117,12 +127,14 @@
      * @throws IOException
      * @throws InterruptedException
      */
-    public static void logAppBreadcrumb(int label, int state, Logger logger)
+    public static void logAppBreadcrumb(int label, int state, Logger logger, String deviceSerial)
             throws IOException, InterruptedException {
         runCommand(
                 null,
                 logger,
                 "adb",
+                "-s",
+                deviceSerial,
                 "shell",
                 CMD_LOG_APP_BREADCRUMB,
                 String.valueOf(label),
@@ -145,13 +157,14 @@
      * Algorithm: true if (sdk >= minSdk) || (sdk == minSdk-1 && codeName.startsWith(minCodeName))
      * If all else fails, assume it will work (letting future commands deal with any errors).
      */
-    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename) {
+    public static boolean isAcceptableStatsd(Logger logger, int minSdk, String minCodename,
+            String deviceSerial) {
         BufferedReader in = null;
         try {
             File outFileSdk = File.createTempFile("shelltools_sdk", "tmp");
             outFileSdk.deleteOnExit();
             runCommand(outFileSdk, logger,
-                    "adb", "shell", "getprop", "ro.build.version.sdk");
+                    "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.sdk");
             in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileSdk)));
             // If NullPointerException/NumberFormatException/etc., just catch and return true.
             int sdk = Integer.parseInt(in.readLine().trim());
@@ -162,7 +175,7 @@
                 File outFileCode = File.createTempFile("shelltools_codename", "tmp");
                 outFileCode.deleteOnExit();
                 runCommand(outFileCode, logger,
-                        "adb", "shell", "getprop", "ro.build.version.codename");
+                        "adb", "-s", deviceSerial, "shell", "getprop", "ro.build.version.codename");
                 in = new BufferedReader(new InputStreamReader(new FileInputStream(outFileCode)));
                 return in.readLine().startsWith(minCodename);
             } else {
@@ -190,4 +203,30 @@
             return record.getMessage() + "\n";
         }
     }
+
+    /**
+     * Parse the result of "adb devices" to return the list of connected devices.
+     * @param logger Logger to log error messages
+     * @return List of the serial numbers of the connected devices.
+     */
+    public static List<String> getDeviceSerials(Logger logger) {
+        try {
+            ArrayList<String> devices = new ArrayList<>();
+            File outFile = File.createTempFile("device_serial", "tmp");
+            outFile.deleteOnExit();
+            Utils.runCommand(outFile, logger, "adb", "devices");
+            List<String> outputLines = Files.readLines(outFile, Charset.defaultCharset());
+            Pattern regex = Pattern.compile("^(.*)\tdevice$");
+            for (String line : outputLines) {
+                Matcher m = regex.matcher(line);
+                if (m.find()) {
+                    devices.add(m.group(1));
+                }
+            }
+            return devices;
+        } catch (Exception ex) {
+            logger.log(Level.SEVERE, "Failed to list connected devices: " + ex.getMessage());
+        }
+        return null;
+    }
 }
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
index 2eb4660..7db5141 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/localdrive/LocalDrive.java
@@ -26,6 +26,8 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.List;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -49,7 +51,7 @@
     public static final String HELP_STRING =
         "Usage:\n\n" +
 
-        "statsd_localdrive upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] upload CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Uploads the given statsd config file (in binary or human-readable-text format).\n" +
         "  If a config with this id already exists, removes it first.\n" +
         "    CONFIG_FILE    Location of config file on host.\n" +
@@ -59,12 +61,12 @@
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] update CONFIG_FILE [CONFIG_ID] [--binary]\n" +
         "  Same as upload, but does not remove the old config first (if it already exists).\n" +
         // Similar to: adb shell cmd stats config update SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]\n" +
         "  Prints the output statslog data (in binary or human-readable-text format).\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         "    --binary       Output should be in binary, instead of default human-readable text.\n" +
@@ -75,13 +77,13 @@
         //                                                      --include_current_bucket --proto
         "\n" +
 
-        "statsd_localdrive remove [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] remove [CONFIG_ID]\n" +
         "  Removes the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Equivalent to: adb shell cmd stats config remove SHELL_UID CONFIG_ID
         "\n" +
 
-        "statsd_localdrive clear [CONFIG_ID]\n" +
+        "statsd_localdrive [-s DEVICE_SERIAL] clear [CONFIG_ID]\n" +
         "  Clears the data associated with the config.\n" +
         "    CONFIG_ID      Long ID of the config. If absent, uses " + DEFAULT_CONFIG_ID + ".\n" +
         // Similar to: adb shell cmd stats dump-report SHELL_UID CONFIG_ID
@@ -94,29 +96,59 @@
     /** Usage: make statsd_localdrive && statsd_localdrive */
     public static void main(String[] args) {
         Utils.setUpLogger(sLogger, DEBUG);
+        if (args.length == 0) {
+            printHelp();
+            return;
+        }
 
-        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME)) {
+        int remainingArgsLength = args.length;
+        String deviceSerial = null;
+        if (args[0].equals("-s")) {
+            if (args.length == 1) {
+                printHelp();
+            }
+            deviceSerial = args[1];
+            remainingArgsLength -= 2;
+        }
+
+        List<String> connectedDevices = Utils.getDeviceSerials(sLogger);
+        if (connectedDevices == null || connectedDevices.size() == 0) {
+            sLogger.log(Level.SEVERE, "No device connected.");
+            return;
+        }
+        if (connectedDevices.size() == 1 && deviceSerial == null) {
+            deviceSerial = connectedDevices.get(0);
+        }
+
+        if (deviceSerial == null) {
+            sLogger.log(Level.SEVERE, "More than one devices connected. Please specify"
+                    + " with -s DEVICE_SERIAL");
+            return;
+        }
+
+        if (!Utils.isAcceptableStatsd(sLogger, MIN_SDK, MIN_CODENAME, deviceSerial)) {
             sLogger.severe("LocalDrive only works with statsd versions for Android "
                     + MIN_CODENAME + " or higher.");
             return;
         }
 
-        if (args.length > 0) {
-            switch (args[0]) {
+        int idx = args.length - remainingArgsLength;
+        if (remainingArgsLength > 0) {
+            switch (args[idx]) {
                 case "clear":
-                    cmdClear(args);
+                    cmdClear(args, idx, deviceSerial);
                     return;
                 case "get-data":
-                    cmdGetData(args);
+                    cmdGetData(args, idx, deviceSerial);
                     return;
                 case "remove":
-                    cmdRemove(args);
+                    cmdRemove(args, idx);
                     return;
                 case "update":
-                    cmdUpdate(args);
+                    cmdUpdate(args, idx, deviceSerial);
                     return;
                 case "upload":
-                    cmdUpload(args);
+                    cmdUpload(args, idx, deviceSerial);
                     return;
             }
         }
@@ -128,17 +160,18 @@
     }
 
     // upload CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpload(String[] args) {
-        return updateConfig(args, true);
+    private static boolean cmdUpload(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, true, deviceSerial);
     }
 
     // update CONFIG_FILE [CONFIG_ID] [--binary]
-    private static boolean cmdUpdate(String[] args) {
-        return updateConfig(args, false);
+    private static boolean cmdUpdate(String[] args, int idx, String deviceSerial) {
+        return updateConfig(args, idx, false, deviceSerial);
     }
 
-    private static boolean updateConfig(String[] args, boolean removeOldConfig) {
-        int argCount = args.length - 1; // Used up one for upload/update.
+    private static boolean updateConfig(String[] args, int idx, boolean removeOldConfig,
+            String deviceSerial) {
+        int argCount = args.length - 1 - idx; // Used up one for upload/update.
 
         // Get CONFIG_FILE
         if (argCount < 1) {
@@ -146,7 +179,7 @@
             printHelp();
             return false;
         }
-        final String origConfigLocation = args[1];
+        final String origConfigLocation = args[idx + 1];
         if (!new File(origConfigLocation).exists()) {
             sLogger.severe("Error - Cannot find the provided config file: " + origConfigLocation);
             return false;
@@ -154,13 +187,13 @@
         argCount--;
 
         // Get --binary
-        boolean binary = contains(args, 2, BINARY_FLAG);
+        boolean binary = contains(args, idx + 2, BINARY_FLAG);
         if (binary) argCount --;
 
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 2);
+            configId = getConfigId(argCount < 1, args, idx + 2);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -174,7 +207,8 @@
             try {
                 Utils.runCommand(null, sLogger, "adb", "shell", Utils.CMD_REMOVE_CONFIG,
                         Utils.SHELL_UID, String.valueOf(configId));
-                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+                Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                        deviceSerial);
             } catch (InterruptedException | IOException e) {
                 sLogger.severe("Failed to remove config: " + e.getMessage());
                 return false;
@@ -218,19 +252,19 @@
     }
 
     // get-data [CONFIG_ID] [--clear] [--binary] [--no-uid-map]
-    private static boolean cmdGetData(String[] args) {
-        boolean binary = contains(args, 1, BINARY_FLAG);
-        boolean noUidMap = contains(args, 1, NO_UID_MAP_FLAG);
-        boolean clearData = contains(args, 1, CLEAR_DATA);
+    private static boolean cmdGetData(String[] args, int idx, String deviceSerial) {
+        boolean binary = contains(args, idx + 1, BINARY_FLAG);
+        boolean noUidMap = contains(args, idx + 1, NO_UID_MAP_FLAG);
+        boolean clearData = contains(args, idx + 1, CLEAR_DATA);
 
         // Get CONFIG_ID
-        int argCount = args.length - 1; // Used up one for get-data.
+        int argCount = args.length - 1 - idx; // Used up one for get-data.
         if (binary) argCount--;
         if (noUidMap) argCount--;
         if (clearData) argCount--;
         long configId;
         try {
-            configId = getConfigId(argCount < 1, args, 1);
+            configId = getConfigId(argCount < 1, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -243,7 +277,8 @@
         // Even if the args request no modifications, we still parse it to make sure it's valid.
         ConfigMetricsReportList reportList;
         try {
-            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger);
+            reportList = Utils.getReportList(configId, clearData, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -274,11 +309,11 @@
     }
 
     // clear [CONFIG_ID]
-    private static boolean cmdClear(String[] args) {
+    private static boolean cmdClear(String[] args, int idx, String deviceSerial) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
@@ -287,7 +322,8 @@
         sLogger.fine(String.format("cmdClear with %d", configId));
 
         try {
-            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger);
+            Utils.getReportList(configId, true /* clearData */, true /* SHELL_UID */, sLogger,
+                    deviceSerial);
         } catch (IOException | InterruptedException e) {
             sLogger.severe("Failed to get report list: " + e.getMessage());
             return false;
@@ -296,11 +332,11 @@
     }
 
     // remove [CONFIG_ID]
-    private static boolean cmdRemove(String[] args) {
+    private static boolean cmdRemove(String[] args, int idx) {
         // Get CONFIG_ID
         long configId;
         try {
-            configId = getConfigId(false, args, 1);
+            configId = getConfigId(false, args, idx + 1);
         } catch (NumberFormatException e) {
             sLogger.severe("Invalid config id provided.");
             printHelp();
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 75518a3..2a7cfd3 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -37,6 +37,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -71,6 +72,7 @@
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
     private String mAdditionalAllowedPackage;
+    private String mDeviceSerial;
     private final Set<Long> mTrackedMetrics = new HashSet<>();
 
     public static void main(String[] args) {
@@ -81,15 +83,41 @@
 
         if (args.length < 1) {
             LOGGER.log(Level.SEVERE, "Usage: ./test_drive [-p additional_allowed_package] "
+                    + "[-s DEVICE_SERIAL_NUMBER]"
                     + "<atomId1> <atomId2> ... <atomIdN>");
             return;
         }
 
-        if (args.length >= 3 && args[0].equals("-p")) {
-            testDrive.mAdditionalAllowedPackage = args[1];
+        List<String> connectedDevices = Utils.getDeviceSerials(LOGGER);
+        if (connectedDevices == null || connectedDevices.size() == 0) {
+            LOGGER.log(Level.SEVERE, "No device connected.");
+            return;
         }
 
-        for (int i = testDrive.mAdditionalAllowedPackage == null ? 0 : 2; i < args.length; i++) {
+        int arg_index = 0;
+        while (arg_index < args.length) {
+            String arg = args[arg_index];
+            if (arg.equals("-p")) {
+                testDrive.mAdditionalAllowedPackage = args[++arg_index];
+            } else if (arg.equals("-s")) {
+                testDrive.mDeviceSerial = args[++arg_index];
+            } else {
+                break;
+            }
+            arg_index++;
+        }
+
+        if (connectedDevices.size() == 1 && testDrive.mDeviceSerial == null) {
+            testDrive.mDeviceSerial = connectedDevices.get(0);
+        }
+
+        if (testDrive.mDeviceSerial == null) {
+            LOGGER.log(Level.SEVERE, "More than one devices connected. Please specify"
+                    + " with -s DEVICE_SERIAL");
+            return;
+        }
+
+        for (int i = arg_index; i < args.length; i++) {
             try {
                 int atomId = Integer.valueOf(args[i]);
                 if (Atom.getDescriptor().findFieldByNumber(atomId) == null) {
@@ -109,7 +137,7 @@
                 LOGGER.log(Level.SEVERE, "Failed to create valid config.");
                 return;
             }
-            remoteConfigPath = testDrive.pushConfig(config);
+            remoteConfigPath = testDrive.pushConfig(config, testDrive.mDeviceSerial);
             LOGGER.info("Pushed the following config to statsd:");
             LOGGER.info(config.toString());
             if (!hasPulledAtom(trackedAtoms)) {
@@ -120,17 +148,18 @@
             } else {
                 LOGGER.info("Now wait for 1.5 minutes ...");
                 Thread.sleep(15_000);
-                Utils.logAppBreadcrumb(0, 0, LOGGER);
+                Utils.logAppBreadcrumb(0, 0, LOGGER, testDrive.mDeviceSerial);
                 Thread.sleep(75_000);
             }
             testDrive.dumpMetrics();
         } catch (Exception e) {
             LOGGER.log(Level.SEVERE, "Failed to test drive: " + e.getMessage(), e);
         } finally {
-            testDrive.removeConfig();
+            testDrive.removeConfig(testDrive.mDeviceSerial);
             if (remoteConfigPath != null) {
                 try {
-                    Utils.runCommand(null, LOGGER, "adb", "shell", "rm", remoteConfigPath);
+                    Utils.runCommand(null, LOGGER,
+                            "adb", "-s", testDrive.mDeviceSerial, "shell", "rm", remoteConfigPath);
                 } catch (Exception e) {
                     LOGGER.log(Level.WARNING,
                             "Unable to remove remote config file: " + remoteConfigPath, e);
@@ -140,7 +169,8 @@
     }
 
     private void dumpMetrics() throws Exception {
-        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER);
+        ConfigMetricsReportList reportList = Utils.getReportList(CONFIG_ID, true, false, LOGGER,
+                mDeviceSerial);
         // We may get multiple reports. Take the last one.
         ConfigMetricsReport report = reportList.getReports(reportList.getReportsCount() - 1);
         for (StatsLogReport statsLog : report.getMetricsList()) {
@@ -216,22 +246,24 @@
         return atomMatcherBuilder.build();
     }
 
-    private static String pushConfig(StatsdConfig config) throws IOException, InterruptedException {
+    private static String pushConfig(StatsdConfig config, String deviceSerial)
+            throws IOException, InterruptedException {
         File configFile = File.createTempFile("statsdconfig", ".config");
         configFile.deleteOnExit();
         Files.write(config.toByteArray(), configFile);
         String remotePath = "/data/local/tmp/" + configFile.getName();
-        Utils.runCommand(null, LOGGER, "adb", "push", configFile.getAbsolutePath(), remotePath);
-        Utils.runCommand(null, LOGGER,
-                "adb", "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "push", configFile.getAbsolutePath(), remotePath);
+        Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                "shell", "cat", remotePath, "|", Utils.CMD_UPDATE_CONFIG,
                 String.valueOf(CONFIG_ID));
         return remotePath;
     }
 
-    private static void removeConfig() {
+    private static void removeConfig(String deviceSerial) {
         try {
-            Utils.runCommand(null, LOGGER,
-                    "adb", "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
+            Utils.runCommand(null, LOGGER, "adb", "-s", deviceSerial,
+                    "shell", Utils.CMD_REMOVE_CONFIG, String.valueOf(CONFIG_ID));
         } catch (Exception e) {
             LOGGER.log(Level.SEVERE, "Failed to remove config: " + e.getMessage());
         }