Merge "Add util methods to read partial files." into android10-dev
diff --git a/src/com/android/tradefed/util/FileUtil.java b/src/com/android/tradefed/util/FileUtil.java
index 9d919e9..cf15bc2 100644
--- a/src/com/android/tradefed/util/FileUtil.java
+++ b/src/com/android/tradefed/util/FileUtil.java
@@ -564,13 +564,30 @@
      * @throws FileNotFoundException
      */
     public static String readStringFromFile(File sourceFile) throws IOException {
-        FileInputStream is = null;
-        try {
-            // no need to buffer since StreamUtil does
-            is = new FileInputStream(sourceFile);
-            return StreamUtil.getStringFromStream(is);
-        } finally {
-            StreamUtil.close(is);
+        return readStringFromFile(sourceFile, 0, 0);
+    }
+
+    /**
+     * A helper method for reading partial string data from a file
+     *
+     * @param sourceFile the file to read from
+     * @param startOffset the start offset to read from the file.
+     * @param length the number of bytes to read of the file.
+     * @throws IOException
+     * @throws FileNotFoundException
+     */
+    public static String readStringFromFile(File sourceFile, long startOffset, long length)
+            throws IOException {
+        try (FileInputStream is = new FileInputStream(sourceFile)) {
+            if (startOffset < 0) {
+                startOffset = 0;
+            }
+            long fileLength = sourceFile.length();
+            is.skip(startOffset);
+            if (length <= 0 || fileLength <= startOffset + length) {
+                return StreamUtil.getStringFromStream(is);
+            }
+            return StreamUtil.getStringFromStream(is, length);
         }
     }
 
diff --git a/src/com/android/tradefed/util/StreamUtil.java b/src/com/android/tradefed/util/StreamUtil.java
index 2f1d009..5cab3b2 100644
--- a/src/com/android/tradefed/util/StreamUtil.java
+++ b/src/com/android/tradefed/util/StreamUtil.java
@@ -111,11 +111,28 @@
      * @throws IOException if failure occurred reading the stream
      */
     public static String getStringFromStream(InputStream stream) throws IOException {
+        return getStringFromStream(stream, 0);
+    }
+
+    /**
+     * Retrieves a {@link String} from a character stream.
+     *
+     * @param stream the {@link InputStream}
+     * @param length the size of the content to read, set to 0 to read all contents
+     * @return a {@link String} containing the stream contents
+     * @throws IOException if failure occurred reading the stream
+     */
+    public static String getStringFromStream(InputStream stream, long length) throws IOException {
         int irChar = -1;
         StringBuilder builder = new StringBuilder();
         try (Reader ir = new BufferedReader(new InputStreamReader(stream))) {
+            long count = 0;
             while ((irChar = ir.read()) != -1) {
                 builder.append((char) irChar);
+                count++;
+                if (length > 0 && count >= length) {
+                    break;
+                }
             }
         }
         return builder.toString();
diff --git a/tests/src/com/android/tradefed/util/FileUtilTest.java b/tests/src/com/android/tradefed/util/FileUtilTest.java
index e0f8ff6..687e783 100644
--- a/tests/src/com/android/tradefed/util/FileUtilTest.java
+++ b/tests/src/com/android/tradefed/util/FileUtilTest.java
@@ -535,4 +535,28 @@
             FileUtil.recursiveDelete(tmpDir);
         }
     }
+
+    @Test
+    public void testReadPartialStringFromFile() throws IOException {
+        File tmpFile = FileUtil.createTempFile("test", ".txt");
+        try {
+            StringBuilder content = new StringBuilder();
+            StringBuilder partialContent = new StringBuilder();
+            for (int i = 0; i < 1024; i++) {
+                content.append('A');
+            }
+            for (int i = 0; i < 1024; i++) {
+                content.append('C');
+                partialContent.append('C');
+            }
+            FileUtil.writeToFile(content.toString(), tmpFile);
+            // Test to read the whole file with `length` greater than the file length.
+            assertEquals(content.toString(), FileUtil.readStringFromFile(tmpFile, -1024, 4096 * 2));
+            // Test to read the tailing part of the file.
+            assertEquals(
+                    partialContent.toString(), FileUtil.readStringFromFile(tmpFile, 1024, 4096));
+        } finally {
+            FileUtil.deleteFile(tmpFile);
+        }
+    }
 }
diff --git a/tests/src/com/android/tradefed/util/StreamUtilTest.java b/tests/src/com/android/tradefed/util/StreamUtilTest.java
index f1a238c..da1ddd2 100644
--- a/tests/src/com/android/tradefed/util/StreamUtilTest.java
+++ b/tests/src/com/android/tradefed/util/StreamUtilTest.java
@@ -108,8 +108,8 @@
     }
 
     /**
-     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream} works as
-     * expected.
+     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream(InputStream)}
+     * works as expected.
      */
     public void testGetStringFromStream() throws Exception {
         final String contents = "this is a string";
@@ -119,6 +119,17 @@
     }
 
     /**
+     * Verify that {@link com.android.tradefed.util.StreamUtil#getStringFromStream(InputStream,
+     * long)} works as expected.
+     */
+    public void testGetStringFromStream_withLength() throws Exception {
+        final String contents = "this is a string";
+        final String output =
+                StreamUtil.getStringFromStream(new ByteArrayInputStream(contents.getBytes()), 5);
+        assertEquals("this ", output);
+    }
+
+    /**
      * Verify that {@link com.android.tradefed.util.StreamUtil#calculateMd5(InputStream)} works as
      * expected.
      *