diff options
| author | Eugene Susla <eugenesusla@google.com> | 2020-09-24 16:46:12 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-09-24 16:46:12 +0000 |
| commit | c50a911daa817d5aba2383286423a80d0473c1fc (patch) | |
| tree | 0cff9841aaa081be5f16fcccabba1a5372cc5c18 /core/java | |
| parent | 098991b929581d717d332171e8b9b7ec81fd69de (diff) | |
| parent | 8fa2bdb099376894beccf0f2d4c561e1572dfeec (diff) | |
Merge "Run shell commands reliably"
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/app/IUiAutomationConnection.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/app/UiAutomation.java | 48 | ||||
| -rw-r--r-- | core/java/android/app/UiAutomationConnection.java | 27 |
3 files changed, 72 insertions, 5 deletions
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 4c9e400681ee..9eeb9f6a95bf 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -52,4 +52,6 @@ interface IUiAutomationConnection { void dropShellPermissionIdentity(); // Called from the system process. oneway void shutdown(); + void executeShellCommandWithStderr(String command, in ParcelFileDescriptor sink, + in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index ab997e9f7832..4e868fe7370b 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -25,6 +25,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; @@ -1245,7 +1246,34 @@ public final class UiAutomation { * @hide */ @TestApi - public ParcelFileDescriptor[] executeShellCommandRw(String command) { + public @NonNull ParcelFileDescriptor[] executeShellCommandRw(@NonNull String command) { + return executeShellCommandInternal(command, false /* includeStderr */); + } + + /** + * Executes a shell command. This method returns three file descriptors, + * one that points to the standard output stream (element at index 0), one that points + * to the standard input stream (element at index 1), and one points to + * standard error stream (element at index 2). The command execution is similar + * to running "adb shell <command>" from a host connected to the device. + * <p> + * <strong>Note:</strong> It is your responsibility to close the returned file + * descriptors once you are done reading/writing. + * </p> + * + * @param command The command to execute. + * @return File descriptors (out, in, err) to the standard output/input/error streams. + * + * @hide + */ + @TestApi + @SuppressLint("ArrayReturn") // For consistency with other APIs + public @NonNull ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String command) { + return executeShellCommandInternal(command, true /* includeStderr */); + } + + private ParcelFileDescriptor[] executeShellCommandInternal( + String command, boolean includeStderr) { warnIfBetterCommand(command); ParcelFileDescriptor source_read = null; @@ -1254,6 +1282,9 @@ public final class UiAutomation { ParcelFileDescriptor source_write = null; ParcelFileDescriptor sink_write = null; + ParcelFileDescriptor stderr_source_read = null; + ParcelFileDescriptor stderr_sink_read = null; + try { ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe(); source_read = pipe_read[0]; @@ -1263,8 +1294,15 @@ public final class UiAutomation { source_write = pipe_write[0]; sink_write = pipe_write[1]; + if (includeStderr) { + ParcelFileDescriptor[] stderr_read = ParcelFileDescriptor.createPipe(); + stderr_source_read = stderr_read[0]; + stderr_sink_read = stderr_read[1]; + } + // Calling out without a lock held. - mUiAutomationConnection.executeShellCommand(command, sink_read, source_write); + mUiAutomationConnection.executeShellCommandWithStderr( + command, sink_read, source_write, stderr_sink_read); } catch (IOException ioe) { Log.e(LOG_TAG, "Error executing shell command!", ioe); } catch (RemoteException re) { @@ -1272,11 +1310,15 @@ public final class UiAutomation { } finally { IoUtils.closeQuietly(sink_read); IoUtils.closeQuietly(source_write); + IoUtils.closeQuietly(stderr_sink_read); } - ParcelFileDescriptor[] result = new ParcelFileDescriptor[2]; + ParcelFileDescriptor[] result = new ParcelFileDescriptor[includeStderr ? 3 : 2]; result[0] = source_read; result[1] = sink_write; + if (includeStderr) { + result[2] = stderr_source_read; + } return result; } diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 70d520176ca1..255b93f79811 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -372,6 +372,13 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void executeShellCommand(final String command, final ParcelFileDescriptor sink, final ParcelFileDescriptor source) throws RemoteException { + executeShellCommandWithStderr(command, sink, source, null /* stderrSink */); + } + + @Override + public void executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink, + final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink) + throws RemoteException { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); @@ -409,6 +416,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { writeToProcess = null; } + // Read from process stderr and write to pipe + final Thread readStderrFromProcess; + if (stderrSink != null) { + InputStream sink_in = process.getErrorStream(); + OutputStream sink_out = new FileOutputStream(stderrSink.getFileDescriptor()); + + readStderrFromProcess = new Thread(new Repeater(sink_in, sink_out)); + readStderrFromProcess.start(); + } else { + readStderrFromProcess = null; + } + Thread cleanup = new Thread(new Runnable() { @Override public void run() { @@ -419,14 +438,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { if (readFromProcess != null) { readFromProcess.join(); } + if (readStderrFromProcess != null) { + readStderrFromProcess.join(); + } } catch (InterruptedException exc) { Log.e(TAG, "At least one of the threads was interrupted"); } IoUtils.closeQuietly(sink); IoUtils.closeQuietly(source); + IoUtils.closeQuietly(stderrSink); process.destroy(); - } - }); + } + }); cleanup.start(); } |
