• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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