Home | History | Annotate | Download | only in ddms
      1 /* //device/tools/ddms/src/com/android/ddms/DeviceCommandDialog.java
      2 **
      3 ** Copyright 2007, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 package com.android.ddms;
     19 
     20 import com.android.ddmlib.IDevice;
     21 import com.android.ddmlib.IShellOutputReceiver;
     22 import com.android.ddmlib.Log;
     23 
     24 import org.eclipse.swt.SWT;
     25 import org.eclipse.swt.events.SelectionAdapter;
     26 import org.eclipse.swt.events.SelectionEvent;
     27 import org.eclipse.swt.graphics.Font;
     28 import org.eclipse.swt.graphics.FontData;
     29 import org.eclipse.swt.layout.GridData;
     30 import org.eclipse.swt.layout.GridLayout;
     31 import org.eclipse.swt.widgets.Button;
     32 import org.eclipse.swt.widgets.Dialog;
     33 import org.eclipse.swt.widgets.Display;
     34 import org.eclipse.swt.widgets.Event;
     35 import org.eclipse.swt.widgets.FileDialog;
     36 import org.eclipse.swt.widgets.Label;
     37 import org.eclipse.swt.widgets.Listener;
     38 import org.eclipse.swt.widgets.Shell;
     39 import org.eclipse.swt.widgets.Text;
     40 
     41 import java.io.BufferedOutputStream;
     42 import java.io.FileOutputStream;
     43 import java.io.IOException;
     44 import java.io.UnsupportedEncodingException;
     45 
     46 
     47 /**
     48  * Execute a command on an ADB-attached device and save the output.
     49  *
     50  * There are several ways to do this.  One is to run a single command
     51  * and show the output.  Another is to have several possible commands and
     52  * let the user click a button next to the one (or ones) they want.  This
     53  * currently uses the simple 1:1 form.
     54  */
     55 public class DeviceCommandDialog extends Dialog {
     56 
     57     public static final int DEVICE_STATE = 0;
     58     public static final int APP_STATE = 1;
     59     public static final int RADIO_STATE = 2;
     60     public static final int LOGCAT = 3;
     61 
     62     private String mCommand;
     63     private String mFileName;
     64 
     65     private Label mStatusLabel;
     66     private Button mCancelDone;
     67     private Button mSave;
     68     private Text mText;
     69     private Font mFont = null;
     70     private boolean mCancel;
     71     private boolean mFinished;
     72 
     73 
     74     /**
     75      * Create with default style.
     76      */
     77     public DeviceCommandDialog(String command, String fileName, Shell parent) {
     78         // don't want a close button, but it seems hard to get rid of on GTK
     79         // keep it on all platforms for consistency
     80         this(command, fileName, parent,
     81             SWT.DIALOG_TRIM | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE);
     82     }
     83 
     84     /**
     85      * Create with app-defined style.
     86      */
     87     public DeviceCommandDialog(String command, String fileName, Shell parent,
     88         int style)
     89     {
     90         super(parent, style);
     91         mCommand = command;
     92         mFileName = fileName;
     93     }
     94 
     95     /**
     96      * Prepare and display the dialog.
     97      * @param currentDevice
     98      */
     99     public void open(IDevice currentDevice) {
    100         Shell parent = getParent();
    101         Shell shell = new Shell(parent, getStyle());
    102         shell.setText("Remote Command");
    103 
    104         mFinished = false;
    105         mFont = findFont(shell.getDisplay());
    106         createContents(shell);
    107 
    108         // Getting weird layout behavior under Linux when Text is added --
    109         // looks like text widget has min width of 400 when FILL_HORIZONTAL
    110         // is used, and layout gets tweaked to force this.  (Might be even
    111         // more with the scroll bars in place -- it wigged out when the
    112         // file save dialog was invoked.)
    113         shell.setMinimumSize(500, 200);
    114         shell.setSize(800, 600);
    115         shell.open();
    116 
    117         executeCommand(shell, currentDevice);
    118 
    119         Display display = parent.getDisplay();
    120         while (!shell.isDisposed()) {
    121             if (!display.readAndDispatch())
    122                 display.sleep();
    123         }
    124 
    125         if (mFont != null)
    126             mFont.dispose();
    127     }
    128 
    129     /*
    130      * Create a text widget to show the output and some buttons to
    131      * manage things.
    132      */
    133     private void createContents(final Shell shell) {
    134         GridData data;
    135 
    136         shell.setLayout(new GridLayout(2, true));
    137 
    138         shell.addListener(SWT.Close, new Listener() {
    139             public void handleEvent(Event event) {
    140                 if (!mFinished) {
    141                     Log.d("ddms", "NOT closing - cancelling command");
    142                     event.doit = false;
    143                     mCancel = true;
    144                 }
    145             }
    146         });
    147 
    148         mStatusLabel = new Label(shell, SWT.NONE);
    149         mStatusLabel.setText("Executing '" + shortCommandString() + "'");
    150         data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
    151         data.horizontalSpan = 2;
    152         mStatusLabel.setLayoutData(data);
    153 
    154         mText = new Text(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
    155         mText.setEditable(false);
    156         mText.setFont(mFont);
    157         data = new GridData(GridData.FILL_BOTH);
    158         data.horizontalSpan = 2;
    159         mText.setLayoutData(data);
    160 
    161         // "save" button
    162         mSave = new Button(shell, SWT.PUSH);
    163         mSave.setText("Save");
    164         data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
    165         data.widthHint = 80;
    166         mSave.setLayoutData(data);
    167         mSave.addSelectionListener(new SelectionAdapter() {
    168             @Override
    169             public void widgetSelected(SelectionEvent e) {
    170                 saveText(shell);
    171             }
    172         });
    173         mSave.setEnabled(false);
    174 
    175         // "cancel/done" button
    176         mCancelDone = new Button(shell, SWT.PUSH);
    177         mCancelDone.setText("Cancel");
    178         data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
    179         data.widthHint = 80;
    180         mCancelDone.setLayoutData(data);
    181         mCancelDone.addSelectionListener(new SelectionAdapter() {
    182             @Override
    183             public void widgetSelected(SelectionEvent e) {
    184                 if (!mFinished)
    185                     mCancel = true;
    186                 else
    187                     shell.close();
    188             }
    189         });
    190     }
    191 
    192     /*
    193      * Figure out what font to use.
    194      *
    195      * Returns "null" if we can't figure it out, which SWT understands to
    196      * mean "use default system font".
    197      */
    198     private Font findFont(Display display) {
    199         String fontStr = PrefsDialog.getStore().getString("textOutputFont");
    200         if (fontStr != null) {
    201             FontData fdat = new FontData(fontStr);
    202             if (fdat != null)
    203                 return new Font(display, fdat);
    204         }
    205         return null;
    206     }
    207 
    208 
    209     /*
    210      * Callback class for command execution.
    211      */
    212     class Gatherer extends Thread implements IShellOutputReceiver {
    213         public static final int RESULT_UNKNOWN = 0;
    214         public static final int RESULT_SUCCESS = 1;
    215         public static final int RESULT_FAILURE = 2;
    216         public static final int RESULT_CANCELLED = 3;
    217 
    218         private Shell mShell;
    219         private String mCommand;
    220         private Text mText;
    221         private int mResult;
    222         private IDevice mDevice;
    223 
    224         /**
    225          * Constructor; pass in the text widget that will receive the output.
    226          * @param device
    227          */
    228         public Gatherer(Shell shell, IDevice device, String command, Text text) {
    229             mShell = shell;
    230             mDevice = device;
    231             mCommand = command;
    232             mText = text;
    233             mResult = RESULT_UNKNOWN;
    234 
    235             // this is in outer class
    236             mCancel = false;
    237         }
    238 
    239         /**
    240          * Thread entry point.
    241          */
    242         @Override
    243         public void run() {
    244 
    245             if (mDevice == null) {
    246                 Log.w("ddms", "Cannot execute command: no device selected.");
    247                 mResult = RESULT_FAILURE;
    248             } else {
    249                 try {
    250                     mDevice.executeShellCommand(mCommand, this);
    251                     if (mCancel)
    252                         mResult = RESULT_CANCELLED;
    253                     else
    254                         mResult = RESULT_SUCCESS;
    255                 }
    256                 catch (IOException ioe) {
    257                     Log.w("ddms", "Remote exec failed: " + ioe.getMessage());
    258                     mResult = RESULT_FAILURE;
    259                 }
    260             }
    261 
    262             mShell.getDisplay().asyncExec(new Runnable() {
    263                 public void run() {
    264                     updateForResult(mResult);
    265                 }
    266             });
    267         }
    268 
    269         /**
    270          * Called by executeRemoteCommand().
    271          */
    272         public void addOutput(byte[] data, int offset, int length) {
    273 
    274             Log.v("ddms", "received " + length + " bytes");
    275             try {
    276                 final String text;
    277                 text = new String(data, offset, length, "ISO-8859-1");
    278 
    279                 // add to text widget; must do in UI thread
    280                 mText.getDisplay().asyncExec(new Runnable() {
    281                     public void run() {
    282                         mText.append(text);
    283                     }
    284                 });
    285             }
    286             catch (UnsupportedEncodingException uee) {
    287                 uee.printStackTrace();      // not expected
    288             }
    289         }
    290 
    291         public void flush() {
    292             // nothing to flush.
    293         }
    294 
    295         /**
    296          * Called by executeRemoteCommand().
    297          */
    298         public boolean isCancelled() {
    299             return mCancel;
    300         }
    301     };
    302 
    303     /*
    304      * Execute a remote command, add the output to the text widget, and
    305      * update controls.
    306      *
    307      * We have to run the command in a thread so that the UI continues
    308      * to work.
    309      */
    310     private void executeCommand(Shell shell, IDevice device) {
    311         Gatherer gath = new Gatherer(shell, device, commandString(), mText);
    312         gath.start();
    313     }
    314 
    315     /*
    316      * Update the controls after the remote operation completes.  This
    317      * must be called from the UI thread.
    318      */
    319     private void updateForResult(int result) {
    320         if (result == Gatherer.RESULT_SUCCESS) {
    321             mStatusLabel.setText("Successfully executed '"
    322                 + shortCommandString() + "'");
    323             mSave.setEnabled(true);
    324         } else if (result == Gatherer.RESULT_CANCELLED) {
    325             mStatusLabel.setText("Execution cancelled; partial results below");
    326             mSave.setEnabled(true);     // save partial
    327         } else if (result == Gatherer.RESULT_FAILURE) {
    328             mStatusLabel.setText("Failed");
    329         }
    330         mStatusLabel.pack();
    331         mCancelDone.setText("Done");
    332         mFinished = true;
    333     }
    334 
    335     /*
    336      * Allow the user to save the contents of the text dialog.
    337      */
    338     private void saveText(Shell shell) {
    339         FileDialog dlg = new FileDialog(shell, SWT.SAVE);
    340         String fileName;
    341 
    342         dlg.setText("Save output...");
    343         dlg.setFileName(defaultFileName());
    344         dlg.setFilterPath(PrefsDialog.getStore().getString("lastTextSaveDir"));
    345         dlg.setFilterNames(new String[] {
    346             "Text Files (*.txt)"
    347         });
    348         dlg.setFilterExtensions(new String[] {
    349             "*.txt"
    350         });
    351 
    352         fileName = dlg.open();
    353         if (fileName != null) {
    354             PrefsDialog.getStore().setValue("lastTextSaveDir",
    355                                             dlg.getFilterPath());
    356 
    357             Log.d("ddms", "Saving output to " + fileName);
    358 
    359             /*
    360              * Convert to 8-bit characters.
    361              */
    362             String text = mText.getText();
    363             byte[] ascii;
    364             try {
    365                 ascii = text.getBytes("ISO-8859-1");
    366             }
    367             catch (UnsupportedEncodingException uee) {
    368                 uee.printStackTrace();
    369                 ascii = new byte[0];
    370             }
    371 
    372             /*
    373              * Output data, converting CRLF to LF.
    374              */
    375             try {
    376                 int length = ascii.length;
    377 
    378                 FileOutputStream outFile = new FileOutputStream(fileName);
    379                 BufferedOutputStream out = new BufferedOutputStream(outFile);
    380                 for (int i = 0; i < length; i++) {
    381                     if (i < length-1 &&
    382                         ascii[i] == 0x0d && ascii[i+1] == 0x0a)
    383                     {
    384                         continue;
    385                     }
    386                     out.write(ascii[i]);
    387                 }
    388                 out.close();        // flush buffer, close file
    389             }
    390             catch (IOException ioe) {
    391                 Log.w("ddms", "Unable to save " + fileName + ": " + ioe);
    392             }
    393         }
    394     }
    395 
    396 
    397     /*
    398      * Return the shell command we're going to use.
    399      */
    400     private String commandString() {
    401         return mCommand;
    402 
    403     }
    404 
    405     /*
    406      * Return a default filename for the "save" command.
    407      */
    408     private String defaultFileName() {
    409         return mFileName;
    410     }
    411 
    412     /*
    413      * Like commandString(), but length-limited.
    414      */
    415     private String shortCommandString() {
    416         String str = commandString();
    417         if (str.length() > 50)
    418             return str.substring(0, 50) + "...";
    419         else
    420             return str;
    421     }
    422 }
    423 
    424