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