• 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.ddmlib;
18 
19 import java.io.IOException;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.HashMap;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26 
27 /**
28  * Provides {@link Device} side file listing service.
29  * <p/>To get an instance for a known {@link Device}, call {@link Device#getFileListingService()}.
30  */
31 public final class FileListingService {
32 
33     /** Pattern to find filenames that match "*.apk" */
34     private final static Pattern sApkPattern =
35         Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
36 
37     private final static String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
38 
39     /** Pattern to parse the output of the 'pm -lf' command.<br>
40      * The output format looks like:<br>
41      * /data/app/myapp.apk=com.mypackage.myapp */
42     private final static Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
43 
44     /** Top level data folder. */
45     public final static String DIRECTORY_DATA = "data"; //$NON-NLS-1$
46     /** Top level sdcard folder. */
47     public final static String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
48     /** Top level mount folder. */
49     public final static String DIRECTORY_MNT = "mnt"; //$NON-NLS-1$
50     /** Top level system folder. */
51     public final static String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
52     /** Top level temp folder. */
53     public final static String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
54     /** Application folder. */
55     public final static String DIRECTORY_APP = "app"; //$NON-NLS-1$
56 
57     public static final long REFRESH_RATE = 5000L;
58     /**
59      * Refresh test has to be slightly lower for precision issue.
60      */
61     static final long REFRESH_TEST = (long)(REFRESH_RATE * .8);
62 
63     /** Entry type: File */
64     public static final int TYPE_FILE = 0;
65     /** Entry type: Directory */
66     public static final int TYPE_DIRECTORY = 1;
67     /** Entry type: Directory Link */
68     public static final int TYPE_DIRECTORY_LINK = 2;
69     /** Entry type: Block */
70     public static final int TYPE_BLOCK = 3;
71     /** Entry type: Character */
72     public static final int TYPE_CHARACTER = 4;
73     /** Entry type: Link */
74     public static final int TYPE_LINK = 5;
75     /** Entry type: Socket */
76     public static final int TYPE_SOCKET = 6;
77     /** Entry type: FIFO */
78     public static final int TYPE_FIFO = 7;
79     /** Entry type: Other */
80     public static final int TYPE_OTHER = 8;
81 
82     /** Device side file separator. */
83     public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
84 
85     private static final String FILE_ROOT = "/"; //$NON-NLS-1$
86 
87 
88     /**
89      * Regexp pattern to parse the result from ls.
90      */
91     private static Pattern sLsPattern = Pattern.compile(
92         "^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+(\\S+)\\s+([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
93 
94     private Device mDevice;
95     private FileEntry mRoot;
96 
97     private ArrayList<Thread> mThreadList = new ArrayList<Thread>();
98 
99     /**
100      * Represents an entry in a directory. This can be a file or a directory.
101      */
102     public final static class FileEntry {
103         /** Pattern to escape filenames for shell command consumption. */
104         private final static Pattern sEscapePattern = Pattern.compile(
105                 "([\\\\()*+?\"'#/\\s])"); //$NON-NLS-1$
106 
107         /**
108          * Comparator object for FileEntry
109          */
110         private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>() {
111             @Override
112             public int compare(FileEntry o1, FileEntry o2) {
113                 if (o1 instanceof FileEntry && o2 instanceof FileEntry) {
114                     FileEntry fe1 = o1;
115                     FileEntry fe2 = o2;
116                     return fe1.name.compareTo(fe2.name);
117                 }
118                 return 0;
119             }
120         };
121 
122         FileEntry parent;
123         String name;
124         String info;
125         String permissions;
126         String size;
127         String date;
128         String time;
129         String owner;
130         String group;
131         int type;
132         boolean isAppPackage;
133 
134         boolean isRoot;
135 
136         /**
137          * Indicates whether the entry content has been fetched yet, or not.
138          */
139         long fetchTime = 0;
140 
141         final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
142 
143         /**
144          * Creates a new file entry.
145          * @param parent parent entry or null if entry is root
146          * @param name name of the entry.
147          * @param type entry type. Can be one of the following: {@link FileListingService#TYPE_FILE},
148          * {@link FileListingService#TYPE_DIRECTORY}, {@link FileListingService#TYPE_OTHER}.
149          */
FileEntry(FileEntry parent, String name, int type, boolean isRoot)150         private FileEntry(FileEntry parent, String name, int type, boolean isRoot) {
151             this.parent = parent;
152             this.name = name;
153             this.type = type;
154             this.isRoot = isRoot;
155 
156             checkAppPackageStatus();
157         }
158 
159         /**
160          * Returns the name of the entry
161          */
getName()162         public String getName() {
163             return name;
164         }
165 
166         /**
167          * Returns the size string of the entry, as returned by <code>ls</code>.
168          */
getSize()169         public String getSize() {
170             return size;
171         }
172 
173         /**
174          * Returns the size of the entry.
175          */
getSizeValue()176         public int getSizeValue() {
177             return Integer.parseInt(size);
178         }
179 
180         /**
181          * Returns the date string of the entry, as returned by <code>ls</code>.
182          */
getDate()183         public String getDate() {
184             return date;
185         }
186 
187         /**
188          * Returns the time string of the entry, as returned by <code>ls</code>.
189          */
getTime()190         public String getTime() {
191             return time;
192         }
193 
194         /**
195          * Returns the permission string of the entry, as returned by <code>ls</code>.
196          */
getPermissions()197         public String getPermissions() {
198             return permissions;
199         }
200 
201         /**
202          * Returns the extra info for the entry.
203          * <p/>For a link, it will be a description of the link.
204          * <p/>For an application apk file it will be the application package as returned
205          * by the Package Manager.
206          */
getInfo()207         public String getInfo() {
208             return info;
209         }
210 
211         /**
212          * Return the full path of the entry.
213          * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator.
214          */
getFullPath()215         public String getFullPath() {
216             if (isRoot) {
217                 return FILE_ROOT;
218             }
219             StringBuilder pathBuilder = new StringBuilder();
220             fillPathBuilder(pathBuilder, false);
221 
222             return pathBuilder.toString();
223         }
224 
225         /**
226          * Return the fully escaped path of the entry. This path is safe to use in a
227          * shell command line.
228          * @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator
229          */
getFullEscapedPath()230         public String getFullEscapedPath() {
231             StringBuilder pathBuilder = new StringBuilder();
232             fillPathBuilder(pathBuilder, true);
233 
234             return pathBuilder.toString();
235         }
236 
237         /**
238          * Returns the path as a list of segments.
239          */
getPathSegments()240         public String[] getPathSegments() {
241             ArrayList<String> list = new ArrayList<String>();
242             fillPathSegments(list);
243 
244             return list.toArray(new String[list.size()]);
245         }
246 
247         /**
248          * Returns true if the entry is a directory, false otherwise;
249          */
getType()250         public int getType() {
251             return type;
252         }
253 
254         /**
255          * Returns if the entry is a folder or a link to a folder.
256          */
isDirectory()257         public boolean isDirectory() {
258             return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK;
259         }
260 
261         /**
262          * Returns the parent entry.
263          */
getParent()264         public FileEntry getParent() {
265             return parent;
266         }
267 
268         /**
269          * Returns the cached children of the entry. This returns the cache created from calling
270          * <code>FileListingService.getChildren()</code>.
271          */
getCachedChildren()272         public FileEntry[] getCachedChildren() {
273             return mChildren.toArray(new FileEntry[mChildren.size()]);
274         }
275 
276         /**
277          * Returns the child {@link FileEntry} matching the name.
278          * This uses the cached children list.
279          * @param name the name of the child to return.
280          * @return the FileEntry matching the name or null.
281          */
findChild(String name)282         public FileEntry findChild(String name) {
283             for (FileEntry entry : mChildren) {
284                 if (entry.name.equals(name)) {
285                     return entry;
286                 }
287             }
288             return null;
289         }
290 
291         /**
292          * Returns whether the entry is the root.
293          */
isRoot()294         public boolean isRoot() {
295             return isRoot;
296         }
297 
addChild(FileEntry child)298         void addChild(FileEntry child) {
299             mChildren.add(child);
300         }
301 
setChildren(ArrayList<FileEntry> newChildren)302         void setChildren(ArrayList<FileEntry> newChildren) {
303             mChildren.clear();
304             mChildren.addAll(newChildren);
305         }
306 
needFetch()307         boolean needFetch() {
308             if (fetchTime == 0) {
309                 return true;
310             }
311             long current = System.currentTimeMillis();
312             if (current-fetchTime > REFRESH_TEST) {
313                 return true;
314             }
315 
316             return false;
317         }
318 
319         /**
320          * Returns if the entry is a valid application package.
321          */
isApplicationPackage()322         public boolean isApplicationPackage() {
323             return isAppPackage;
324         }
325 
326         /**
327          * Returns if the file name is an application package name.
328          */
isAppFileName()329         public boolean isAppFileName() {
330             Matcher m = sApkPattern.matcher(name);
331             return m.matches();
332         }
333 
334         /**
335          * Recursively fills the pathBuilder with the full path
336          * @param pathBuilder a StringBuilder used to create the path.
337          * @param escapePath Whether the path need to be escaped for consumption by
338          * a shell command line.
339          */
fillPathBuilder(StringBuilder pathBuilder, boolean escapePath)340         protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath) {
341             if (isRoot) {
342                 return;
343             }
344 
345             if (parent != null) {
346                 parent.fillPathBuilder(pathBuilder, escapePath);
347             }
348             pathBuilder.append(FILE_SEPARATOR);
349             pathBuilder.append(escapePath ? escape(name) : name);
350         }
351 
352         /**
353          * Recursively fills the segment list with the full path.
354          * @param list The list of segments to fill.
355          */
fillPathSegments(ArrayList<String> list)356         protected void fillPathSegments(ArrayList<String> list) {
357             if (isRoot) {
358                 return;
359             }
360 
361             if (parent != null) {
362                 parent.fillPathSegments(list);
363             }
364 
365             list.add(name);
366         }
367 
368         /**
369          * Sets the internal app package status flag. This checks whether the entry is in an app
370          * directory like /data/app or /system/app
371          */
checkAppPackageStatus()372         private void checkAppPackageStatus() {
373             isAppPackage = false;
374 
375             String[] segments = getPathSegments();
376             if (type == TYPE_FILE && segments.length == 3 && isAppFileName()) {
377                 isAppPackage = DIRECTORY_APP.equals(segments[1]) &&
378                     (DIRECTORY_SYSTEM.equals(segments[0]) || DIRECTORY_DATA.equals(segments[0]));
379             }
380         }
381 
382         /**
383          * Returns an escaped version of the entry name.
384          * @param entryName
385          */
escape(String entryName)386         public static String escape(String entryName) {
387             return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
388         }
389     }
390 
391     private class LsReceiver extends MultiLineReceiver {
392 
393         private ArrayList<FileEntry> mEntryList;
394         private ArrayList<String> mLinkList;
395         private FileEntry[] mCurrentChildren;
396         private FileEntry mParentEntry;
397 
398         /**
399          * Create an ls receiver/parser.
400          * @param currentChildren The list of current children. To prevent
401          *      collapse during update, reusing the same FileEntry objects for
402          *      files that were already there is paramount.
403          * @param entryList the list of new children to be filled by the
404          *      receiver.
405          * @param linkList the list of link path to compute post ls, to figure
406          *      out if the link pointed to a file or to a directory.
407          */
LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList, ArrayList<String> linkList)408         public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList,
409                 ArrayList<String> linkList) {
410             mParentEntry = parentEntry;
411             mCurrentChildren = parentEntry.getCachedChildren();
412             mEntryList = entryList;
413             mLinkList = linkList;
414         }
415 
416         @Override
processNewLines(String[] lines)417         public void processNewLines(String[] lines) {
418             for (String line : lines) {
419                 // no need to handle empty lines.
420                 if (line.length() == 0) {
421                     continue;
422                 }
423 
424                 // run the line through the regexp
425                 Matcher m = sLsPattern.matcher(line);
426                 if (m.matches() == false) {
427                     continue;
428                 }
429 
430                 // get the name
431                 String name = m.group(7);
432 
433                 // get the rest of the groups
434                 String permissions = m.group(1);
435                 String owner = m.group(2);
436                 String group = m.group(3);
437                 String size = m.group(4);
438                 String date = m.group(5);
439                 String time = m.group(6);
440                 String info = null;
441 
442                 // and the type
443                 int objectType = TYPE_OTHER;
444                 switch (permissions.charAt(0)) {
445                     case '-' :
446                         objectType = TYPE_FILE;
447                         break;
448                     case 'b' :
449                         objectType = TYPE_BLOCK;
450                         break;
451                     case 'c' :
452                         objectType = TYPE_CHARACTER;
453                         break;
454                     case 'd' :
455                         objectType = TYPE_DIRECTORY;
456                         break;
457                     case 'l' :
458                         objectType = TYPE_LINK;
459                         break;
460                     case 's' :
461                         objectType = TYPE_SOCKET;
462                         break;
463                     case 'p' :
464                         objectType = TYPE_FIFO;
465                         break;
466                 }
467 
468 
469                 // now check what we may be linking to
470                 if (objectType == TYPE_LINK) {
471                     String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
472 
473                     // we should have 2 segments
474                     if (segments.length == 2) {
475                         // update the entry name to not contain the link
476                         name = segments[0];
477 
478                         // and the link name
479                         info = segments[1];
480 
481                         // now get the path to the link
482                         String[] pathSegments = info.split(FILE_SEPARATOR);
483                         if (pathSegments.length == 1) {
484                             // the link is to something in the same directory,
485                             // unless the link is ..
486                             if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
487                                 // set the type and we're done.
488                                 objectType = TYPE_DIRECTORY_LINK;
489                             } else {
490                                 // either we found the object already
491                                 // or we'll find it later.
492                             }
493                         }
494                     }
495 
496                     // add an arrow in front to specify it's a link.
497                     info = "-> " + info; //$NON-NLS-1$;
498                 }
499 
500                 // get the entry, either from an existing one, or a new one
501                 FileEntry entry = getExistingEntry(name);
502                 if (entry == null) {
503                     entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */);
504                 }
505 
506                 // add some misc info
507                 entry.permissions = permissions;
508                 entry.size = size;
509                 entry.date = date;
510                 entry.time = time;
511                 entry.owner = owner;
512                 entry.group = group;
513                 if (objectType == TYPE_LINK) {
514                     entry.info = info;
515                 }
516 
517                 mEntryList.add(entry);
518             }
519         }
520 
521         /**
522          * Queries for an already existing Entry per name
523          * @param name the name of the entry
524          * @return the existing FileEntry or null if no entry with a matching
525          * name exists.
526          */
getExistingEntry(String name)527         private FileEntry getExistingEntry(String name) {
528             for (int i = 0 ; i < mCurrentChildren.length; i++) {
529                 FileEntry e = mCurrentChildren[i];
530 
531                 // since we're going to "erase" the one we use, we need to
532                 // check that the item is not null.
533                 if (e != null) {
534                     // compare per name, case-sensitive.
535                     if (name.equals(e.name)) {
536                         // erase from the list
537                         mCurrentChildren[i] = null;
538 
539                         // and return the object
540                         return e;
541                     }
542                 }
543             }
544 
545             // couldn't find any matching object, return null
546             return null;
547         }
548 
549         @Override
isCancelled()550         public boolean isCancelled() {
551             return false;
552         }
553 
finishLinks()554         public void finishLinks() {
555             // TODO Handle links in the listing service
556         }
557     }
558 
559     /**
560      * Classes which implement this interface provide a method that deals with asynchronous
561      * result from <code>ls</code> command on the device.
562      *
563      * @see FileListingService#getChildren(com.android.ddmlib.FileListingService.FileEntry, boolean, com.android.ddmlib.FileListingService.IListingReceiver)
564      */
565     public interface IListingReceiver {
setChildren(FileEntry entry, FileEntry[] children)566         public void setChildren(FileEntry entry, FileEntry[] children);
567 
refreshEntry(FileEntry entry)568         public void refreshEntry(FileEntry entry);
569     }
570 
571     /**
572      * Creates a File Listing Service for a specified {@link Device}.
573      * @param device The Device the service is connected to.
574      */
FileListingService(Device device)575     FileListingService(Device device) {
576         mDevice = device;
577     }
578 
579     /**
580      * Returns the root element.
581      * @return the {@link FileEntry} object representing the root element or
582      * <code>null</code> if the device is invalid.
583      */
getRoot()584     public FileEntry getRoot() {
585         if (mDevice != null) {
586             if (mRoot == null) {
587                 mRoot = new FileEntry(null /* parent */, "" /* name */, TYPE_DIRECTORY,
588                         true /* isRoot */);
589             }
590 
591             return mRoot;
592         }
593 
594         return null;
595     }
596 
597     /**
598      * Returns the children of a {@link FileEntry}.
599      * <p/>
600      * This method supports a cache mechanism and synchronous and asynchronous modes.
601      * <p/>
602      * If <var>receiver</var> is <code>null</code>, the device side <code>ls</code>
603      * command is done synchronously, and the method will return upon completion of the command.<br>
604      * If <var>receiver</var> is non <code>null</code>, the command is launched is a separate
605      * thread and upon completion, the receiver will be notified of the result.
606      * <p/>
607      * The result for each <code>ls</code> command is cached in the parent
608      * <code>FileEntry</code>. <var>useCache</var> allows usage of this cache, but only if the
609      * cache is valid. The cache is valid only for {@link FileListingService#REFRESH_RATE} ms.
610      * After that a new <code>ls</code> command is always executed.
611      * <p/>
612      * If the cache is valid and <code>useCache == true</code>, the method will always simply
613      * return the value of the cache, whether a {@link IListingReceiver} has been provided or not.
614      *
615      * @param entry The parent entry.
616      * @param useCache A flag to use the cache or to force a new ls command.
617      * @param receiver A receiver for asynchronous calls.
618      * @return The list of children or <code>null</code> for asynchronous calls.
619      *
620      * @see FileEntry#getCachedChildren()
621      */
getChildren(final FileEntry entry, boolean useCache, final IListingReceiver receiver)622     public FileEntry[] getChildren(final FileEntry entry, boolean useCache,
623             final IListingReceiver receiver) {
624         // first thing we do is check the cache, and if we already have a recent
625         // enough children list, we just return that.
626         if (useCache && entry.needFetch() == false) {
627             return entry.getCachedChildren();
628         }
629 
630         // if there's no receiver, then this is a synchronous call, and we
631         // return the result of ls
632         if (receiver == null) {
633             doLs(entry);
634             return entry.getCachedChildren();
635         }
636 
637         // this is a asynchronous call.
638         // we launch a thread that will do ls and give the listing
639         // to the receiver
640         Thread t = new Thread("ls " + entry.getFullPath()) { //$NON-NLS-1$
641             @Override
642             public void run() {
643                 doLs(entry);
644 
645                 receiver.setChildren(entry, entry.getCachedChildren());
646 
647                 final FileEntry[] children = entry.getCachedChildren();
648                 if (children.length > 0 && children[0].isApplicationPackage()) {
649                     final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
650 
651                     for (FileEntry child : children) {
652                         String path = child.getFullPath();
653                         map.put(path, child);
654                     }
655 
656                     // call pm.
657                     String command = PM_FULL_LISTING;
658                     try {
659                         mDevice.executeShellCommand(command, new MultiLineReceiver() {
660                             @Override
661                             public void processNewLines(String[] lines) {
662                                 for (String line : lines) {
663                                     if (line.length() > 0) {
664                                         // get the filepath and package from the line
665                                         Matcher m = sPmPattern.matcher(line);
666                                         if (m.matches()) {
667                                             // get the children with that path
668                                             FileEntry entry = map.get(m.group(1));
669                                             if (entry != null) {
670                                                 entry.info = m.group(2);
671                                                 receiver.refreshEntry(entry);
672                                             }
673                                         }
674                                     }
675                                 }
676                             }
677                             @Override
678                             public boolean isCancelled() {
679                                 return false;
680                             }
681                         });
682                     } catch (Exception e) {
683                         // adb failed somehow, we do nothing.
684                     }
685                 }
686 
687 
688                 // if another thread is pending, launch it
689                 synchronized (mThreadList) {
690                     // first remove ourselves from the list
691                     mThreadList.remove(this);
692 
693                     // then launch the next one if applicable.
694                     if (mThreadList.size() > 0) {
695                         Thread t = mThreadList.get(0);
696                         t.start();
697                     }
698                 }
699             }
700         };
701 
702         // we don't want to run multiple ls on the device at the same time, so we
703         // store the thread in a list and launch it only if there's no other thread running.
704         // the thread will launch the next one once it's done.
705         synchronized (mThreadList) {
706             // add to the list
707             mThreadList.add(t);
708 
709             // if it's the only one, launch it.
710             if (mThreadList.size() == 1) {
711                 t.start();
712             }
713         }
714 
715         // and we return null.
716         return null;
717     }
718 
719     /**
720      * Returns the children of a {@link FileEntry}.
721      * <p/>
722      * This method is the explicit synchronous version of
723      * {@link #getChildren(FileEntry, boolean, IListingReceiver)}. It is roughly equivalent to
724      * calling
725      * getChildren(FileEntry, false, null)
726      *
727      * @param entry The parent entry.
728      * @return The list of children
729      * @throws TimeoutException in case of timeout on the connection when sending the command.
730      * @throws AdbCommandRejectedException if adb rejects the command.
731      * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
732      *            for a period longer than <var>maxTimeToOutputResponse</var>.
733      * @throws IOException in case of I/O error on the connection.
734      */
getChildrenSync(final FileEntry entry)735     public FileEntry[] getChildrenSync(final FileEntry entry) throws TimeoutException,
736             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
737         doLsAndThrow(entry);
738         return entry.getCachedChildren();
739     }
740 
doLs(FileEntry entry)741     private void doLs(FileEntry entry) {
742         try {
743             doLsAndThrow(entry);
744         } catch (Exception e) {
745             // do nothing
746         }
747     }
748 
doLsAndThrow(FileEntry entry)749     private void doLsAndThrow(FileEntry entry) throws TimeoutException,
750             AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException {
751         // create a list that will receive the list of the entries
752         ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
753 
754         // create a list that will receive the link to compute post ls;
755         ArrayList<String> linkList = new ArrayList<String>();
756 
757         try {
758             // create the command
759             String command = "ls -l " + entry.getFullEscapedPath(); //$NON-NLS-1$
760 
761             // create the receiver object that will parse the result from ls
762             LsReceiver receiver = new LsReceiver(entry, entryList, linkList);
763 
764             // call ls.
765             mDevice.executeShellCommand(command, receiver);
766 
767             // finish the process of the receiver to handle links
768             receiver.finishLinks();
769         } finally {
770             // at this point we need to refresh the viewer
771             entry.fetchTime = System.currentTimeMillis();
772 
773             // sort the children and set them as the new children
774             Collections.sort(entryList, FileEntry.sEntryComparator);
775             entry.setChildren(entryList);
776         }
777     }
778 
779 }
780