1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.io; 19 20 import java.net.URI; 21 import java.net.URISyntaxException; 22 import java.net.URL; 23 import java.security.AccessController; 24 import java.util.ArrayList; 25 // BEGIN android-added 26 import java.util.Collections; 27 // END android-added 28 import java.util.List; 29 30 // BEGIN android-removed 31 // import org.apache.harmony.luni.util.DeleteOnExit; 32 // END android-removed 33 import org.apache.harmony.luni.util.Msg; 34 import org.apache.harmony.luni.util.PriviAction; 35 import org.apache.harmony.luni.util.Util; 36 37 /** 38 * An "abstract" representation of a file system entity identified by a 39 * pathname. The pathname may be absolute (relative to the root directory 40 * of the file system) or relative to the current directory in which the program 41 * is running. 42 * <p> 43 * This class provides methods for querying/changing information about the file 44 * as well as directory listing capabilities if the file represents a directory. 45 * <p> 46 * When manipulating file paths, the static fields of this class may be used to 47 * determine the platform specific separators. 48 * 49 * @see java.io.Serializable 50 * @see java.lang.Comparable 51 * 52 * @since Android 1.0 53 */ 54 public class File implements Serializable, Comparable<File> { 55 private static final long serialVersionUID = 301077366599181567L; 56 57 private String path; 58 59 transient byte[] properPath; 60 61 /** 62 * The system dependent file separator character. Since Android is a Unix- 63 * based system, this defaults to '/'. 64 * 65 * @since Android 1.0 66 */ 67 public static final char separatorChar; 68 69 /** 70 * The system dependent file separator string. The initial value of this 71 * field is the system property "file.separator". Since Android is a Unix- 72 * based system, this defaults to "/". 73 * 74 * @since Android 1.0 75 */ 76 public static final String separator; 77 78 /** 79 * The system dependent path separator character. Since Android is a Unix- 80 * based system, this defaults to ':'. 81 * 82 * @since Android 1.0 83 */ 84 public static final char pathSeparatorChar; 85 86 /** 87 * The system dependent path separator string. The initial value of this 88 * field is the system property "path.separator". Since Android is a Unix- 89 * based system, this defaults to ':'. 90 * 91 * @since Android 1.0 92 */ 93 public static final String pathSeparator; 94 95 /* Temp file counter */ 96 private static int counter; 97 98 private static boolean caseSensitive; 99 oneTimeInitialization()100 private static native void oneTimeInitialization(); 101 102 static { oneTimeInitialization()103 oneTimeInitialization(); 104 105 // The default protection domain grants access to these properties 106 // BEGIN android-changed 107 // We're on linux so the filesystem is case sensitive and the separator is /. 108 separatorChar = System.getProperty("file.separator", "/").charAt(0); //$NON-NLS-1$ //$NON-NLS-2$ 109 pathSeparatorChar = System.getProperty("path.separator", ";").charAt(0); //$NON-NLS-1$//$NON-NLS-2$ 110 separator = new String(new char[] { separatorChar }, 0, 1); 111 pathSeparator = new String(new char[] { pathSeparatorChar }, 0, 1); 112 caseSensitive = true; 113 // END android-changed 114 } 115 116 /** 117 * Constructs a new file using the specified directory and name. 118 * 119 * @param dir 120 * the directory where the file is stored. 121 * @param name 122 * the file's name. 123 * @throws NullPointerException 124 * if {@code name} is null. 125 * @since Android 1.0 126 */ File(File dir, String name)127 public File(File dir, String name) { 128 if (name == null) { 129 throw new NullPointerException(); 130 } 131 if (dir == null) { 132 this.path = fixSlashes(name); 133 } else { 134 this.path = calculatePath(dir.getPath(), name); 135 } 136 } 137 138 /** 139 * Constructs a new file using the specified path. 140 * 141 * @param path 142 * the path to be used for the file. 143 * @since Android 1.0 144 */ File(String path)145 public File(String path) { 146 // path == null check & NullPointerException thrown by fixSlashes 147 this.path = fixSlashes(path); 148 } 149 150 /** 151 * Constructs a new File using the specified directory path and file name, 152 * placing a path separator between the two. 153 * 154 * @param dirPath 155 * the path to the directory where the file is stored. 156 * @param name 157 * the file's name. 158 * @throws NullPointerException 159 * if {@code name} is null. 160 * @since Android 1.0 161 */ File(String dirPath, String name)162 public File(String dirPath, String name) { 163 if (name == null) { 164 throw new NullPointerException(); 165 } 166 if (dirPath == null) { 167 this.path = fixSlashes(name); 168 } else { 169 this.path = calculatePath(dirPath, name); 170 } 171 } 172 173 /** 174 * Constructs a new File using the path of the specified URI. {@code uri} 175 * needs to be an absolute and hierarchical Unified Resource Identifier with 176 * file scheme and non-empty path component, but with undefined authority, 177 * query or fragment components. 178 * 179 * @param uri 180 * the Unified Resource Identifier that is used to construct this 181 * file. 182 * @throws IllegalArgumentException 183 * if {@code uri} does not comply with the conditions above. 184 * @see #toURI 185 * @see java.net.URI 186 * @since Android 1.0 187 */ File(URI uri)188 public File(URI uri) { 189 // check pre-conditions 190 checkURI(uri); 191 this.path = fixSlashes(uri.getPath()); 192 } 193 calculatePath(String dirPath, String name)194 private String calculatePath(String dirPath, String name) { 195 dirPath = fixSlashes(dirPath); 196 if (!name.equals("")) { //$NON-NLS-1$ 197 // Remove all the proceeding separator chars from name 198 name = fixSlashes(name); 199 200 int separatorIndex = 0; 201 while ((separatorIndex < name.length()) 202 && (name.charAt(separatorIndex) == separatorChar)) { 203 separatorIndex++; 204 } 205 if (separatorIndex > 0) { 206 name = name.substring(separatorIndex, name.length()); 207 } 208 209 // Ensure there is a separator char between dirPath and name 210 if (dirPath.length() > 0 211 && (dirPath.charAt(dirPath.length() - 1) == separatorChar)) { 212 return dirPath + name; 213 } 214 return dirPath + separatorChar + name; 215 } 216 217 return dirPath; 218 } 219 checkURI(URI uri)220 private void checkURI(URI uri) { 221 if (!uri.isAbsolute()) { 222 throw new IllegalArgumentException(Msg.getString("K031a", uri)); //$NON-NLS-1$ 223 } else if (!uri.getRawSchemeSpecificPart().startsWith("/")) { //$NON-NLS-1$ 224 throw new IllegalArgumentException(Msg.getString("K031b", uri)); //$NON-NLS-1$ 225 } 226 227 String temp = uri.getScheme(); 228 if (temp == null || !temp.equals("file")) { //$NON-NLS-1$ 229 throw new IllegalArgumentException(Msg.getString("K031c", uri)); //$NON-NLS-1$ 230 } 231 232 temp = uri.getRawPath(); 233 if (temp == null || temp.length() == 0) { 234 throw new IllegalArgumentException(Msg.getString("K031d", uri)); //$NON-NLS-1$ 235 } 236 237 if (uri.getRawAuthority() != null) { 238 throw new IllegalArgumentException(Msg.getString( 239 "K031e", new String[] { "authority", uri.toString() })); //$NON-NLS-1$ //$NON-NLS-2$ 240 } 241 242 if (uri.getRawQuery() != null) { 243 throw new IllegalArgumentException(Msg.getString( 244 "K031e", new String[] { "query", uri.toString() })); //$NON-NLS-1$//$NON-NLS-2$ 245 } 246 247 if (uri.getRawFragment() != null) { 248 throw new IllegalArgumentException(Msg.getString( 249 "K031e", new String[] { "fragment", uri.toString() })); //$NON-NLS-1$ //$NON-NLS-2$ 250 } 251 } 252 rootsImpl()253 private static native byte[][] rootsImpl(); 254 isCaseSensitiveImpl()255 private static native boolean isCaseSensitiveImpl(); 256 257 /** 258 * Lists the file system roots. The Java platform may support zero or more 259 * file systems, each with its own platform-dependent root. Further, the 260 * canonical pathname of any file on the system will always begin with one 261 * of the returned file system roots. 262 * 263 * @return the array of file system roots. 264 * @since Android 1.0 265 */ listRoots()266 public static File[] listRoots() { 267 byte[][] rootsList = rootsImpl(); 268 if (rootsList == null) { 269 return new File[0]; 270 } 271 File result[] = new File[rootsList.length]; 272 for (int i = 0; i < rootsList.length; i++) { 273 result[i] = new File(Util.toString(rootsList[i])); 274 } 275 return result; 276 } 277 278 /** 279 * The purpose of this method is to take a path and fix the slashes up. This 280 * includes changing them all to the current platforms fileSeparator and 281 * removing duplicates. 282 */ fixSlashes(String origPath)283 private String fixSlashes(String origPath) { 284 int uncIndex = 1; 285 int length = origPath.length(), newLength = 0; 286 if (separatorChar == '/') { 287 uncIndex = 0; 288 } else if (length > 2 && origPath.charAt(1) == ':') { 289 uncIndex = 2; 290 } 291 292 boolean foundSlash = false; 293 char newPath[] = origPath.toCharArray(); 294 for (int i = 0; i < length; i++) { 295 char pathChar = newPath[i]; 296 if (pathChar == '\\' || pathChar == '/') { 297 /* UNC Name requires 2 leading slashes */ 298 if ((foundSlash && i == uncIndex) || !foundSlash) { 299 newPath[newLength++] = separatorChar; 300 foundSlash = true; 301 } 302 } else { 303 // check for leading slashes before a drive 304 if (pathChar == ':' 305 && uncIndex > 0 306 && (newLength == 2 || (newLength == 3 && newPath[1] == separatorChar)) 307 && newPath[0] == separatorChar) { 308 newPath[0] = newPath[newLength - 1]; 309 newLength = 1; 310 // allow trailing slash after drive letter 311 uncIndex = 2; 312 } 313 newPath[newLength++] = pathChar; 314 foundSlash = false; 315 } 316 } 317 // remove trailing slash 318 if (foundSlash 319 && (newLength > (uncIndex + 1) || (newLength == 2 && newPath[0] != separatorChar))) { 320 newLength--; 321 } 322 String tempPath = new String(newPath, 0, newLength); 323 // If it's the same keep it identical for SecurityManager purposes 324 if (!tempPath.equals(origPath)) { 325 return tempPath; 326 } 327 return origPath; 328 } 329 330 /** 331 * Indicates whether the current context is allowed to read from this file. 332 * 333 * @return {@code true} if this file can be read, {@code false} otherwise. 334 * @throws SecurityException 335 * if a {@code SecurityManager} is installed and it denies the 336 * read request. 337 * @since Android 1.0 338 */ canRead()339 public boolean canRead() { 340 SecurityManager security = System.getSecurityManager(); 341 if (security != null) { 342 security.checkRead(path); 343 } 344 // BEGIN android-changed 345 return exists() && isReadableImpl(properPath(true)); 346 // END android-changed 347 } 348 349 /** 350 * Indicates whether the current context is allowed to write to this file. 351 * 352 * @return {@code true} if this file can be written, {@code false} 353 * otherwise. 354 * @throws SecurityException 355 * if a {@code SecurityManager} is installed and it denies the 356 * write request. 357 * @since Android 1.0 358 */ canWrite()359 public boolean canWrite() { 360 SecurityManager security = System.getSecurityManager(); 361 if (security != null) { 362 security.checkWrite(path); 363 } 364 365 // Cannot use exists() since that does an unwanted read-check. 366 boolean exists = false; 367 if (path.length() > 0) { 368 exists = existsImpl(properPath(true)); 369 } 370 // BEGIN android-changed 371 return exists && isWriteableImpl(properPath(true)); 372 // END android-changed 373 } 374 375 /** 376 * Returns the relative sort ordering of the paths for this file and the 377 * file {@code another}. The ordering is platform dependent. 378 * 379 * @param another 380 * a file to compare this file to 381 * @return an int determined by comparing the two paths. Possible values are 382 * described in the Comparable interface. 383 * @see Comparable 384 * @since Android 1.0 385 */ compareTo(File another)386 public int compareTo(File another) { 387 if (caseSensitive) { 388 return this.getPath().compareTo(another.getPath()); 389 } 390 return this.getPath().compareToIgnoreCase(another.getPath()); 391 } 392 393 /** 394 * Deletes this file. Directories must be empty before they will be deleted. 395 * 396 * @return {@code true} if this file was deleted, {@code false} otherwise. 397 * @throws SecurityException 398 * if a {@code SecurityManager} is installed and it denies the 399 * request. 400 * @see java.lang.SecurityManager#checkDelete 401 * @since Android 1.0 402 */ delete()403 public boolean delete() { 404 SecurityManager security = System.getSecurityManager(); 405 if (security != null) { 406 security.checkDelete(path); 407 } 408 byte[] propPath = properPath(true); 409 if ((path.length() != 0) && isDirectoryImpl(propPath)) { 410 return deleteDirImpl(propPath); 411 } 412 return deleteFileImpl(propPath); 413 } 414 deleteDirImpl(byte[] filePath)415 private native boolean deleteDirImpl(byte[] filePath); 416 deleteFileImpl(byte[] filePath)417 private native boolean deleteFileImpl(byte[] filePath); 418 419 /** 420 * Schedules this file to be automatically deleted once the virtual machine 421 * terminates. This will only happen when the virtual machine terminates 422 * normally as described by the Java Language Specification section 12.9. 423 * 424 * @throws SecurityException 425 * if a {@code SecurityManager} is installed and it denies the 426 * request. 427 * @since Android 1.0 428 */ deleteOnExit()429 public void deleteOnExit() { 430 SecurityManager security = System.getSecurityManager(); 431 if (security != null) { 432 security.checkDelete(path); 433 } 434 // BEGIN android-changed 435 DeleteOnExit.getInstance().addFile(getAbsoluteName()); 436 // END android-changed 437 } 438 439 /** 440 * Compares {@code obj} to this file and returns {@code true} if they 441 * represent the <em>same</em> object using a path specific comparison. 442 * 443 * @param obj 444 * the object to compare this file with. 445 * @return {@code true} if {@code obj} is the same as this object, 446 * {@code false} otherwise. 447 * @since Android 1.0 448 */ 449 @Override equals(Object obj)450 public boolean equals(Object obj) { 451 if (!(obj instanceof File)) { 452 return false; 453 } 454 if (!caseSensitive) { 455 return path.equalsIgnoreCase(((File) obj).getPath()); 456 } 457 return path.equals(((File) obj).getPath()); 458 } 459 460 /** 461 * Returns a boolean indicating whether this file can be found on the 462 * underlying file system. 463 * 464 * @return {@code true} if this file exists, {@code false} otherwise. 465 * @throws SecurityException 466 * if a {@code SecurityManager} is installed and it denies read 467 * access to this file. 468 * @see #getPath 469 * @since Android 1.0 470 */ exists()471 public boolean exists() { 472 if (path.length() == 0) { 473 return false; 474 } 475 SecurityManager security = System.getSecurityManager(); 476 if (security != null) { 477 security.checkRead(path); 478 } 479 return existsImpl(properPath(true)); 480 } 481 existsImpl(byte[] filePath)482 private native boolean existsImpl(byte[] filePath); 483 484 /** 485 * Returns the absolute path of this file. 486 * 487 * @return the absolute file path. 488 * @see java.lang.SecurityManager#checkPropertyAccess 489 * @since Android 1.0 490 */ getAbsolutePath()491 public String getAbsolutePath() { 492 byte[] absolute = properPath(false); 493 return Util.toString(absolute); 494 } 495 496 /** 497 * Returns a new file constructed using the absolute path of this file. 498 * 499 * @return a new file from this file's absolute path. 500 * @see java.lang.SecurityManager#checkPropertyAccess 501 * @since Android 1.0 502 */ getAbsoluteFile()503 public File getAbsoluteFile() { 504 return new File(this.getAbsolutePath()); 505 } 506 507 /** 508 * Returns the absolute path of this file with all references resolved. An 509 * <em>absolute</em> path is one that begins at the root of the file 510 * system. The canonical path is one in which all references have been 511 * resolved. For the cases of '..' and '.', where the file system supports 512 * parent and working directory respectively, these are removed and replaced 513 * with a direct directory reference. If the file does not exist, 514 * getCanonicalPath() may not resolve any references and simply returns an 515 * absolute path name or throws an IOException. 516 * 517 * @return the canonical path of this file. 518 * @throws IOException 519 * if an I/O error occurs. 520 * @see java.lang.SecurityManager#checkPropertyAccess 521 * @since Android 1.0 522 */ getCanonicalPath()523 public String getCanonicalPath() throws IOException { 524 byte[] result = properPath(false); 525 526 boolean exists = false; 527 byte[] pathBytes = result; 528 do { 529 byte[] linkBytes = getLinkImpl(pathBytes); 530 if (linkBytes == pathBytes) { 531 break; 532 } 533 if (linkBytes[0] == separatorChar) { 534 pathBytes = linkBytes; 535 } else { 536 int index = pathBytes.length - 1; 537 while (pathBytes[index] != separatorChar) { 538 index--; 539 } 540 byte[] temp = new byte[index + 1 + linkBytes.length]; 541 System.arraycopy(pathBytes, 0, temp, 0, index + 1); 542 System.arraycopy(linkBytes, 0, temp, index + 1, 543 linkBytes.length); 544 pathBytes = temp; 545 } 546 exists = existsImpl(pathBytes); 547 } while (exists); 548 if (exists) { 549 result = pathBytes; 550 } 551 552 int numSeparators = 1; 553 for (int i = 0; i < result.length; i++) { 554 if (result[i] == separatorChar) { 555 numSeparators++; 556 } 557 } 558 int sepLocations[] = new int[numSeparators]; 559 int rootLoc = 0; 560 if (separatorChar != '/') { 561 if (result[0] == '\\') { 562 rootLoc = (result.length > 1 && result[1] == '\\') ? 1 : 0; 563 } else { 564 rootLoc = 2; // skip drive i.e. c: 565 } 566 } 567 byte newResult[] = new byte[result.length + 1]; 568 int newLength = 0, lastSlash = 0, foundDots = 0; 569 sepLocations[lastSlash] = rootLoc; 570 for (int i = 0; i <= result.length; i++) { 571 if (i < rootLoc) { 572 newResult[newLength++] = result[i]; 573 } else { 574 if (i == result.length || result[i] == separatorChar) { 575 if (i == result.length && foundDots == 0) { 576 break; 577 } 578 if (foundDots == 1) { 579 /* Don't write anything, just reset and continue */ 580 foundDots = 0; 581 continue; 582 } 583 if (foundDots > 1) { 584 /* Go back N levels */ 585 lastSlash = lastSlash > (foundDots - 1) ? lastSlash 586 - (foundDots - 1) : 0; 587 newLength = sepLocations[lastSlash] + 1; 588 foundDots = 0; 589 continue; 590 } 591 sepLocations[++lastSlash] = newLength; 592 newResult[newLength++] = (byte) separatorChar; 593 continue; 594 } 595 if (result[i] == '.') { 596 foundDots++; 597 continue; 598 } 599 /* Found some dots within text, write them out */ 600 if (foundDots > 0) { 601 for (int j = 0; j < foundDots; j++) { 602 newResult[newLength++] = (byte) '.'; 603 } 604 } 605 newResult[newLength++] = result[i]; 606 foundDots = 0; 607 } 608 } 609 // remove trailing slash 610 if (newLength > (rootLoc + 1) 611 && newResult[newLength - 1] == separatorChar) { 612 newLength--; 613 } 614 newResult[newLength] = 0; 615 newResult = getCanonImpl(newResult); 616 newLength = newResult.length; 617 return Util.toString(newResult, 0, newLength); 618 } 619 620 /** 621 * Returns a new file created using the canonical path of this file. 622 * Equivalent to {@code new File(this.getCanonicalPath())}. 623 * 624 * @return the new file constructed from this file's canonical path. 625 * @throws IOException 626 * if an I/O error occurs. 627 * @see java.lang.SecurityManager#checkPropertyAccess 628 * @since Android 1.0 629 */ getCanonicalFile()630 public File getCanonicalFile() throws IOException { 631 return new File(getCanonicalPath()); 632 } 633 getCanonImpl(byte[] filePath)634 private native byte[] getCanonImpl(byte[] filePath); 635 636 /** 637 * Returns the name of the file or directory represented by this file. 638 * 639 * @return this file's name or an empty string if there is no name part in 640 * the file's path. 641 * @since Android 1.0 642 */ getName()643 public String getName() { 644 int separatorIndex = path.lastIndexOf(separator); 645 return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, 646 path.length()); 647 } 648 649 /** 650 * Returns the pathname of the parent of this file. This is the path up to 651 * but not including the last name. {@code null} is returned if there is no 652 * parent. 653 * 654 * @return this file's parent pathname or {@code null}. 655 * @since Android 1.0 656 */ getParent()657 public String getParent() { 658 int length = path.length(), firstInPath = 0; 659 if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') { 660 firstInPath = 2; 661 } 662 int index = path.lastIndexOf(separatorChar); 663 if (index == -1 && firstInPath > 0) { 664 index = 2; 665 } 666 if (index == -1 || path.charAt(length - 1) == separatorChar) { 667 return null; 668 } 669 if (path.indexOf(separatorChar) == index 670 && path.charAt(firstInPath) == separatorChar) { 671 return path.substring(0, index + 1); 672 } 673 return path.substring(0, index); 674 } 675 676 /** 677 * Returns a new file made from the pathname of the parent of this file. 678 * This is the path up to but not including the last name. {@code null} is 679 * returned when there is no parent. 680 * 681 * @return a new file representing this file's parent or {@code null}. 682 * @since Android 1.0 683 */ getParentFile()684 public File getParentFile() { 685 String tempParent = getParent(); 686 if (tempParent == null) { 687 return null; 688 } 689 return new File(tempParent); 690 } 691 692 /** 693 * Returns the path of this file. 694 * 695 * @return this file's path. 696 * @since Android 1.0 697 */ getPath()698 public String getPath() { 699 return path; 700 } 701 702 /** 703 * Returns an integer hash code for the receiver. Any two objects for which 704 * {@code equals} returns {@code true} must return the same hash code. 705 * 706 * @return this files's hash value. 707 * @see #equals 708 * @since Android 1.0 709 */ 710 @Override hashCode()711 public int hashCode() { 712 if (caseSensitive) { 713 return path.hashCode() ^ 1234321; 714 } 715 return path.toLowerCase().hashCode() ^ 1234321; 716 } 717 718 /** 719 * Indicates if this file's pathname is absolute. Whether a pathname is 720 * absolute is platform specific. On UNIX, absolute paths must start with 721 * the character '/'; on Windows it is absolute if either it starts with 722 * '\', '/', '\\' (to represent a file server), or a letter followed by a 723 * colon. 724 * 725 * @return {@code true} if this file's pathname is absolute, {@code false} 726 * otherwise. 727 * @see #getPath 728 * @since Android 1.0 729 */ isAbsolute()730 public boolean isAbsolute() { 731 // BEGIN android-changed 732 // Removing platform independent code because we're always on linux. 733 return path.length() > 0 && path.charAt(0) == separatorChar; 734 // END android-changed 735 } 736 737 // BEGIN android-removed 738 // private native boolean isAbsoluteImpl(byte[] filePath); 739 // END android-removed 740 741 /** 742 * Indicates if this file represents a <em>directory</em> on the 743 * underlying file system. 744 * 745 * @return {@code true} if this file is a directory, {@code false} 746 * otherwise. 747 * @throws SecurityException 748 * if a {@code SecurityManager} is installed and it denies read 749 * access to this file. 750 * @since Android 1.0 751 */ isDirectory()752 public boolean isDirectory() { 753 if (path.length() == 0) { 754 return false; 755 } 756 SecurityManager security = System.getSecurityManager(); 757 if (security != null) { 758 security.checkRead(path); 759 } 760 return isDirectoryImpl(properPath(true)); 761 } 762 isDirectoryImpl(byte[] filePath)763 private native boolean isDirectoryImpl(byte[] filePath); 764 765 /** 766 * Indicates if this file represents a <em>file</em> on the underlying 767 * file system. 768 * 769 * @return {@code true} if this file is a file, {@code false} otherwise. 770 * @throws SecurityException 771 * if a {@code SecurityManager} is installed and it denies read 772 * access to this file. 773 * @since Android 1.0 774 */ isFile()775 public boolean isFile() { 776 if (path.length() == 0) { 777 return false; 778 } 779 SecurityManager security = System.getSecurityManager(); 780 if (security != null) { 781 security.checkRead(path); 782 } 783 return isFileImpl(properPath(true)); 784 } 785 isFileImpl(byte[] filePath)786 private native boolean isFileImpl(byte[] filePath); 787 788 /** 789 * Returns whether or not this file is a hidden file as defined by the 790 * operating system. The notion of "hidden" is system-dependent. For 791 * Unix systems (like Android) a file is considered hidden if its name 792 * starts with a ".". For Windows systems there is an explicit flag in the 793 * file system for this purpose. 794 * 795 * @return {@code true} if the file is hidden, {@code false} otherwise. 796 * @throws SecurityException 797 * if a {@code SecurityManager} is installed and it denies read 798 * access to this file. 799 * @since Android 1.0 800 */ isHidden()801 public boolean isHidden() { 802 if (path.length() == 0) { 803 return false; 804 } 805 SecurityManager security = System.getSecurityManager(); 806 if (security != null) { 807 security.checkRead(path); 808 } 809 return isHiddenImpl(properPath(true)); 810 } 811 isHiddenImpl(byte[] filePath)812 private native boolean isHiddenImpl(byte[] filePath); 813 814 // BEGIN android-changed isReadableImpl(byte[] filePath)815 private native boolean isReadableImpl(byte[] filePath); 816 isWriteableImpl(byte[] filePath)817 private native boolean isWriteableImpl(byte[] filePath); 818 // END android-changed 819 getLinkImpl(byte[] filePath)820 private native byte[] getLinkImpl(byte[] filePath); 821 822 /** 823 * Returns the time when this file was last modified, measured in 824 * milliseconds since January 1st, 1970, midnight. 825 * 826 * @return the time when this file was last modified. 827 * @throws SecurityException 828 * if a {@code SecurityManager} is installed and it denies read 829 * access to this file. 830 * @since Android 1.0 831 */ lastModified()832 public long lastModified() { 833 SecurityManager security = System.getSecurityManager(); 834 if (security != null) { 835 security.checkRead(path); 836 } 837 long result = lastModifiedImpl(properPath(true)); 838 /* Temporary code to handle both return cases until natives fixed */ 839 if (result == -1 || result == 0) { 840 return 0; 841 } 842 return result; 843 } 844 lastModifiedImpl(byte[] filePath)845 private native long lastModifiedImpl(byte[] filePath); 846 847 /** 848 * Sets the time this file was last modified, measured in milliseconds since 849 * January 1st, 1970, midnight. 850 * 851 * @param time 852 * the last modification time for this file. 853 * @return {@code true} if the operation is successful, {@code false} 854 * otherwise. 855 * @throws IllegalArgumentException 856 * if {@code time < 0}. 857 * @throws SecurityException 858 * if a {@code SecurityManager} is installed and it denies write 859 * access to this file. 860 * @since Android 1.0 861 */ setLastModified(long time)862 public boolean setLastModified(long time) { 863 if (time < 0) { 864 throw new IllegalArgumentException(Msg.getString("K006a")); //$NON-NLS-1$ 865 } 866 SecurityManager security = System.getSecurityManager(); 867 if (security != null) { 868 security.checkWrite(path); 869 } 870 return (setLastModifiedImpl(properPath(true), time)); 871 } 872 setLastModifiedImpl(byte[] path, long time)873 private native boolean setLastModifiedImpl(byte[] path, long time); 874 875 /** 876 * Marks this file or directory to be read-only as defined by the operating 877 * system. 878 * 879 * @return {@code true} if the operation is successful, {@code false} 880 * otherwise. 881 * @throws SecurityException 882 * if a {@code SecurityManager} is installed and it denies write 883 * access to this file. 884 * @since Android 1.0 885 */ setReadOnly()886 public boolean setReadOnly() { 887 SecurityManager security = System.getSecurityManager(); 888 if (security != null) { 889 security.checkWrite(path); 890 } 891 return (setReadOnlyImpl(properPath(true))); 892 } 893 setReadOnlyImpl(byte[] path)894 private native boolean setReadOnlyImpl(byte[] path); 895 896 /** 897 * Returns the length of this file in bytes. 898 * 899 * @return the number of bytes in this file. 900 * @throws SecurityException 901 * if a {@code SecurityManager} is installed and it denies read 902 * access to this file. 903 * @since Android 1.0 904 */ length()905 public long length() { 906 SecurityManager security = System.getSecurityManager(); 907 if (security != null) { 908 security.checkRead(path); 909 } 910 return lengthImpl(properPath(true)); 911 } 912 lengthImpl(byte[] filePath)913 private native long lengthImpl(byte[] filePath); 914 915 /** 916 * Returns an array of strings with the file names in the directory 917 * represented by this file. The result is {@ null} if this file is not a 918 * directory. 919 * <p> 920 * The entries {@code .} and {@code ..} representing the current and parent 921 * directory are not returned as part of the list. 922 * </p> 923 * 924 * @return an array of strings with file names or {@code null}. 925 * @throws SecurityException 926 * if a {@code SecurityManager} is installed and it denies read 927 * access to this file. 928 * @see #isDirectory 929 * @since Android 1.0 930 */ list()931 public java.lang.String[] list() { 932 SecurityManager security = System.getSecurityManager(); 933 if (security != null) { 934 security.checkRead(path); 935 } 936 if (!isDirectory()) { 937 return null; 938 } 939 byte[][] implList = listImpl(properPath(true)); 940 if (implList == null) { 941 return new String[0]; 942 } 943 String result[] = new String[implList.length]; 944 for (int index = 0; index < implList.length; index++) { 945 result[index] = Util.toString(implList[index]); 946 } 947 return result; 948 } 949 950 /** 951 * Returns an array of files contained in the directory represented by this 952 * file. The result is {@code null} if this file is not a directory. The 953 * paths of the files in the array are absolute if the path of this file is 954 * absolute, they are relative otherwise. 955 * 956 * @return an array of files or {@code null}. 957 * @throws SecurityException 958 * if a {@code SecurityManager} is installed and it denies read 959 * access to this file. 960 * @see #list 961 * @since Android 1.0 962 */ listFiles()963 public File[] listFiles() { 964 String[] tempNames = list(); 965 if (tempNames == null) { 966 return null; 967 } 968 int resultLength = tempNames.length; 969 File results[] = new File[resultLength]; 970 for (int i = 0; i < resultLength; i++) { 971 results[i] = new File(this, tempNames[i]); 972 } 973 return results; 974 } 975 976 /** 977 * Gets a list of the files in the directory represented by this file. This 978 * list is then filtered through a FilenameFilter and files with matching 979 * names are returned as an array of files. Returns {@code null} if this 980 * file is not a directory. If {@code filter} is {@code null} then all 981 * filenames match. 982 * <p> 983 * The entries {@code .} and {@code ..} representing the current and parent 984 * directories are not returned as part of the list. 985 * </p> 986 * 987 * @param filter 988 * the filter to match names against, may be {@code null}. 989 * @return an array of files or {@code null}. 990 * @throws SecurityException 991 * if a {@code SecurityManager} is installed and it denies read 992 * access to this file. 993 * @see #list(FilenameFilter filter) 994 * @since Android 1.0 995 */ listFiles(FilenameFilter filter)996 public File[] listFiles(FilenameFilter filter) { 997 String[] tempNames = list(filter); 998 if (tempNames == null) { 999 return null; 1000 } 1001 int resultLength = tempNames.length; 1002 File results[] = new File[resultLength]; 1003 for (int i = 0; i < resultLength; i++) { 1004 results[i] = new File(this, tempNames[i]); 1005 } 1006 return results; 1007 } 1008 1009 /** 1010 * Gets a list of the files in the directory represented by this file. This 1011 * list is then filtered through a FileFilter and matching files are 1012 * returned as an array of files. Returns {@code null} if this file is not a 1013 * directory. If {@code filter} is {@code null} then all files match. 1014 * <p> 1015 * The entries {@code .} and {@code ..} representing the current and parent 1016 * directories are not returned as part of the list. 1017 * </p> 1018 * 1019 * @param filter 1020 * the filter to match names against, may be {@code null}. 1021 * @return an array of files or {@code null}. 1022 * @throws SecurityException 1023 * if a {@code SecurityManager} is installed and it denies read 1024 * access to this file. 1025 * @since Android 1.0 1026 */ listFiles(FileFilter filter)1027 public File[] listFiles(FileFilter filter) { 1028 SecurityManager security = System.getSecurityManager(); 1029 if (security != null) { 1030 security.checkRead(path); 1031 } 1032 if (!isDirectory()) { 1033 return null; 1034 } 1035 byte[][] implList = listImpl(properPath(true)); 1036 if (implList == null) { 1037 return new File[0]; 1038 } 1039 List<File> tempResult = new ArrayList<File>(); 1040 for (int index = 0; index < implList.length; index++) { 1041 String aName = Util.toString(implList[index]); 1042 File aFile = new File(this, aName); 1043 if (filter == null || filter.accept(aFile)) { 1044 tempResult.add(aFile); 1045 } 1046 } 1047 return tempResult.toArray(new File[tempResult.size()]); 1048 } 1049 1050 /** 1051 * Gets a list of the files in the directory represented by this file. This 1052 * list is then filtered through a FilenameFilter and the names of files 1053 * with matching names are returned as an array of strings. Returns 1054 * {@code null} if this file is not a directory. If {@code filter} is 1055 * {@code null} then all filenames match. 1056 * <p> 1057 * The entries {@code .} and {@code ..} representing the current and parent 1058 * directories are not returned as part of the list. 1059 * </p> 1060 * 1061 * @param filter 1062 * the filter to match names against, may be {@code null}. 1063 * @return an array of files or {@code null}. 1064 * @throws SecurityException 1065 * if a {@code SecurityManager} is installed and it denies read 1066 * access to this file. 1067 * @since Android 1.0 1068 */ list(FilenameFilter filter)1069 public java.lang.String[] list(FilenameFilter filter) { 1070 SecurityManager security = System.getSecurityManager(); 1071 if (security != null) { 1072 security.checkRead(path); 1073 } 1074 if (!isDirectory()) { 1075 return null; 1076 } 1077 byte[][] implList = listImpl(properPath(true)); 1078 if (implList == null) { 1079 return new String[0]; 1080 } 1081 java.util.Vector<String> tempResult = new java.util.Vector<String>(); 1082 for (int index = 0; index < implList.length; index++) { 1083 String aName = Util.toString(implList[index]); 1084 if (filter == null || filter.accept(this, aName)) { 1085 tempResult.addElement(aName); 1086 } 1087 } 1088 String[] result = new String[tempResult.size()]; 1089 tempResult.copyInto(result); 1090 return result; 1091 } 1092 listImpl(byte[] path)1093 private synchronized static native byte[][] listImpl(byte[] path); 1094 1095 /** 1096 * Creates the directory named by the trailing filename of this file. Does 1097 * not create the complete path required to create this directory. 1098 * 1099 * @return {@code true} if the directory has been created, {@code false} 1100 * otherwise. 1101 * @throws SecurityException 1102 * if a {@code SecurityManager} is installed and it denies write 1103 * access for this file. 1104 * @see #mkdirs 1105 * @since Android 1.0 1106 */ mkdir()1107 public boolean mkdir() { 1108 SecurityManager security = System.getSecurityManager(); 1109 if (security != null) { 1110 security.checkWrite(path); 1111 } 1112 return mkdirImpl(properPath(true)); 1113 } 1114 mkdirImpl(byte[] filePath)1115 private native boolean mkdirImpl(byte[] filePath); 1116 1117 /** 1118 * Creates the directory named by the trailing filename of this file, 1119 * including the complete directory path required to create this directory. 1120 * 1121 * @return {@code true} if the necessary directories have been created, 1122 * {@code false} if the target directory already exists or one of 1123 * the directories can not be created. 1124 * @throws SecurityException 1125 * if a {@code SecurityManager} is installed and it denies write 1126 * access for this file. 1127 * @see #mkdir 1128 * @since Android 1.0 1129 */ mkdirs()1130 public boolean mkdirs() { 1131 /* If the terminal directory already exists, answer false */ 1132 if (exists()) { 1133 return false; 1134 } 1135 1136 /* If the receiver can be created, answer true */ 1137 if (mkdir()) { 1138 return true; 1139 } 1140 1141 String parentDir = getParent(); 1142 /* If there is no parent and we were not created, answer false */ 1143 if (parentDir == null) { 1144 return false; 1145 } 1146 1147 /* Otherwise, try to create a parent directory and then this directory */ 1148 return (new File(parentDir).mkdirs() && mkdir()); 1149 } 1150 1151 /** 1152 * Creates a new, empty file on the file system according to the path 1153 * information stored in this file. 1154 * 1155 * @return {@code true} if the file has been created, {@code false} if it 1156 * already exists. 1157 * @throws IOException 1158 * if an I/O error occurs or the directory does not exist where 1159 * the file should have been created. 1160 * @throws SecurityException 1161 * if a {@code SecurityManager} is installed and it denies write 1162 * access for this file. 1163 * @since Android 1.0 1164 */ createNewFile()1165 public boolean createNewFile() throws IOException { 1166 SecurityManager security = System.getSecurityManager(); 1167 if (security != null) { 1168 security.checkWrite(path); 1169 } 1170 if (0 == path.length()) { 1171 throw new IOException(Msg.getString("KA012")); //$NON-NLS-1$ 1172 } 1173 int result = newFileImpl(properPath(true)); 1174 switch (result) { 1175 case 0: 1176 return true; 1177 case 1: 1178 return false; 1179 // BEGIN android-changed 1180 default: { 1181 // Try to provide a reasonable explanation. 1182 String msg = null; 1183 try { 1184 File parent = getAbsoluteFile().getParentFile(); 1185 if (parent == null) { 1186 /* 1187 * This shouldn't happen, unless the caller 1188 * tried to create "/". We just use the 1189 * generic message for this case. 1190 */ 1191 } else if (! parent.exists()) { 1192 msg = "Parent directory of file does not exist"; 1193 } else if (! parent.isDirectory()) { 1194 msg = "Parent of file is not a directory"; 1195 } else if (! parent.canWrite()) { 1196 msg = "Parent directory of file is not writable"; 1197 } 1198 } catch (RuntimeException ex) { 1199 /* 1200 * Ignore the exception, and just fall through to 1201 * use a generic message. 1202 */ 1203 } 1204 1205 if (msg == null) { 1206 msg = "Cannot create"; 1207 } 1208 throw new IOException(msg + ": " + path); //$NON-NLS-1$ 1209 } 1210 // END android-changed 1211 } 1212 } 1213 newFileImpl(byte[] filePath)1214 private native int newFileImpl(byte[] filePath); 1215 1216 /** 1217 * Creates an empty temporary file using the given prefix and suffix as part 1218 * of the file name. If suffix is null, {@code .tmp} is used. This method 1219 * is a convenience method that calls {@link #createTempFile(String, String, 1220 * File)} with the third argument being {@code null}. 1221 * 1222 * @param prefix 1223 * the prefix to the temp file name. 1224 * @param suffix 1225 * the suffix to the temp file name. 1226 * @return the temporary file. 1227 * @throws IOException 1228 * if an error occurs when writing the file. 1229 * @since Android 1.0 1230 */ createTempFile(String prefix, String suffix)1231 public static File createTempFile(String prefix, String suffix) 1232 throws IOException { 1233 return createTempFile(prefix, suffix, null); 1234 } 1235 1236 /** 1237 * Creates an empty temporary file in the given directory using the given 1238 * prefix and suffix as part of the file name. 1239 * 1240 * @param prefix 1241 * the prefix to the temp file name. 1242 * @param suffix 1243 * the suffix to the temp file name. 1244 * @param directory 1245 * the location to which the temp file is to be written, or 1246 * {@code null} for the default location for temporary files, 1247 * which is taken from the "java.io.tmpdir" system property. It 1248 * may be necessary to set this property to an existing, writable 1249 * directory for this method to work properly. 1250 * @return the temporary file. 1251 * @throws IllegalArgumentException 1252 * if the length of {@code prefix} is less than 3. 1253 * @throws IOException 1254 * if an error occurs when writing the file. 1255 * @since Android 1.0 1256 */ createTempFile(String prefix, String suffix, File directory)1257 public static File createTempFile(String prefix, String suffix, 1258 File directory) throws IOException { 1259 // Force a prefix null check first 1260 if (prefix.length() < 3) { 1261 throw new IllegalArgumentException(Msg.getString("K006b")); //$NON-NLS-1$ 1262 } 1263 String newSuffix = suffix == null ? ".tmp" : suffix; //$NON-NLS-1$ 1264 String tmpDir = "."; //$NON-NLS-1$ 1265 tmpDir = AccessController.doPrivileged(new PriviAction<String>( 1266 "java.io.tmpdir", ".")); //$NON-NLS-1$//$NON-NLS-2$ 1267 File result, tmpDirFile = directory == null ? new File(tmpDir) 1268 : directory; 1269 do { 1270 result = genTempFile(prefix, newSuffix, tmpDirFile); 1271 } while (!result.createNewFile()); 1272 return result; 1273 } 1274 genTempFile(String prefix, String suffix, File directory)1275 private static File genTempFile(String prefix, String suffix, File directory) { 1276 if (counter == 0) { 1277 int newInt = new java.util.Random().nextInt(); 1278 counter = ((newInt / 65535) & 0xFFFF) + 0x2710; 1279 } 1280 StringBuilder newName = new StringBuilder(); 1281 newName.append(prefix); 1282 newName.append(counter++); 1283 newName.append(suffix); 1284 return new File(directory, newName.toString()); 1285 } 1286 1287 // BEGIN android-changed 1288 // Removing platform independent code because we're always on linux. 1289 /** 1290 * Returns a string representing the proper path for this file. If this file 1291 * path is absolute, the user.dir property is not prepended, otherwise it 1292 * is. 1293 * 1294 * @param internal 1295 * is user.dir internal. 1296 * @return the proper path. 1297 */ properPath(boolean internal)1298 byte[] properPath(boolean internal) { 1299 if (properPath != null) { 1300 return properPath; 1301 } 1302 if(path.length() > 0 && path.charAt(0) == separatorChar) { 1303 return properPath = Util.getBytes(path); 1304 } 1305 // Check security by getting user.dir when the path is not absolute 1306 String userdir; 1307 if (internal) { 1308 userdir = AccessController.doPrivileged(new PriviAction<String>( 1309 "user.dir")); //$NON-NLS-1$ 1310 } else { 1311 userdir = System.getProperty("user.dir"); //$NON-NLS-1$ 1312 } 1313 if (path.length() == 0) { 1314 return properPath = Util.getBytes(userdir); 1315 } 1316 int length = userdir.length(); 1317 if (length > 0 && userdir.charAt(length - 1) == separatorChar) { 1318 return properPath = Util.getBytes(userdir + path); 1319 } 1320 return properPath = Util.getBytes(userdir + separator + path); 1321 } 1322 // END android-changed 1323 1324 // BEGIN android-removed 1325 // private static native byte[] properPathImpl(byte[] path); 1326 // END android-removed 1327 1328 /** 1329 * Renames this file to the name represented by the {@code dest} file. This 1330 * works for both normal files and directories. 1331 * 1332 * @param dest 1333 * the file containing the new name. 1334 * @return {@code true} if the File was renamed, {@code false} otherwise. 1335 * @throws SecurityException 1336 * if a {@code SecurityManager} is installed and it denies write 1337 * access for this file or the {@code dest} file. 1338 * @since Android 1.0 1339 */ renameTo(java.io.File dest)1340 public boolean renameTo(java.io.File dest) { 1341 SecurityManager security = System.getSecurityManager(); 1342 if (security != null) { 1343 security.checkWrite(path); 1344 security.checkWrite(dest.path); 1345 } 1346 return renameToImpl(properPath(true), dest.properPath(true)); 1347 } 1348 renameToImpl(byte[] pathExist, byte[] pathNew)1349 private native boolean renameToImpl(byte[] pathExist, byte[] pathNew); 1350 1351 /** 1352 * Returns a string containing a concise, human-readable description of this 1353 * file. 1354 * 1355 * @return a printable representation of this file. 1356 * @since Android 1.0 1357 */ 1358 @Override toString()1359 public String toString() { 1360 return path; 1361 } 1362 1363 /** 1364 * Returns a Uniform Resource Identifier for this file. The URI is system 1365 * dependent and may not be transferable between different operating / file 1366 * systems. 1367 * 1368 * @return an URI for this file. 1369 * @since Android 1.0 1370 */ toURI()1371 public URI toURI() { 1372 String name = getAbsoluteName(); 1373 try { 1374 if (!name.startsWith("/")) { //$NON-NLS-1$ 1375 // start with sep. 1376 return new URI("file", null, //$NON-NLS-1$ 1377 new StringBuilder(name.length() + 1).append('/') 1378 .append(name).toString(), null, null); 1379 } else if (name.startsWith("//")) { //$NON-NLS-1$ 1380 return new URI("file", name, null); // UNC path //$NON-NLS-1$ 1381 } 1382 return new URI("file", null, name, null, null); //$NON-NLS-1$ 1383 } catch (URISyntaxException e) { 1384 // this should never happen 1385 return null; 1386 } 1387 } 1388 1389 /** 1390 * Returns a Uniform Resource Locator for this file. The URL is system 1391 * dependent and may not be transferable between different operating / file 1392 * systems. 1393 * 1394 * @return an URL for this file. 1395 * @throws java.net.MalformedURLException 1396 * if the path cannot be transformed into an URL. 1397 * @since Android 1.0 1398 */ toURL()1399 public URL toURL() throws java.net.MalformedURLException { 1400 String name = getAbsoluteName(); 1401 if (!name.startsWith("/")) { //$NON-NLS-1$ 1402 // start with sep. 1403 return new URL("file", "", -1, new StringBuilder(name.length() + 1) //$NON-NLS-1$ //$NON-NLS-2$ 1404 .append('/').append(name).toString(), null); 1405 } else if (name.startsWith("//")) { //$NON-NLS-1$ 1406 return new URL("file:" + name); // UNC path //$NON-NLS-1$ 1407 } 1408 return new URL("file", "", -1, name, null); //$NON-NLS-1$ //$NON-NLS-2$ 1409 } 1410 getAbsoluteName()1411 private String getAbsoluteName() { 1412 File f = getAbsoluteFile(); 1413 String name = f.getPath(); 1414 1415 if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) { 1416 // Directories must end with a slash 1417 name = new StringBuilder(name.length() + 1).append(name) 1418 .append('/').toString(); 1419 } 1420 if (separatorChar != '/') { // Must convert slashes. 1421 name = name.replace(separatorChar, '/'); 1422 } 1423 return name; 1424 } 1425 writeObject(ObjectOutputStream stream)1426 private void writeObject(ObjectOutputStream stream) throws IOException { 1427 stream.defaultWriteObject(); 1428 stream.writeChar(separatorChar); 1429 1430 } 1431 readObject(ObjectInputStream stream)1432 private void readObject(ObjectInputStream stream) throws IOException, 1433 ClassNotFoundException { 1434 stream.defaultReadObject(); 1435 char inSeparator = stream.readChar(); 1436 path = path.replace(inSeparator, separatorChar); 1437 } 1438 } 1439 1440 // BEGIN android-added 1441 /** 1442 * Implements the actual DeleteOnExit mechanism. Is registered as a shutdown 1443 * hook in the Runtime, once it is actually being used. 1444 */ 1445 class DeleteOnExit extends Thread { 1446 1447 /** 1448 * Our singleton instance. 1449 */ 1450 private static DeleteOnExit instance; 1451 1452 /** 1453 * Our list of files scheduled for deletion. 1454 */ 1455 private ArrayList<String> files = new ArrayList<String>(); 1456 1457 /** 1458 * Returns our singleton instance, creating it if necessary. 1459 */ getInstance()1460 public static synchronized DeleteOnExit getInstance() { 1461 if (instance == null) { 1462 instance = new DeleteOnExit(); 1463 Runtime.getRuntime().addShutdownHook(instance); 1464 } 1465 1466 return instance; 1467 } 1468 1469 /** 1470 * Schedules a file for deletion. 1471 * 1472 * @param filename The file to delete. 1473 */ addFile(String filename)1474 public void addFile(String filename) { 1475 synchronized(files) { 1476 if (!files.contains(filename)) { 1477 files.add(filename); 1478 } 1479 } 1480 } 1481 1482 /** 1483 * Does the actual work. Note we (a) first sort the files lexicographically 1484 * and then (b) delete them in reverse order. This is to make sure files 1485 * get deleted before their parent directories. 1486 */ 1487 @Override run()1488 public void run() { 1489 Collections.sort(files); 1490 for (int i = files.size() - 1; i >= 0; i--) { 1491 new File(files.get(i)).delete(); 1492 } 1493 } 1494 } 1495 // END android-added 1496