--- /dev/null
+/* //device/tools/ddms/src/com/android/ddms/DeviceCommandDialog.java
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.ddms;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.TimeoutException;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * Execute a command on an ADB-attached device and save the output.
+ *
+ * There are several ways to do this. One is to run a single command
+ * and show the output. Another is to have several possible commands and
+ * let the user click a button next to the one (or ones) they want. This
+ * currently uses the simple 1:1 form.
+ */
+public class DeviceCommandDialog extends Dialog {
+
+ public static final int DEVICE_STATE = 0;
+ public static final int APP_STATE = 1;
+ public static final int RADIO_STATE = 2;
+ public static final int LOGCAT = 3;
+
+ private String mCommand;
+ private String mFileName;
+
+ private Label mStatusLabel;
+ private Button mCancelDone;
+ private Button mSave;
+ private Text mText;
+ private Font mFont = null;
+ private boolean mCancel;
+ private boolean mFinished;
+
+
+ /**
+ * Create with default style.
+ */
+ public DeviceCommandDialog(String command, String fileName, Shell parent) {
+ // don't want a close button, but it seems hard to get rid of on GTK
+ // keep it on all platforms for consistency
+ this(command, fileName, parent,
+ SWT.DIALOG_TRIM | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE);
+ }
+
+ /**
+ * Create with app-defined style.
+ */
+ public DeviceCommandDialog(String command, String fileName, Shell parent,
+ int style)
+ {
+ super(parent, style);
+ mCommand = command;
+ mFileName = fileName;
+ }
+
+ /**
+ * Prepare and display the dialog.
+ * @param currentDevice
+ */
+ public void open(IDevice currentDevice) {
+ Shell parent = getParent();
+ Shell shell = new Shell(parent, getStyle());
+ shell.setText("Remote Command");
+
+ mFinished = false;
+ mFont = findFont(shell.getDisplay());
+ createContents(shell);
+
+ // Getting weird layout behavior under Linux when Text is added --
+ // looks like text widget has min width of 400 when FILL_HORIZONTAL
+ // is used, and layout gets tweaked to force this. (Might be even
+ // more with the scroll bars in place -- it wigged out when the
+ // file save dialog was invoked.)
+ shell.setMinimumSize(500, 200);
+ shell.setSize(800, 600);
+ shell.open();
+
+ executeCommand(shell, currentDevice);
+
+ Display display = parent.getDisplay();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+
+ if (mFont != null)
+ mFont.dispose();
+ }
+
+ /*
+ * Create a text widget to show the output and some buttons to
+ * manage things.
+ */
+ private void createContents(final Shell shell) {
+ GridData data;
+
+ shell.setLayout(new GridLayout(2, true));
+
+ shell.addListener(SWT.Close, new Listener() {
+ public void handleEvent(Event event) {
+ if (!mFinished) {
+ Log.d("ddms", "NOT closing - cancelling command");
+ event.doit = false;
+ mCancel = true;
+ }
+ }
+ });
+
+ mStatusLabel = new Label(shell, SWT.NONE);
+ mStatusLabel.setText("Executing '" + shortCommandString() + "'");
+ data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+ data.horizontalSpan = 2;
+ mStatusLabel.setLayoutData(data);
+
+ mText = new Text(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
+ mText.setEditable(false);
+ mText.setFont(mFont);
+ data = new GridData(GridData.FILL_BOTH);
+ data.horizontalSpan = 2;
+ mText.setLayoutData(data);
+
+ // "save" button
+ mSave = new Button(shell, SWT.PUSH);
+ mSave.setText("Save");
+ data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
+ data.widthHint = 80;
+ mSave.setLayoutData(data);
+ mSave.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ saveText(shell);
+ }
+ });
+ mSave.setEnabled(false);
+
+ // "cancel/done" button
+ mCancelDone = new Button(shell, SWT.PUSH);
+ mCancelDone.setText("Cancel");
+ data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
+ data.widthHint = 80;
+ mCancelDone.setLayoutData(data);
+ mCancelDone.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (!mFinished)
+ mCancel = true;
+ else
+ shell.close();
+ }
+ });
+ }
+
+ /*
+ * Figure out what font to use.
+ *
+ * Returns "null" if we can't figure it out, which SWT understands to
+ * mean "use default system font".
+ */
+ private Font findFont(Display display) {
+ String fontStr = PrefsDialog.getStore().getString("textOutputFont");
+ if (fontStr != null) {
+ FontData fdat = new FontData(fontStr);
+ if (fdat != null)
+ return new Font(display, fdat);
+ }
+ return null;
+ }
+
+
+ /*
+ * Callback class for command execution.
+ */
+ class Gatherer extends Thread implements IShellOutputReceiver {
+ public static final int RESULT_UNKNOWN = 0;
+ public static final int RESULT_SUCCESS = 1;
+ public static final int RESULT_FAILURE = 2;
+ public static final int RESULT_CANCELLED = 3;
+
+ private Shell mShell;
+ private String mCommand;
+ private Text mText;
+ private int mResult;
+ private IDevice mDevice;
+
+ /**
+ * Constructor; pass in the text widget that will receive the output.
+ * @param device
+ */
+ public Gatherer(Shell shell, IDevice device, String command, Text text) {
+ mShell = shell;
+ mDevice = device;
+ mCommand = command;
+ mText = text;
+ mResult = RESULT_UNKNOWN;
+
+ // this is in outer class
+ mCancel = false;
+ }
+
+ /**
+ * Thread entry point.
+ */
+ @Override
+ public void run() {
+
+ if (mDevice == null) {
+ Log.w("ddms", "Cannot execute command: no device selected.");
+ mResult = RESULT_FAILURE;
+ } else {
+ try {
+ mDevice.executeShellCommand(mCommand, this);
+ if (mCancel)
+ mResult = RESULT_CANCELLED;
+ else
+ mResult = RESULT_SUCCESS;
+ }
+ catch (IOException ioe) {
+ Log.w("ddms", "Remote exec failed: " + ioe.getMessage());
+ mResult = RESULT_FAILURE;
+ } catch (TimeoutException e) {
+ Log.w("ddms", "Remote exec failed: " + e.getMessage());
+ mResult = RESULT_FAILURE;
+ } catch (AdbCommandRejectedException e) {
+ Log.w("ddms", "Remote exec failed: " + e.getMessage());
+ mResult = RESULT_FAILURE;
+ } catch (ShellCommandUnresponsiveException e) {
+ Log.w("ddms", "Remote exec failed: " + e.getMessage());
+ mResult = RESULT_FAILURE;
+ }
+ }
+
+ mShell.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ updateForResult(mResult);
+ }
+ });
+ }
+
+ /**
+ * Called by executeRemoteCommand().
+ */
+ public void addOutput(byte[] data, int offset, int length) {
+
+ Log.v("ddms", "received " + length + " bytes");
+ try {
+ final String text;
+ text = new String(data, offset, length, "ISO-8859-1");
+
+ // add to text widget; must do in UI thread
+ mText.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ mText.append(text);
+ }
+ });
+ }
+ catch (UnsupportedEncodingException uee) {
+ uee.printStackTrace(); // not expected
+ }
+ }
+
+ public void flush() {
+ // nothing to flush.
+ }
+
+ /**
+ * Called by executeRemoteCommand().
+ */
+ public boolean isCancelled() {
+ return mCancel;
+ }
+ };
+
+ /*
+ * Execute a remote command, add the output to the text widget, and
+ * update controls.
+ *
+ * We have to run the command in a thread so that the UI continues
+ * to work.
+ */
+ private void executeCommand(Shell shell, IDevice device) {
+ Gatherer gath = new Gatherer(shell, device, commandString(), mText);
+ gath.start();
+ }
+
+ /*
+ * Update the controls after the remote operation completes. This
+ * must be called from the UI thread.
+ */
+ private void updateForResult(int result) {
+ if (result == Gatherer.RESULT_SUCCESS) {
+ mStatusLabel.setText("Successfully executed '"
+ + shortCommandString() + "'");
+ mSave.setEnabled(true);
+ } else if (result == Gatherer.RESULT_CANCELLED) {
+ mStatusLabel.setText("Execution cancelled; partial results below");
+ mSave.setEnabled(true); // save partial
+ } else if (result == Gatherer.RESULT_FAILURE) {
+ mStatusLabel.setText("Failed");
+ }
+ mStatusLabel.pack();
+ mCancelDone.setText("Done");
+ mFinished = true;
+ }
+
+ /*
+ * Allow the user to save the contents of the text dialog.
+ */
+ private void saveText(Shell shell) {
+ FileDialog dlg = new FileDialog(shell, SWT.SAVE);
+ String fileName;
+
+ dlg.setText("Save output...");
+ dlg.setFileName(defaultFileName());
+ dlg.setFilterPath(PrefsDialog.getStore().getString("lastTextSaveDir"));
+ dlg.setFilterNames(new String[] {
+ "Text Files (*.txt)"
+ });
+ dlg.setFilterExtensions(new String[] {
+ "*.txt"
+ });
+
+ fileName = dlg.open();
+ if (fileName != null) {
+ PrefsDialog.getStore().setValue("lastTextSaveDir",
+ dlg.getFilterPath());
+
+ Log.d("ddms", "Saving output to " + fileName);
+
+ /*
+ * Convert to 8-bit characters.
+ */
+ String text = mText.getText();
+ byte[] ascii;
+ try {
+ ascii = text.getBytes("ISO-8859-1");
+ }
+ catch (UnsupportedEncodingException uee) {
+ uee.printStackTrace();
+ ascii = new byte[0];
+ }
+
+ /*
+ * Output data, converting CRLF to LF.
+ */
+ try {
+ int length = ascii.length;
+
+ FileOutputStream outFile = new FileOutputStream(fileName);
+ BufferedOutputStream out = new BufferedOutputStream(outFile);
+ for (int i = 0; i < length; i++) {
+ if (i < length-1 &&
+ ascii[i] == 0x0d && ascii[i+1] == 0x0a)
+ {
+ continue;
+ }
+ out.write(ascii[i]);
+ }
+ out.close(); // flush buffer, close file
+ }
+ catch (IOException ioe) {
+ Log.w("ddms", "Unable to save " + fileName + ": " + ioe);
+ }
+ }
+ }
+
+
+ /*
+ * Return the shell command we're going to use.
+ */
+ private String commandString() {
+ return mCommand;
+
+ }
+
+ /*
+ * Return a default filename for the "save" command.
+ */
+ private String defaultFileName() {
+ return mFileName;
+ }
+
+ /*
+ * Like commandString(), but length-limited.
+ */
+ private String shortCommandString() {
+ String str = commandString();
+ if (str.length() > 50)
+ return str.substring(0, 50) + "...";
+ else
+ return str;
+ }
+}
+