/* //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.Device; import com.android.ddmlib.IShellOutputReceiver; import com.android.ddmlib.Log; 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(Device 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.i("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 Device mDevice; /** * Constructor; pass in the text widget that will receive the output. * @param device */ public Gatherer(Shell shell, Device 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; } } 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, Device 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.i("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; } }