Implement the handler push file (write) method

Not currently hooked up in the main APIs.

Bug: 123529934
Test: local runs
Bug: 121325876
Change-Id: I7c996ea156d2001c3f15b99b980bdaf2bfcf4743
Merged-In: I7c996ea156d2001c3f15b99b980bdaf2bfcf4743
diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java
index ff4071b..cea6d8d 100644
--- a/src/com/android/tradefed/device/INativeDevice.java
+++ b/src/com/android/tradefed/device/INativeDevice.java
@@ -276,6 +276,19 @@
     public CommandResult executeShellV2Command(String command) throws DeviceNotAvailableException;
 
     /**
+     * Helper method which executes an adb shell command and returns the results as a {@link
+     * CommandResult} properly populated with the command status output, stdout and stderr.
+     *
+     * @param command The command that should be run.
+     * @param pipeAsInput A {@link File} that will be piped as input to the command.
+     * @return The result in {@link CommandResult}.
+     * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+     *     recovered.
+     */
+    public CommandResult executeShellV2Command(String command, File pipeAsInput)
+            throws DeviceNotAvailableException;
+
+    /**
      * Executes a adb shell command, with more parameters to control command behavior.
      *
      * @see #executeShellV2Command(String)
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index cfbf3a1..12c3a6a 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -258,15 +258,21 @@
 
         private String[] mCmd;
         private long mTimeout;
+        private File mPipeAsInput;
 
-        AdbShellAction(String[] cmd, long timeout) {
+        AdbShellAction(String[] cmd, File pipeAsInput, long timeout) {
             mCmd = cmd;
+            mPipeAsInput = pipeAsInput;
             mTimeout = timeout;
         }
 
         @Override
         public boolean run() throws TimeoutException, IOException {
-            mResult = getRunUtil().runTimedCmd(mTimeout, mCmd);
+            if (mPipeAsInput != null) {
+                mResult = getRunUtil().runTimedCmdWithInputRedirect(mTimeout, mPipeAsInput, mCmd);
+            } else {
+                mResult = getRunUtil().runTimedCmd(mTimeout, mCmd);
+            }
             if (mResult.getStatus() == CommandStatus.EXCEPTION) {
                 throw new IOException(mResult.getStderr());
             } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) {
@@ -685,10 +691,18 @@
 
     /** {@inheritDoc} */
     @Override
+    public CommandResult executeShellV2Command(String cmd, File pipeAsInput)
+            throws DeviceNotAvailableException {
+        return executeShellV2Command(
+                cmd, pipeAsInput, getCommandTimeout(), TimeUnit.MILLISECONDS, MAX_RETRY_ATTEMPTS);
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public CommandResult executeShellV2Command(
             String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)
             throws DeviceNotAvailableException {
-        return executeShellV2Command(cmd, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
+        return executeShellV2Command(cmd, null, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
     }
 
     /** {@inheritDoc} */
@@ -696,9 +710,19 @@
     public CommandResult executeShellV2Command(
             String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)
             throws DeviceNotAvailableException {
+        return executeShellV2Command(cmd, null, maxTimeoutForCommand, timeUnit, retryAttempts);
+    }
+
+    private CommandResult executeShellV2Command(
+            String cmd,
+            File pipeAsInput,
+            final long maxTimeoutForCommand,
+            final TimeUnit timeUnit,
+            int retryAttempts)
+            throws DeviceNotAvailableException {
         final String[] fullCmd = buildAdbShellCommand(cmd);
         AdbShellAction adbActionV2 =
-                new AdbShellAction(fullCmd, timeUnit.toMillis(maxTimeoutForCommand));
+                new AdbShellAction(fullCmd, pipeAsInput, timeUnit.toMillis(maxTimeoutForCommand));
         performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts);
         return adbActionV2.mResult;
     }
diff --git a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index bf8978f..a245dd8 100644
--- a/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/src/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -17,12 +17,13 @@
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.WifiHelper;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
 
+import com.google.common.base.Strings;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -74,21 +75,21 @@
     }
 
     /** Clean the device from the content provider helper. */
-    public void tearDown() throws Exception {
+    public void tearDown() throws DeviceNotAvailableException {
         FileUtil.deleteFile(mContentProviderApk);
         mDevice.uninstallPackage(PACKAGE_NAME);
     }
 
     /**
      * Content provider callback that delete a file at the URI location. File will be deleted from
-     * the disk.
+     * the device content.
      *
      * @param deviceFilePath The path on the device of the file to delete.
      * @return True if successful, False otherwise
      * @throws DeviceNotAvailableException
      */
     public boolean deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
-        String contentUri = String.format("%s/%s", CONTENT_PROVIDER_URI, deviceFilePath);
+        String contentUri = createContentUri(deviceFilePath);
         String deleteCommand =
                 String.format(
                         "content delete --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
@@ -103,11 +104,55 @@
         return false;
     }
 
+    /**
+     * Content provider callback that push a file to the URI location.
+     *
+     * @param fileToPush The {@link File} to be pushed to the device.
+     * @param deviceFilePath The path on the device where to push the file.
+     * @return True if successful, False otherwise
+     * @throws DeviceNotAvailableException
+     * @throws IllegalArgumentException
+     */
+    public boolean pushFile(File fileToPush, String deviceFilePath)
+            throws DeviceNotAvailableException, IllegalArgumentException {
+        if (fileToPush.isDirectory()) {
+            throw new IllegalArgumentException(
+                    String.format("File '%s' to push is a directory.", fileToPush));
+        }
+        if (!fileToPush.exists()) {
+            throw new IllegalArgumentException(
+                    String.format("File '%s' to push does not exist.", fileToPush));
+        }
+        String contentUri = createContentUri(deviceFilePath);
+        String pushCommand =
+                String.format(
+                        "content write --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
+        CommandResult pushResult = mDevice.executeShellV2Command(pushCommand, fileToPush);
+
+        String stderr = pushResult.getStderr();
+        if (CommandStatus.SUCCESS.equals(pushResult.getStatus())) {
+            // Command above returns success even if it prints stack failure.
+            if (Strings.isNullOrEmpty(stderr)) {
+                return true;
+            }
+        }
+        CLog.e(
+                "Failed to push a file '%s' at %s using content provider. Error: '%s'",
+                fileToPush, deviceFilePath, stderr);
+        return false;
+    }
+
     /** Helper method to extract the content provider apk. */
     private File extractResourceApk() throws IOException {
         File apkTempFile = FileUtil.createTempFile(APK_NAME, ".apk");
-        InputStream apkStream = WifiHelper.class.getResourceAsStream(CONTENT_PROVIDER_APK_RES);
+        InputStream apkStream =
+                ContentProviderHandler.class.getResourceAsStream(CONTENT_PROVIDER_APK_RES);
         FileUtil.writeToFile(apkStream, apkTempFile);
         return apkTempFile;
     }
+
+    /** Returns the full URI string for the given device path. */
+    private String createContentUri(String deviceFilePath) {
+        return String.format("%s/%s", CONTENT_PROVIDER_URI, deviceFilePath);
+    }
 }
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index 2183140..2c1395f 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -24,6 +24,7 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -32,6 +33,7 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Mockito;
 
+import java.io.File;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -116,4 +118,28 @@
                                         + devicePath));
         assertFalse(mProvider.deleteFile(devicePath));
     }
+
+    /** Test {@link ContentProviderHandler#pushFile(File, String)}. */
+    @Test
+    public void testPushFile() throws Exception {
+        File toPush = FileUtil.createTempFile("content-provider-test", ".txt");
+        try {
+            String devicePath = "path/somewhere/file.txt";
+            CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+            result.setStderr("");
+            doReturn(99).when(mMockDevice).getCurrentUser();
+            doReturn(result)
+                    .when(mMockDevice)
+                    .executeShellV2Command(
+                            eq(
+                                    "content write --user 99 --uri "
+                                            + ContentProviderHandler.CONTENT_PROVIDER_URI
+                                            + "/"
+                                            + devicePath),
+                            eq(toPush));
+            assertTrue(mProvider.pushFile(toPush, devicePath));
+        } finally {
+            FileUtil.deleteFile(toPush);
+        }
+    }
 }