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