• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ddmuilib.explorer;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.DdmConstants;
21 import com.android.ddmlib.FileListingService;
22 import com.android.ddmlib.FileListingService.FileEntry;
23 import com.android.ddmlib.IDevice;
24 import com.android.ddmlib.IShellOutputReceiver;
25 import com.android.ddmlib.ShellCommandUnresponsiveException;
26 import com.android.ddmlib.SyncException;
27 import com.android.ddmlib.SyncService;
28 import com.android.ddmlib.SyncService.ISyncProgressMonitor;
29 import com.android.ddmlib.TimeoutException;
30 import com.android.ddmuilib.DdmUiPreferences;
31 import com.android.ddmuilib.ImageLoader;
32 import com.android.ddmuilib.Panel;
33 import com.android.ddmuilib.SyncProgressHelper;
34 import com.android.ddmuilib.SyncProgressHelper.SyncRunnable;
35 import com.android.ddmuilib.TableHelper;
36 import com.android.ddmuilib.actions.ICommonAction;
37 import com.android.ddmuilib.console.DdmConsole;
38 
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.Status;
41 import org.eclipse.jface.dialogs.ErrorDialog;
42 import org.eclipse.jface.dialogs.IInputValidator;
43 import org.eclipse.jface.dialogs.InputDialog;
44 import org.eclipse.jface.preference.IPreferenceStore;
45 import org.eclipse.jface.viewers.DoubleClickEvent;
46 import org.eclipse.jface.viewers.IDoubleClickListener;
47 import org.eclipse.jface.viewers.ISelection;
48 import org.eclipse.jface.viewers.ISelectionChangedListener;
49 import org.eclipse.jface.viewers.IStructuredSelection;
50 import org.eclipse.jface.viewers.SelectionChangedEvent;
51 import org.eclipse.jface.viewers.TreeViewer;
52 import org.eclipse.jface.viewers.ViewerDropAdapter;
53 import org.eclipse.swt.SWT;
54 import org.eclipse.swt.dnd.DND;
55 import org.eclipse.swt.dnd.FileTransfer;
56 import org.eclipse.swt.dnd.Transfer;
57 import org.eclipse.swt.dnd.TransferData;
58 import org.eclipse.swt.graphics.Image;
59 import org.eclipse.swt.layout.FillLayout;
60 import org.eclipse.swt.widgets.Composite;
61 import org.eclipse.swt.widgets.Control;
62 import org.eclipse.swt.widgets.DirectoryDialog;
63 import org.eclipse.swt.widgets.Display;
64 import org.eclipse.swt.widgets.FileDialog;
65 import org.eclipse.swt.widgets.Tree;
66 import org.eclipse.swt.widgets.TreeItem;
67 
68 import java.io.BufferedReader;
69 import java.io.File;
70 import java.io.IOException;
71 import java.io.InputStreamReader;
72 import java.util.ArrayList;
73 import java.util.regex.Matcher;
74 import java.util.regex.Pattern;
75 
76 /**
77  * Device filesystem explorer class.
78  */
79 public class DeviceExplorer extends Panel {
80 
81     private final static String TRACE_KEY_EXT = ".key"; // $NON-NLS-1S
82     private final static String TRACE_DATA_EXT = ".data"; // $NON-NLS-1S
83 
84     private static Pattern mKeyFilePattern = Pattern.compile(
85             "(.+)\\" + TRACE_KEY_EXT); // $NON-NLS-1S
86     private static Pattern mDataFilePattern = Pattern.compile(
87             "(.+)\\" + TRACE_DATA_EXT); // $NON-NLS-1S
88 
89     public static String COLUMN_NAME = "android.explorer.name"; //$NON-NLS-1S
90     public static String COLUMN_SIZE = "android.explorer.size"; //$NON-NLS-1S
91     public static String COLUMN_DATE = "android.explorer.data"; //$NON-NLS-1S
92     public static String COLUMN_TIME = "android.explorer.time"; //$NON-NLS-1S
93     public static String COLUMN_PERMISSIONS = "android.explorer.permissions"; // $NON-NLS-1S
94     public static String COLUMN_INFO = "android.explorer.info"; // $NON-NLS-1S
95 
96     private Composite mParent;
97     private TreeViewer mTreeViewer;
98     private Tree mTree;
99     private DeviceContentProvider mContentProvider;
100 
101     private ICommonAction mPushAction;
102     private ICommonAction mPullAction;
103     private ICommonAction mDeleteAction;
104     private ICommonAction mCreateNewFolderAction;
105 
106     private Image mFileImage;
107     private Image mFolderImage;
108     private Image mPackageImage;
109     private Image mOtherImage;
110 
111     private IDevice mCurrentDevice;
112 
113     private String mDefaultSave;
114 
DeviceExplorer()115     public DeviceExplorer() {
116     }
117 
118     /**
119      * Sets custom images for the device explorer. If none are set then defaults are used.
120      * This can be useful to set platform-specific explorer icons.
121      *
122      * This should be called before {@link #createControl(Composite)}.
123      *
124      * @param fileImage the icon to represent a file.
125      * @param folderImage the icon to represent a folder.
126      * @param packageImage the icon to represent an apk.
127      * @param otherImage the icon to represent other types of files.
128      */
setCustomImages(Image fileImage, Image folderImage, Image packageImage, Image otherImage)129     public void setCustomImages(Image fileImage, Image folderImage, Image packageImage,
130             Image otherImage) {
131         mFileImage = fileImage;
132         mFolderImage = folderImage;
133         mPackageImage = packageImage;
134         mOtherImage = otherImage;
135     }
136 
137     /**
138      * Sets the actions so that the device explorer can enable/disable them based on the current
139      * selection
140      * @param pushAction
141      * @param pullAction
142      * @param deleteAction
143      * @param createNewFolderAction
144      */
setActions(ICommonAction pushAction, ICommonAction pullAction, ICommonAction deleteAction, ICommonAction createNewFolderAction)145     public void setActions(ICommonAction pushAction, ICommonAction pullAction,
146             ICommonAction deleteAction, ICommonAction createNewFolderAction) {
147         mPushAction = pushAction;
148         mPullAction = pullAction;
149         mDeleteAction = deleteAction;
150         mCreateNewFolderAction = createNewFolderAction;
151     }
152 
153     /**
154      * Creates a control capable of displaying some information.  This is
155      * called once, when the application is initializing, from the UI thread.
156      */
157     @Override
createControl(Composite parent)158     protected Control createControl(Composite parent) {
159         mParent = parent;
160         parent.setLayout(new FillLayout());
161 
162         ImageLoader loader = ImageLoader.getDdmUiLibLoader();
163         if (mFileImage == null) {
164             mFileImage = loader.loadImage("file.png", mParent.getDisplay());
165         }
166         if (mFolderImage == null) {
167             mFolderImage = loader.loadImage("folder.png", mParent.getDisplay());
168         }
169         if (mPackageImage == null) {
170             mPackageImage = loader.loadImage("android.png", mParent.getDisplay());
171         }
172         if (mOtherImage == null) {
173             // TODO: find a default image for other.
174         }
175 
176         mTree = new Tree(parent, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL);
177         mTree.setHeaderVisible(true);
178 
179         IPreferenceStore store = DdmUiPreferences.getStore();
180 
181         // create columns
182         TableHelper.createTreeColumn(mTree, "Name", SWT.LEFT,
183                 "0000drwxrwxrwx", COLUMN_NAME, store); //$NON-NLS-1$
184         TableHelper.createTreeColumn(mTree, "Size", SWT.RIGHT,
185                 "000000", COLUMN_SIZE, store); //$NON-NLS-1$
186         TableHelper.createTreeColumn(mTree, "Date", SWT.LEFT,
187                 "2007-08-14", COLUMN_DATE, store); //$NON-NLS-1$
188         TableHelper.createTreeColumn(mTree, "Time", SWT.LEFT,
189                 "20:54", COLUMN_TIME, store); //$NON-NLS-1$
190         TableHelper.createTreeColumn(mTree, "Permissions", SWT.LEFT,
191                 "drwxrwxrwx", COLUMN_PERMISSIONS, store); //$NON-NLS-1$
192         TableHelper.createTreeColumn(mTree, "Info", SWT.LEFT,
193                 "drwxrwxrwx", COLUMN_INFO, store); //$NON-NLS-1$
194 
195         // create the jface wrapper
196         mTreeViewer = new TreeViewer(mTree);
197 
198         // setup data provider
199         mContentProvider = new DeviceContentProvider();
200         mTreeViewer.setContentProvider(mContentProvider);
201         mTreeViewer.setLabelProvider(new FileLabelProvider(mFileImage,
202                 mFolderImage, mPackageImage, mOtherImage));
203 
204         // setup a listener for selection
205         mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
206             @Override
207             public void selectionChanged(SelectionChangedEvent event) {
208                 ISelection sel = event.getSelection();
209                 if (sel.isEmpty()) {
210                     mPullAction.setEnabled(false);
211                     mPushAction.setEnabled(false);
212                     mDeleteAction.setEnabled(false);
213                     mCreateNewFolderAction.setEnabled(false);
214                     return;
215                 }
216                 if (sel instanceof IStructuredSelection) {
217                     IStructuredSelection selection = (IStructuredSelection) sel;
218                     Object element = selection.getFirstElement();
219                     if (element == null)
220                         return;
221                     if (element instanceof FileEntry) {
222                         mPullAction.setEnabled(true);
223                         mPushAction.setEnabled(selection.size() == 1);
224                         if (selection.size() == 1) {
225                             FileEntry entry = (FileEntry) element;
226                             setDeleteEnabledState(entry);
227                             mCreateNewFolderAction.setEnabled(entry.isDirectory());
228                         } else {
229                             mDeleteAction.setEnabled(false);
230                         }
231                     }
232                 }
233             }
234         });
235 
236         // add support for double click
237         mTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
238             @Override
239             public void doubleClick(DoubleClickEvent event) {
240                 ISelection sel = event.getSelection();
241 
242                 if (sel instanceof IStructuredSelection) {
243                     IStructuredSelection selection = (IStructuredSelection) sel;
244 
245                     if (selection.size() == 1) {
246                         FileEntry entry = (FileEntry)selection.getFirstElement();
247                         String name = entry.getName();
248 
249                         FileEntry parentEntry = entry.getParent();
250 
251                         // can't really do anything with no parent
252                         if (parentEntry == null) {
253                             return;
254                         }
255 
256                         // check this is a file like we want.
257                         Matcher m = mKeyFilePattern.matcher(name);
258                         if (m.matches()) {
259                             // get the name w/o the extension
260                             String baseName = m.group(1);
261 
262                             // add the data extension
263                             String dataName = baseName + TRACE_DATA_EXT;
264 
265                             FileEntry dataEntry = parentEntry.findChild(dataName);
266 
267                             handleTraceDoubleClick(baseName, entry, dataEntry);
268 
269                         } else {
270                             m = mDataFilePattern.matcher(name);
271                             if (m.matches()) {
272                                 // get the name w/o the extension
273                                 String baseName = m.group(1);
274 
275                                 // add the key extension
276                                 String keyName = baseName + TRACE_KEY_EXT;
277 
278                                 FileEntry keyEntry = parentEntry.findChild(keyName);
279 
280                                 handleTraceDoubleClick(baseName, keyEntry, entry);
281                             }
282                         }
283                     }
284                 }
285             }
286         });
287 
288         // setup drop listener
289         mTreeViewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE,
290                 new Transfer[] { FileTransfer.getInstance() },
291                 new ViewerDropAdapter(mTreeViewer) {
292             @Override
293             public boolean performDrop(Object data) {
294                 // get the item on which we dropped the item(s)
295                 FileEntry target = (FileEntry)getCurrentTarget();
296 
297                 // in case we drop at the same level as root
298                 if (target == null) {
299                     return false;
300                 }
301 
302                 // if the target is not a directory, we get the parent directory
303                 if (target.isDirectory() == false) {
304                     target = target.getParent();
305                 }
306 
307                 if (target == null) {
308                     return false;
309                 }
310 
311                 // get the list of files to drop
312                 String[] files = (String[])data;
313 
314                 // do the drop
315                 pushFiles(files, target);
316 
317                 // we need to finish with a refresh
318                 refresh(target);
319 
320                 return true;
321             }
322 
323             @Override
324             public boolean validateDrop(Object target, int operation, TransferData transferType) {
325                 if (target == null) {
326                     return false;
327                 }
328 
329                 // convert to the real item
330                 FileEntry targetEntry = (FileEntry)target;
331 
332                 // if the target is not a directory, we get the parent directory
333                 if (targetEntry.isDirectory() == false) {
334                     target = targetEntry.getParent();
335                 }
336 
337                 if (target == null) {
338                     return false;
339                 }
340 
341                 return true;
342             }
343         });
344 
345         // create and start the refresh thread
346         new Thread("Device Ls refresher") {
347             @Override
348             public void run() {
349                 while (true) {
350                     try {
351                         sleep(FileListingService.REFRESH_RATE);
352                     } catch (InterruptedException e) {
353                         return;
354                     }
355 
356                     if (mTree != null && mTree.isDisposed() == false) {
357                         Display display = mTree.getDisplay();
358                         if (display.isDisposed() == false) {
359                             display.asyncExec(new Runnable() {
360                                 @Override
361                                 public void run() {
362                                     if (mTree.isDisposed() == false) {
363                                         mTreeViewer.refresh(true);
364                                     }
365                                 }
366                             });
367                         } else {
368                             return;
369                         }
370                     } else {
371                         return;
372                     }
373                 }
374 
375             }
376         }.start();
377 
378         return mTree;
379     }
380 
381     @Override
postCreation()382     protected void postCreation() {
383 
384     }
385 
386     /**
387      * Sets the focus to the proper control inside the panel.
388      */
389     @Override
setFocus()390     public void setFocus() {
391         mTree.setFocus();
392     }
393 
394     /**
395      * Processes a double click on a trace file
396      * @param baseName the base name of the 2 files.
397      * @param keyEntry The FileEntry for the .key file.
398      * @param dataEntry The FileEntry for the .data file.
399      */
handleTraceDoubleClick(String baseName, FileEntry keyEntry, FileEntry dataEntry)400     private void handleTraceDoubleClick(String baseName, FileEntry keyEntry,
401             FileEntry dataEntry) {
402         // first we need to download the files.
403         File keyFile;
404         File dataFile;
405         String path;
406         try {
407             // create a temp file for keyFile
408             File f = File.createTempFile(baseName, DdmConstants.DOT_TRACE);
409             f.delete();
410             f.mkdir();
411 
412             path = f.getAbsolutePath();
413 
414             keyFile = new File(path + File.separator + keyEntry.getName());
415             dataFile = new File(path + File.separator + dataEntry.getName());
416         } catch (IOException e) {
417             return;
418         }
419 
420         // download the files
421         try {
422             SyncService sync = mCurrentDevice.getSyncService();
423             if (sync != null) {
424                 ISyncProgressMonitor monitor = SyncService.getNullProgressMonitor();
425                 sync.pullFile(keyEntry, keyFile.getAbsolutePath(), monitor);
426                 sync.pullFile(dataEntry, dataFile.getAbsolutePath(), monitor);
427 
428                 // now that we have the file, we need to launch traceview
429                 String[] command = new String[2];
430                 command[0] = DdmUiPreferences.getTraceview();
431                 command[1] = path + File.separator + baseName;
432 
433                 try {
434                     final Process p = Runtime.getRuntime().exec(command);
435 
436                     // create a thread for the output
437                     new Thread("Traceview output") {
438                         @Override
439                         public void run() {
440                             // create a buffer to read the stderr output
441                             InputStreamReader is = new InputStreamReader(p.getErrorStream());
442                             BufferedReader resultReader = new BufferedReader(is);
443 
444                             // read the lines as they come. if null is returned, it's
445                             // because the process finished
446                             try {
447                                 while (true) {
448                                     String line = resultReader.readLine();
449                                     if (line != null) {
450                                         DdmConsole.printErrorToConsole("Traceview: " + line);
451                                     } else {
452                                         break;
453                                     }
454                                 }
455                                 // get the return code from the process
456                                 p.waitFor();
457                             } catch (IOException e) {
458                             } catch (InterruptedException e) {
459 
460                             }
461                         }
462                     }.start();
463 
464                 } catch (IOException e) {
465                 }
466             }
467         } catch (IOException e) {
468             DdmConsole.printErrorToConsole(String.format(
469                     "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage()));
470             return;
471         } catch (SyncException e) {
472             if (e.wasCanceled() == false) {
473                 DdmConsole.printErrorToConsole(String.format(
474                         "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage()));
475                 return;
476             }
477         } catch (TimeoutException e) {
478             DdmConsole.printErrorToConsole(String.format(
479                     "Failed to pull %1$s: timeout", keyEntry.getName()));
480         } catch (AdbCommandRejectedException e) {
481             DdmConsole.printErrorToConsole(String.format(
482                     "Failed to pull %1$s: %2$s", keyEntry.getName(), e.getMessage()));
483         }
484     }
485 
486     /**
487      * Pull the current selection on the local drive. This method displays
488      * a dialog box to let the user select where to store the file(s) and
489      * folder(s).
490      */
pullSelection()491     public void pullSelection() {
492         // get the selection
493         TreeItem[] items = mTree.getSelection();
494 
495         // name of the single file pull, or null if we're pulling a directory
496         // or more than one object.
497         String filePullName = null;
498         FileEntry singleEntry = null;
499 
500         // are we pulling a single file?
501         if (items.length == 1) {
502             singleEntry = (FileEntry)items[0].getData();
503             if (singleEntry.getType() == FileListingService.TYPE_FILE) {
504                 filePullName = singleEntry.getName();
505             }
506         }
507 
508         // where do we save by default?
509         String defaultPath = mDefaultSave;
510         if (defaultPath == null) {
511             defaultPath = System.getProperty("user.home"); //$NON-NLS-1$
512         }
513 
514         if (filePullName != null) {
515             FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.SAVE);
516 
517             fileDialog.setText("Get Device File");
518             fileDialog.setFileName(filePullName);
519             fileDialog.setFilterPath(defaultPath);
520 
521             String fileName = fileDialog.open();
522             if (fileName != null) {
523                 mDefaultSave = fileDialog.getFilterPath();
524 
525                 pullFile(singleEntry, fileName);
526             }
527         } else {
528             DirectoryDialog directoryDialog = new DirectoryDialog(mParent.getShell(), SWT.SAVE);
529 
530             directoryDialog.setText("Get Device Files/Folders");
531             directoryDialog.setFilterPath(defaultPath);
532 
533             String directoryName = directoryDialog.open();
534             if (directoryName != null) {
535                 pullSelection(items, directoryName);
536             }
537         }
538     }
539 
540     /**
541      * Push new file(s) and folder(s) into the current selection. Current
542      * selection must be single item. If the current selection is not a
543      * directory, the parent directory is used.
544      * This method displays a dialog to let the user choose file to push to
545      * the device.
546      */
pushIntoSelection()547     public void pushIntoSelection() {
548         // get the name of the object we're going to pull
549         TreeItem[] items = mTree.getSelection();
550 
551         if (items.length == 0) {
552             return;
553         }
554 
555         FileDialog dlg = new FileDialog(mParent.getShell(), SWT.OPEN);
556         String fileName;
557 
558         dlg.setText("Put File on Device");
559 
560         // There should be only one.
561         FileEntry entry = (FileEntry)items[0].getData();
562         dlg.setFileName(entry.getName());
563 
564         String defaultPath = mDefaultSave;
565         if (defaultPath == null) {
566             defaultPath = System.getProperty("user.home"); //$NON-NLS-1$
567         }
568         dlg.setFilterPath(defaultPath);
569 
570         fileName = dlg.open();
571         if (fileName != null) {
572             mDefaultSave = dlg.getFilterPath();
573 
574             // we need to figure out the remote path based on the current selection type.
575             String remotePath;
576             FileEntry toRefresh = entry;
577             if (entry.isDirectory()) {
578                 remotePath = entry.getFullPath();
579             } else {
580                 toRefresh = entry.getParent();
581                 remotePath = toRefresh.getFullPath();
582             }
583 
584             pushFile(fileName, remotePath);
585             mTreeViewer.refresh(toRefresh);
586         }
587     }
588 
deleteSelection()589     public void deleteSelection() {
590         // get the name of the object we're going to pull
591         TreeItem[] items = mTree.getSelection();
592 
593         if (items.length != 1) {
594             return;
595         }
596 
597         FileEntry entry = (FileEntry)items[0].getData();
598         final FileEntry parentEntry = entry.getParent();
599 
600         // create the delete command
601         String command = "rm " + entry.getFullEscapedPath(); //$NON-NLS-1$
602 
603         try {
604             mCurrentDevice.executeShellCommand(command, new IShellOutputReceiver() {
605                 @Override
606                 public void addOutput(byte[] data, int offset, int length) {
607                     // pass
608                     // TODO get output to display errors if any.
609                 }
610 
611                 @Override
612                 public void flush() {
613                     mTreeViewer.refresh(parentEntry);
614                 }
615 
616                 @Override
617                 public boolean isCancelled() {
618                     return false;
619                 }
620             });
621         } catch (IOException e) {
622             // adb failed somehow, we do nothing. We should be displaying the error from the output
623             // of the shell command.
624         } catch (TimeoutException e) {
625             // adb failed somehow, we do nothing. We should be displaying the error from the output
626             // of the shell command.
627         } catch (AdbCommandRejectedException e) {
628             // adb failed somehow, we do nothing. We should be displaying the error from the output
629             // of the shell command.
630         } catch (ShellCommandUnresponsiveException e) {
631             // adb failed somehow, we do nothing. We should be displaying the error from the output
632             // of the shell command.
633         }
634 
635     }
636 
createNewFolderInSelection()637     public void createNewFolderInSelection() {
638         TreeItem[] items = mTree.getSelection();
639 
640         if (items.length != 1) {
641             return;
642         }
643 
644         final FileEntry entry = (FileEntry) items[0].getData();
645 
646         if (entry.isDirectory()) {
647             InputDialog inputDialog = new InputDialog(mTree.getShell(), "New Folder",
648                     "Please enter the new folder name", "New Folder", new IInputValidator() {
649                         @Override
650                         public String isValid(String newText) {
651                             if ((newText != null) && (newText.length() > 0)
652                                     && (newText.trim().length() > 0)
653                                     && (newText.indexOf('/') == -1)
654                                     && (newText.indexOf('\\') == -1)) {
655                                 return null;
656                             } else {
657                                 return "Invalid name";
658                             }
659                         }
660                     });
661             inputDialog.open();
662             String value = inputDialog.getValue();
663 
664             if (value != null) {
665                 // create the mkdir command
666                 String command = "mkdir " + entry.getFullEscapedPath() //$NON-NLS-1$
667                         + FileListingService.FILE_SEPARATOR + FileEntry.escape(value);
668 
669                 try {
670                     mCurrentDevice.executeShellCommand(command, new IShellOutputReceiver() {
671 
672                         @Override
673                         public boolean isCancelled() {
674                             return false;
675                         }
676 
677                         @Override
678                         public void flush() {
679                             mTreeViewer.refresh(entry);
680                         }
681 
682                         @Override
683                         public void addOutput(byte[] data, int offset, int length) {
684                             String errorMessage;
685                             if (data != null) {
686                                 errorMessage = new String(data);
687                             } else {
688                                 errorMessage = "";
689                             }
690                             Status status = new Status(IStatus.ERROR,
691                                     "DeviceExplorer", 0, errorMessage, null); //$NON-NLS-1$
692                             ErrorDialog.openError(mTree.getShell(), "New Folder Error",
693                                     "New Folder Error", status);
694                         }
695                     });
696                 } catch (TimeoutException e) {
697                     // adb failed somehow, we do nothing. We should be
698                     // displaying the error from the output of the shell
699                     // command.
700                 } catch (AdbCommandRejectedException e) {
701                     // adb failed somehow, we do nothing. We should be
702                     // displaying the error from the output of the shell
703                     // command.
704                 } catch (ShellCommandUnresponsiveException e) {
705                     // adb failed somehow, we do nothing. We should be
706                     // displaying the error from the output of the shell
707                     // command.
708                 } catch (IOException e) {
709                     // adb failed somehow, we do nothing. We should be
710                     // displaying the error from the output of the shell
711                     // command.
712                 }
713             }
714         }
715     }
716 
717     /**
718      * Force a full refresh of the explorer.
719      */
refresh()720     public void refresh() {
721         mTreeViewer.refresh(true);
722     }
723 
724     /**
725      * Sets the new device to explorer
726      */
switchDevice(final IDevice device)727     public void switchDevice(final IDevice device) {
728         if (device != mCurrentDevice) {
729             mCurrentDevice = device;
730             // now we change the input. but we need to do that in the
731             // ui thread.
732             if (mTree.isDisposed() == false) {
733                 Display d = mTree.getDisplay();
734                 d.asyncExec(new Runnable() {
735                     @Override
736                     public void run() {
737                         if (mTree.isDisposed() == false) {
738                             // new service
739                             if (mCurrentDevice != null) {
740                                 FileListingService fls = mCurrentDevice.getFileListingService();
741                                 mContentProvider.setListingService(fls);
742                                 mTreeViewer.setInput(fls.getRoot());
743                             }
744                         }
745                     }
746                 });
747             }
748         }
749     }
750 
751     /**
752      * Refresh an entry from a non ui thread.
753      * @param entry the entry to refresh.
754      */
refresh(final FileEntry entry)755     private void refresh(final FileEntry entry) {
756         Display d = mTreeViewer.getTree().getDisplay();
757         d.asyncExec(new Runnable() {
758             @Override
759             public void run() {
760                 mTreeViewer.refresh(entry);
761             }
762         });
763     }
764 
765     /**
766      * Pulls the selection from a device.
767      * @param items the tree selection the remote file on the device
768      * @param localDirector the local directory in which to save the files.
769      */
pullSelection(TreeItem[] items, final String localDirectory)770     private void pullSelection(TreeItem[] items, final String localDirectory) {
771         try {
772             final SyncService sync = mCurrentDevice.getSyncService();
773             if (sync != null) {
774                 // make a list of the FileEntry.
775                 ArrayList<FileEntry> entries = new ArrayList<FileEntry>();
776                 for (TreeItem item : items) {
777                     Object data = item.getData();
778                     if (data instanceof FileEntry) {
779                         entries.add((FileEntry)data);
780                     }
781                 }
782                 final FileEntry[] entryArray = entries.toArray(
783                         new FileEntry[entries.size()]);
784 
785                 SyncProgressHelper.run(new SyncRunnable() {
786                     @Override
787                     public void run(ISyncProgressMonitor monitor)
788                             throws SyncException, IOException, TimeoutException {
789                         sync.pull(entryArray, localDirectory, monitor);
790                     }
791 
792                     @Override
793                     public void close() {
794                         sync.close();
795                     }
796                 }, "Pulling file(s) from the device", mParent.getShell());
797             }
798         } catch (SyncException e) {
799             if (e.wasCanceled() == false) {
800                 DdmConsole.printErrorToConsole(String.format(
801                         "Failed to pull selection: %1$s", e.getMessage()));
802             }
803         } catch (Exception e) {
804             DdmConsole.printErrorToConsole( "Failed to pull selection");
805             DdmConsole.printErrorToConsole(e.getMessage());
806         }
807     }
808 
809     /**
810      * Pulls a file from a device.
811      * @param remote the remote file on the device
812      * @param local the destination filepath
813      */
pullFile(final FileEntry remote, final String local)814     private void pullFile(final FileEntry remote, final String local) {
815         try {
816             final SyncService sync = mCurrentDevice.getSyncService();
817             if (sync != null) {
818                 SyncProgressHelper.run(new SyncRunnable() {
819                         @Override
820                         public void run(ISyncProgressMonitor monitor)
821                                 throws SyncException, IOException, TimeoutException {
822                             sync.pullFile(remote, local, monitor);
823                         }
824 
825                         @Override
826                         public void close() {
827                             sync.close();
828                         }
829                     }, String.format("Pulling %1$s from the device", remote.getName()),
830                     mParent.getShell());
831             }
832         } catch (SyncException e) {
833             if (e.wasCanceled() == false) {
834                 DdmConsole.printErrorToConsole(String.format(
835                         "Failed to pull selection: %1$s", e.getMessage()));
836             }
837         } catch (Exception e) {
838             DdmConsole.printErrorToConsole( "Failed to pull selection");
839             DdmConsole.printErrorToConsole(e.getMessage());
840         }
841     }
842 
843     /**
844      * Pushes several files and directory into a remote directory.
845      * @param localFiles
846      * @param remoteDirectory
847      */
pushFiles(final String[] localFiles, final FileEntry remoteDirectory)848     private void pushFiles(final String[] localFiles, final FileEntry remoteDirectory) {
849         try {
850             final SyncService sync = mCurrentDevice.getSyncService();
851             if (sync != null) {
852                 SyncProgressHelper.run(new SyncRunnable() {
853                         @Override
854                         public void run(ISyncProgressMonitor monitor)
855                                 throws SyncException, IOException, TimeoutException {
856                             sync.push(localFiles, remoteDirectory, monitor);
857                         }
858 
859                         @Override
860                         public void close() {
861                             sync.close();
862                         }
863                     }, "Pushing file(s) to the device", mParent.getShell());
864             }
865         } catch (SyncException e) {
866             if (e.wasCanceled() == false) {
867                 DdmConsole.printErrorToConsole(String.format(
868                         "Failed to push selection: %1$s", e.getMessage()));
869             }
870         } catch (Exception e) {
871             DdmConsole.printErrorToConsole("Failed to push the items");
872             DdmConsole.printErrorToConsole(e.getMessage());
873         }
874     }
875 
876     /**
877      * Pushes a file on a device.
878      * @param local the local filepath of the file to push
879      * @param remoteDirectory the remote destination directory on the device
880      */
pushFile(final String local, final String remoteDirectory)881     private void pushFile(final String local, final String remoteDirectory) {
882         try {
883             final SyncService sync = mCurrentDevice.getSyncService();
884             if (sync != null) {
885                 // get the file name
886                 String[] segs = local.split(Pattern.quote(File.separator));
887                 String name = segs[segs.length-1];
888                 final String remoteFile = remoteDirectory + FileListingService.FILE_SEPARATOR
889                         + name;
890 
891                 SyncProgressHelper.run(new SyncRunnable() {
892                         @Override
893                         public void run(ISyncProgressMonitor monitor)
894                                 throws SyncException, IOException, TimeoutException {
895                             sync.pushFile(local, remoteFile, monitor);
896                         }
897 
898                         @Override
899                         public void close() {
900                             sync.close();
901                         }
902                     }, String.format("Pushing %1$s to the device.", name), mParent.getShell());
903             }
904         } catch (SyncException e) {
905             if (e.wasCanceled() == false) {
906                 DdmConsole.printErrorToConsole(String.format(
907                         "Failed to push selection: %1$s", e.getMessage()));
908             }
909         } catch (Exception e) {
910             DdmConsole.printErrorToConsole("Failed to push the item(s).");
911             DdmConsole.printErrorToConsole(e.getMessage());
912         }
913     }
914 
915     /**
916      * Sets the enabled state based on a FileEntry properties
917      * @param element The selected FileEntry
918      */
setDeleteEnabledState(FileEntry element)919     protected void setDeleteEnabledState(FileEntry element) {
920         mDeleteAction.setEnabled(element.getType() == FileListingService.TYPE_FILE);
921     }
922 }
923