Add ITestDevice methods waitForDeviceOnline and getExternalStoreFreeSpace.

Change-Id: I1ab3ec4c99889f71b22e52cd675ee32511cc689b
diff --git a/src/com/android/tradefed/device/DeviceFatalError.java b/src/com/android/tradefed/device/DeviceFatalError.java
index b66a93a..41bb8c4 100644
--- a/src/com/android/tradefed/device/DeviceFatalError.java
+++ b/src/com/android/tradefed/device/DeviceFatalError.java
@@ -31,7 +31,7 @@
      *
      * @param msg a descriptive error message of the error
      */
-    DeviceFatalError(String msg) {
+    public DeviceFatalError(String msg) {
         super(msg);
     }
 }
diff --git a/src/com/android/tradefed/device/DeviceStateMonitor.java b/src/com/android/tradefed/device/DeviceStateMonitor.java
index 8deebce..f1a72ef 100644
--- a/src/com/android/tradefed/device/DeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/DeviceStateMonitor.java
@@ -41,7 +41,6 @@
 
     private static final int FASTBOOT_POLL_ATTEMPTS = 5;
 
-
     /** the ratio of time to wait for device to be online vs responsive in
      * {@link DeviceManager#waitForDeviceAvailable(ITestDevice, long)}.
      */
@@ -49,7 +48,7 @@
 
     /** The  time in ms to wait for a device to boot. */
     // TODO: make this configurable - or auto-scale according to device performance ?
-    private static final int DEFAULT_BOOT_TIMEOUT = 4 * 60 * 1000;
+    private static final long DEFAULT_BOOT_TIMEOUT = 4 * 60 * 1000;
 
     DeviceStateMonitor(IDevice device, IAndroidDebugBridge adbBridge) {
         mDevice = device;
@@ -59,22 +58,29 @@
     /**
      * {@inheritDoc}
      */
-    public boolean waitForDevice(long time) {
-        return waitForDeviceState(TestDeviceState.ONLINE, time);
+    public boolean waitForDeviceOnline(long waitTime) {
+        return waitForDeviceState(TestDeviceState.ONLINE, waitTime);
     }
 
     /**
      * {@inheritDoc}
      */
-    public boolean waitForDeviceNotAvailable(long time) {
-        return waitForDeviceState(TestDeviceState.NOT_AVAILABLE, time);
+    public boolean waitForDeviceOnline() {
+        return waitForDeviceOnline((long)(DEFAULT_BOOT_TIMEOUT*WAIT_DEVICE_RATIO));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean waitForDeviceNotAvailable(long waitTime) {
+        return waitForDeviceState(TestDeviceState.NOT_AVAILABLE, waitTime);
     }
 
     /**
      * {@inheritDoc}
      */
     public boolean waitForDeviceAvailable(final long waitTime) {
-        if (waitForDevice((long)(waitTime*WAIT_DEVICE_RATIO))) {
+        if (waitForDeviceOnline((long)(waitTime*WAIT_DEVICE_RATIO))) {
             return waitForPmResponsive((long)(waitTime*(1-WAIT_DEVICE_RATIO)));
         }
         return false;
diff --git a/src/com/android/tradefed/device/IDeviceStateMonitor.java b/src/com/android/tradefed/device/IDeviceStateMonitor.java
index 55a9cb4..c297258 100644
--- a/src/com/android/tradefed/device/IDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/IDeviceStateMonitor.java
@@ -33,7 +33,18 @@
      *
      * @return <code>true</code> if device becomes online before time expires
      */
-    public boolean waitForDevice(long time);
+    public boolean waitForDeviceOnline(long time);
+
+    /**
+     * Waits for device to be online using standard boot timeout.
+     * <p/>
+     * Note: this method will return once device is visible via DDMS. It does not guarantee that the
+     * device is actually responsive to adb commands - use {@link #waitForDeviceAvailable()}
+     * instead.
+     *
+     * @return <code>true</code> if device becomes online before time expires
+     */
+    public boolean waitForDeviceOnline();
 
     /**
      * Waits for the device to be responsive and available for testing.
@@ -56,20 +67,20 @@
     /**
      * Waits for the device to be in bootloader.
      *
-     * @param time the maximum time in ms to wait
+     * @param waitTime the maximum time in ms to wait
      *
      * @return <code>true</code> if device is in bootloader before time expires
      */
-    public boolean waitForDeviceBootloader(long time);
+    public boolean waitForDeviceBootloader(long waitTime);
 
     /**
      * Waits for the device to be not available
      *
-     * @param time the maximum time in ms to wait
+     * @param waitTime the maximum time in ms to wait
      *
      * @return <code>true</code> if device becomes unavailable
      */
-    public boolean waitForDeviceNotAvailable(long time);
+    public boolean waitForDeviceNotAvailable(long waitTime);
 
 
     /**
diff --git a/src/com/android/tradefed/device/ITestDevice.java b/src/com/android/tradefed/device/ITestDevice.java
index 78fc87b..b0a362d 100644
--- a/src/com/android/tradefed/device/ITestDevice.java
+++ b/src/com/android/tradefed/device/ITestDevice.java
@@ -146,6 +146,15 @@
     public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException;
 
     /**
+     * Helper method to determine amount of free space on device external storage.
+     *
+     * @return the amount of free space in KB
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     * recovered.
+     */
+    public long getExternalStoreFreeSpace() throws DeviceNotAvailableException;
+
+    /**
      * Grabs a snapshot stream of the logcat data.
      */
     public InputStream getLogcat();
@@ -191,4 +200,13 @@
      * recovered.
      */
     public void waitForDeviceAvailable() throws DeviceNotAvailableException;
+
+    /**
+     * Waits for the device to be visible via adb. Note the device may not necessarily be
+     * responsive to commands.
+     *
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     * recovered.
+     */
+    public void waitForDeviceOnline() throws DeviceNotAvailableException;
 }
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 50e6df2..3435c57 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -34,6 +34,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Default implementation of a {@link ITestDevice}
@@ -204,11 +206,7 @@
     }
 
     /**
-     * Helper method to determine if file on device exists.
-     *
-     * @param destPath the absolute path of file on device to check
-     * @return <code>true</code> if file exists, <code>false</code> otherwise.
-     * @throws DeviceNotAvailableException if device communication is lost
+     * {@inheritDoc}
      */
     public boolean doesFileExist(String destPath) throws DeviceNotAvailableException {
         String lsGrep = executeShellCommand(String.format("ls \"%s\"", destPath));
@@ -218,6 +216,28 @@
     /**
      * {@inheritDoc}
      */
+    public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
+        String externalStorePath = getIDevice().getMountPoint(
+                IDevice.MNT_EXTERNAL_STORAGE);
+        String output = executeShellCommand(String.format("df %s", externalStorePath));
+        final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
+        Matcher patternMatcher = freeSpacePattern.matcher(output);
+        if (patternMatcher.find()) {
+            String freeSpaceString = patternMatcher.group(1);
+            try {
+                return Long.parseLong(freeSpaceString);
+            } catch (NumberFormatException e) {
+                // fall through
+            }
+        }
+        Log.e(LOG_TAG, String.format(
+                "free space command output \"%s\" did not match expected pattern ", output));
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
         final String[] fullCmd = buildAdbCommand(cmdArgs);
         final String[] output = new String[1];
@@ -626,6 +646,16 @@
         return true;
     }
 
+
+    /**
+     * {@inheritDoc}
+     */
+    public void waitForDeviceOnline() throws DeviceNotAvailableException {
+        if (!mMonitor.waitForDeviceOnline()) {
+            recoverDevice();
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/device/WaitDeviceRecovery.java b/src/com/android/tradefed/device/WaitDeviceRecovery.java
index 6551b35..1c70bdf 100644
--- a/src/com/android/tradefed/device/WaitDeviceRecovery.java
+++ b/src/com/android/tradefed/device/WaitDeviceRecovery.java
@@ -35,8 +35,6 @@
             description="maximum time in ms to wait for a single device recovery command")
     private long mWaitTime = 4 * 60 * 1000;
 
-
-
     /**
      * {@inheritDoc}
      */
@@ -50,7 +48,7 @@
         RunUtil.sleep(INITIAL_PAUSE_TIME);
 
         // wait for device online
-        if (!monitor.waitForDevice(mWaitTime)) {
+        if (!monitor.waitForDeviceOnline(mWaitTime)) {
             throw new DeviceNotAvailableException(String.format("Could not find device %s",
                     device.getSerialNumber()));
         }
@@ -70,7 +68,7 @@
         // wait for device in bootloader
         if (!monitor.waitForDeviceBootloader(mWaitTime)) {
             throw new DeviceNotAvailableException(String.format("Could not find device %s in" +
-            		"bootloader", device.getSerialNumber()));
+                    "bootloader", device.getSerialNumber()));
         }
     }
 }
diff --git a/tests/src/com/android/tradefed/device/StubTestDevice.java b/tests/src/com/android/tradefed/device/StubTestDevice.java
index f113f3e..71c05ac 100644
--- a/tests/src/com/android/tradefed/device/StubTestDevice.java
+++ b/tests/src/com/android/tradefed/device/StubTestDevice.java
@@ -157,6 +157,19 @@
      */
     public void waitForDeviceAvailable() throws DeviceNotAvailableException {
         // ignore
+    }
 
+    /**
+     * {@inheritDoc}
+     */
+    public void waitForDeviceOnline() throws DeviceNotAvailableException {
+        // ignore
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
+        return 0;
     }
 }
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index 18649a0..4e8be37 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -165,6 +165,62 @@
     }
 
     /**
+     * Unit test for {@link TestDevice#getExternalStoreFreeSpace()}.
+     * <p/>
+     * Verify that output of 'adb shell df' command is parsed correctly.
+     */
+    public void testGetExternalStoreFreeSpace() throws Exception {
+        final String mntPoint = "/mnt/sdcard";
+        final String expectedCmd = "df " + mntPoint;
+        final String dfOutput =
+            "/mnt/sdcard: 3864064K total, 1282880K used, 2581184K available (block size 32768)";
+
+        EasyMock.expect(mMockIDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
+                mntPoint);
+        // expect shell command to be called, and return the test df output
+        mMockIDevice.executeShellCommand(EasyMock.eq(expectedCmd), (IShellOutputReceiver)
+                EasyMock.anyObject());
+        EasyMock.expectLastCall().andDelegateTo(
+              new MockDevice() {
+                  @Override
+                  public void executeShellCommand(String cmd, IShellOutputReceiver receiver) {
+                      byte[] inputData = dfOutput.getBytes();
+                      receiver.addOutput(inputData, 0, inputData.length);
+                  }
+              });
+        EasyMock.replay(mMockIDevice);
+        assertEquals(2581184, mTestDevice.getExternalStoreFreeSpace());
+    }
+
+    /**
+     * Unit test for {@link TestDevice#getExternalStoreFreeSpace()}.
+     * <p/>
+     * Verify behavior
+     */
+    public void testGetExternalStoreFreeSpace_badOutput() throws Exception {
+        final String mntPoint = "/mnt/sdcard";
+        final String expectedCmd = "df " + mntPoint;
+        final String dfOutput =
+            "/mnt/sdcard: blaH";
+
+        EasyMock.expect(mMockIDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE)).andReturn(
+                mntPoint);
+        // expect shell command to be called, and return the test df output
+        mMockIDevice.executeShellCommand(EasyMock.eq(expectedCmd), (IShellOutputReceiver)
+                EasyMock.anyObject());
+        EasyMock.expectLastCall().andDelegateTo(
+              new MockDevice() {
+                  @Override
+                  public void executeShellCommand(String cmd, IShellOutputReceiver receiver) {
+                      byte[] inputData = dfOutput.getBytes();
+                      receiver.addOutput(inputData, 0, inputData.length);
+                  }
+              });
+        EasyMock.replay(mMockIDevice);
+        assertEquals(0, mTestDevice.getExternalStoreFreeSpace());
+    }
+
+    /**
      * Test {@link TestDevice#runInstrumentationTests(IRemoteAndroidTestRunner, Collection)}
      * success case.
      */