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