<NME> SpoonUtils.java
<BEF> package com.squareup.spoon;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.IDevice;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.madgag.gif.fmsware.AnimatedGifEncoder;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.time.Duration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;

import static com.android.ddmlib.FileListingService.FileEntry;
import static com.android.ddmlib.FileListingService.TYPE_DIRECTORY;

/** Utilities for executing instrumentation tests on devices. */
public final class SpoonUtils {
  private static final Pattern SERIAL_VALIDATION = Pattern.compile("[^a-zA-Z0-9_-]");
  static final Gson GSON = new GsonBuilder() //
      .registerTypeAdapter(File.class, new TypeAdapter<File>() {
        @Override public void write(JsonWriter jsonWriter, File file) throws IOException {
          if (file == null) {
            jsonWriter.nullValue();
          } else {
            jsonWriter.value(file.getAbsolutePath());
          }
        }

        @Override public File read(JsonReader jsonReader) throws IOException {
          return new File(jsonReader.nextString());
        }
      }) //
      .setPrettyPrinting() //
      .create();

  /** Fetch or create a real device that corresponds to a device model. */
  static IDevice obtainRealDevice(AndroidDebugBridge adb, String serial) {
    // Get an existing real device.
    for (IDevice adbDevice : adb.getDevices()) {
      if (adbDevice.getSerialNumber().equals(serial)) {
        return adbDevice;
      }
    }
    throw new IllegalArgumentException("Unknown device serial: " + serial);
  }

  static String sanitizeSerial(String serial) {
    return SERIAL_VALIDATION.matcher(serial).replaceAll("_");
  }

  /** Get a {@link FileEntry} for an arbitrary path. */
  static FileEntry obtainDirectoryFileEntry(String path) {
    try {
      FileEntry lastEntry = null;
      Constructor<FileEntry> c =
          FileEntry.class.getDeclaredConstructor(FileEntry.class, String.class, int.class,
              boolean.class);
      c.setAccessible(true);
      for (String part : path.split("/")) {
        lastEntry = c.newInstance(lastEntry, part, TYPE_DIRECTORY, lastEntry == null);
      }
      return lastEntry;
    } catch (NoSuchMethodException ignored) {
    } catch (InvocationTargetException ignored) {
    } catch (InstantiationException ignored) {
    } catch (IllegalAccessException ignored) {
    }
    return null;
  }

  /** Turn on debug logging in ddmlib classes. */
  static void setDdmlibInternalLoggingLevel() {
    DdmPreferences.setLogLevel("debug");
  }

  /** Find all device serials that are plugged in through ADB. */
  public static Set<String> findAllDevices(AndroidDebugBridge adb) {
    return findAllDevices(adb, null);
  }

  /**
   * Find all device serials that are plugged in through ADB.
  }

  /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */
  static AndroidDebugBridge initAdb(File sdk) {
    AndroidDebugBridge.initIfNeeded(false);
    File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");
    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);
    waitForAdb(adb);
    return adb;
  }

        DeviceDetails deviceDetails = DeviceDetails.createForDevice(realDevice);
        int apiLevel = deviceDetails.getApiLevel();
        if (apiLevel == DeviceDetails.UNKNOWN_API_LEVEL || apiLevel >= minApiLevel) {
          devices.add(realDevice.getSerialNumber());
        }
      }
    }
    return devices;
  }

  /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */
  public static AndroidDebugBridge initAdb(File sdk, Duration timeOut) {
    AndroidDebugBridge.initIfNeeded(false);
    File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");
    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);
    waitForAdb(adb, timeOut);
    return adb;
  }

  static void createAnimatedGif(List<File> testScreenshots, File animatedGif) throws IOException {
    AnimatedGifEncoder encoder = new AnimatedGifEncoder();
    encoder.finish();
  }

  private static void waitForAdb(AndroidDebugBridge adb) {
    long timeOutMs = TimeUnit.SECONDS.toMillis(30);
    long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);
    while (!adb.hasInitialDeviceList() && timeOutMs > 0) {
      try {
    int height = 0;
    for (File testScreenshot : testScreenshots) {
      BufferedImage bufferedImage = ImageIO.read(testScreenshot);
      width = Math.max(bufferedImage.getWidth(), width);
      height = Math.max(bufferedImage.getHeight(), height);
    }
    encoder.setSize(width, height);

    for (File testScreenshot : testScreenshots) {
      encoder.addFrame(ImageIO.read(testScreenshot));
    }

    encoder.finish();
  }

  private static void waitForAdb(AndroidDebugBridge adb, Duration timeOut) {
    long timeOutMs = timeOut.toMillis();
    long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);
    while (!adb.hasInitialDeviceList() && timeOutMs > 0) {
      try {
        Thread.sleep(sleepTimeMs);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
      timeOutMs -= sleepTimeMs;
    }
    if (timeOutMs <= 0 && !adb.hasInitialDeviceList()) {
      throw new RuntimeException("Timeout getting device list.", null);
    }
  }

  private SpoonUtils() {
    // No instances.
  }
}

<MSG> Add a timeout to initAdb

<DFF> @@ -97,11 +97,11 @@ final class SpoonUtils {
   }
 
   /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */
-  static AndroidDebugBridge initAdb(File sdk) {
+  static AndroidDebugBridge initAdb(File sdk, long timeOutMs) {
     AndroidDebugBridge.initIfNeeded(false);
     File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");
     AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);
-    waitForAdb(adb);
+    waitForAdb(adb, timeOutMs);
     return adb;
   }
 
@@ -129,8 +129,7 @@ final class SpoonUtils {
     encoder.finish();
   }
 
-  private static void waitForAdb(AndroidDebugBridge adb) {
-    long timeOutMs = TimeUnit.SECONDS.toMillis(30);
+  private static void waitForAdb(AndroidDebugBridge adb, long timeOutMs) {
     long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);
     while (!adb.hasInitialDeviceList() && timeOutMs > 0) {
       try {

{'commit': 'diff --git a/spoon-runner/src/main/java/com/squareup/spoon/SpoonDeviceRunner.java b/spoon-runner/src/main/java/com/squareup/spoon/SpoonDeviceRunner.java\nindex ef4e127..85bc0da 100644\n--- a/spoon-runner/src/main/java/com/squareup/spoon/SpoonDeviceRunner.java\n+++ b/spoon-runner/src/main/java/com/squareup/spoon/SpoonDeviceRunner.java\n@@ -433,7 +433,7 @@ public final class SpoonDeviceRunner {\n       SpoonDeviceRunner target = GSON.fromJson(reader, SpoonDeviceRunner.class);\n       reader.close();\n \n-      AndroidDebugBridge adb = SpoonUtils.initAdb(target.sdk);\n+      AndroidDebugBridge adb = SpoonUtils.initAdb(target.sdk, target.adbTimeout);\n       DeviceResult result = target.run(adb);\n       AndroidDebugBridge.terminate();\n \ndiff --git a/spoon-runner/src/main/java/com/squareup/spoon/SpoonRunner.java b/spoon-runner/src/main/java/com/squareup/spoon/SpoonRunner.java\nindex 4e8eb3f..6d3d112 100644\n--- a/spoon-runner/src/main/java/com/squareup/spoon/SpoonRunner.java\n+++ b/spoon-runner/src/main/java/com/squareup/spoon/SpoonRunner.java\n@@ -97,7 +97,7 @@ public final class SpoonRunner {\n     checkArgument(applicationApk.exists(), "Could not find application APK.");\n     checkArgument(instrumentationApk.exists(), "Could not find instrumentation APK.");\n \n-    AndroidDebugBridge adb = SpoonUtils.initAdb(androidSdk);\n+    AndroidDebugBridge adb = SpoonUtils.initAdb(androidSdk, adbTimeout);\n \n     try {\n       // If we were given an empty serial set, load all available devices.\ndiff --git a/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java b/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java\nindex d4bacfb..b35db1d 100644\n--- a/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java\n+++ b/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java\n@@ -97,11 +97,11 @@ final class SpoonUtils {\n   }\n \n   /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */\n-  static AndroidDebugBridge initAdb(File sdk) {\n+  static AndroidDebugBridge initAdb(File sdk, long timeOutMs) {\n     AndroidDebugBridge.initIfNeeded(false);\n     File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");\n     AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);\n-    waitForAdb(adb);\n+    waitForAdb(adb, timeOutMs);\n     return adb;\n   }\n \n@@ -129,8 +129,7 @@ final class SpoonUtils {\n     encoder.finish();\n   }\n \n-  private static void waitForAdb(AndroidDebugBridge adb) {\n-    long timeOutMs = TimeUnit.SECONDS.toMillis(30);\n+  private static void waitForAdb(AndroidDebugBridge adb, long timeOutMs) {\n     long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);\n     while (!adb.hasInitialDeviceList() && timeOutMs > 0) {\n       try {\n', 'commit_subject': 'Add a timeout to initAdb', 'repo_name': 'square/spoon', 'committer': {'name': 'intelradoux', 'email': '80cfd02d3ade18d0bdb2cdd3f3ec0224e7f763ed@users.noreply.github.com', 'time_sec': '1445091831', 'tz_offset': '120', 'date': {'seconds': '1445091831'}}, 'commit_tree': '43e2ad78ff9c8943f2b117ea14b6307dde8cc78d', 'commit_message': 'Add a timeout to initAdb\n', 'language_name': 'Java', 'license': 'apache-2.0', 'hunks': '@@ -97,11 +97,11 @@ final class SpoonUtils {\n   }\n \n   /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */\n-  static AndroidDebugBridge initAdb(File sdk) {\n+  static AndroidDebugBridge initAdb(File sdk, long timeOutMs) {\n     AndroidDebugBridge.initIfNeeded(false);\n     File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");\n     AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);\n-    waitForAdb(adb);\n+    waitForAdb(adb, timeOutMs);\n     return adb;\n   }\n \n@@ -129,8 +129,7 @@ final class SpoonUtils {\n     encoder.finish();\n   }\n \n-  private static void waitForAdb(AndroidDebugBridge adb) {\n-    long timeOutMs = TimeUnit.SECONDS.toMillis(30);\n+  private static void waitForAdb(AndroidDebugBridge adb, long timeOutMs) {\n     long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);\n     while (!adb.hasInitialDeviceList() && timeOutMs > 0) {\n       try {\n', 'addition_count': 3, 'deletion_count': 4, 'src_file': 'a/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java', 'tgt_file': 'b/spoon-runner/src/main/java/com/squareup/spoon/SpoonUtils.java', 'file_extension': '.java', 'before_file': 'package com.squareup.spoon;\n\nimport com.android.ddmlib.AndroidDebugBridge;\nimport com.android.ddmlib.DdmPreferences;\nimport com.android.ddmlib.IDevice;\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.stream.JsonReader;\nimport com.google.gson.stream.JsonWriter;\nimport com.madgag.gif.fmsware.AnimatedGifEncoder;\nimport java.awt.Color;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.time.Duration;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\nimport javax.imageio.ImageIO;\nimport org.apache.commons.io.FileUtils;\n\nimport static com.android.ddmlib.FileListingService.FileEntry;\nimport static com.android.ddmlib.FileListingService.TYPE_DIRECTORY;\n\n/** Utilities for executing instrumentation tests on devices. */\npublic final class SpoonUtils {\n  private static final Pattern SERIAL_VALIDATION = Pattern.compile("[^a-zA-Z0-9_-]");\n  static final Gson GSON = new GsonBuilder() //\n      .registerTypeAdapter(File.class, new TypeAdapter<File>() {\n        @Override public void write(JsonWriter jsonWriter, File file) throws IOException {\n          if (file == null) {\n            jsonWriter.nullValue();\n          } else {\n            jsonWriter.value(file.getAbsolutePath());\n          }\n        }\n\n        @Override public File read(JsonReader jsonReader) throws IOException {\n          return new File(jsonReader.nextString());\n        }\n      }) //\n      .setPrettyPrinting() //\n      .create();\n\n  /** Fetch or create a real device that corresponds to a device model. */\n  static IDevice obtainRealDevice(AndroidDebugBridge adb, String serial) {\n    // Get an existing real device.\n    for (IDevice adbDevice : adb.getDevices()) {\n      if (adbDevice.getSerialNumber().equals(serial)) {\n        return adbDevice;\n      }\n    }\n    throw new IllegalArgumentException("Unknown device serial: " + serial);\n  }\n\n  static String sanitizeSerial(String serial) {\n    return SERIAL_VALIDATION.matcher(serial).replaceAll("_");\n  }\n\n  /** Get a {@link FileEntry} for an arbitrary path. */\n  static FileEntry obtainDirectoryFileEntry(String path) {\n    try {\n      FileEntry lastEntry = null;\n      Constructor<FileEntry> c =\n          FileEntry.class.getDeclaredConstructor(FileEntry.class, String.class, int.class,\n              boolean.class);\n      c.setAccessible(true);\n      for (String part : path.split("/")) {\n        lastEntry = c.newInstance(lastEntry, part, TYPE_DIRECTORY, lastEntry == null);\n      }\n      return lastEntry;\n    } catch (NoSuchMethodException ignored) {\n    } catch (InvocationTargetException ignored) {\n    } catch (InstantiationException ignored) {\n    } catch (IllegalAccessException ignored) {\n    }\n    return null;\n  }\n\n  /** Turn on debug logging in ddmlib classes. */\n  static void setDdmlibInternalLoggingLevel() {\n    DdmPreferences.setLogLevel("debug");\n  }\n\n  /** Find all device serials that are plugged in through ADB. */\n  public static Set<String> findAllDevices(AndroidDebugBridge adb) {\n    return findAllDevices(adb, null);\n  }\n\n  /**\n   * Find all device serials that are plugged in through ADB.\n  }\n\n  /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */\n  static AndroidDebugBridge initAdb(File sdk) {\n    AndroidDebugBridge.initIfNeeded(false);\n    File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");\n    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);\n    waitForAdb(adb);\n    return adb;\n  }\n\n        DeviceDetails deviceDetails = DeviceDetails.createForDevice(realDevice);\n        int apiLevel = deviceDetails.getApiLevel();\n        if (apiLevel == DeviceDetails.UNKNOWN_API_LEVEL || apiLevel >= minApiLevel) {\n          devices.add(realDevice.getSerialNumber());\n        }\n      }\n    }\n    return devices;\n  }\n\n  /** Get an {@link com.android.ddmlib.AndroidDebugBridge} instance given an SDK path. */\n  public static AndroidDebugBridge initAdb(File sdk, Duration timeOut) {\n    AndroidDebugBridge.initIfNeeded(false);\n    File adbPath = FileUtils.getFile(sdk, "platform-tools", "adb");\n    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(adbPath.getAbsolutePath(), false);\n    waitForAdb(adb, timeOut);\n    return adb;\n  }\n\n  static void createAnimatedGif(List<File> testScreenshots, File animatedGif) throws IOException {\n    AnimatedGifEncoder encoder = new AnimatedGifEncoder();\n    encoder.finish();\n  }\n\n  private static void waitForAdb(AndroidDebugBridge adb) {\n    long timeOutMs = TimeUnit.SECONDS.toMillis(30);\n    long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);\n    while (!adb.hasInitialDeviceList() && timeOutMs > 0) {\n      try {\n    int height = 0;\n    for (File testScreenshot : testScreenshots) {\n      BufferedImage bufferedImage = ImageIO.read(testScreenshot);\n      width = Math.max(bufferedImage.getWidth(), width);\n      height = Math.max(bufferedImage.getHeight(), height);\n    }\n    encoder.setSize(width, height);\n\n    for (File testScreenshot : testScreenshots) {\n      encoder.addFrame(ImageIO.read(testScreenshot));\n    }\n\n    encoder.finish();\n  }\n\n  private static void waitForAdb(AndroidDebugBridge adb, Duration timeOut) {\n    long timeOutMs = timeOut.toMillis();\n    long sleepTimeMs = TimeUnit.SECONDS.toMillis(1);\n    while (!adb.hasInitialDeviceList() && timeOutMs > 0) {\n      try {\n        Thread.sleep(sleepTimeMs);\n      } catch (InterruptedException e) {\n        throw new RuntimeException(e);\n      }\n      timeOutMs -= sleepTimeMs;\n    }\n    if (timeOutMs <= 0 && !adb.hasInitialDeviceList()) {\n      throw new RuntimeException("Timeout getting device list.", null);\n    }\n  }\n\n  private SpoonUtils() {\n    // No instances.\n  }\n}\n', 'message': 'Add a timeout to initAdb\n', 'author_name': 'intelradoux', 'lang': 'java'}