1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.provider;
18
19import android.content.ContentResolver;
20import android.content.UriPermission;
21import android.net.Uri;
22import android.os.Environment;
23import android.os.FileUtils;
24import android.os.SystemClock;
25import android.support.v4.provider.DocumentFile;
26import android.test.AndroidTestCase;
27
28import java.io.DataInputStream;
29import java.io.DataOutputStream;
30import java.io.File;
31import java.io.FileInputStream;
32import java.io.FileOutputStream;
33import java.io.IOException;
34import java.util.List;
35
36/**
37 * Tests for {@link DocumentFile}
38 */
39public class DocumentFileTest extends AndroidTestCase {
40
41    private Uri treeUri;
42
43    private File root;
44    private File rootFoo;
45    private File rootMeow;
46    private File rootMeowCat;
47    private File rootMeowDog;
48    private File rootMeowBar;
49
50    private static final String FOO = "foo.randomext";
51    private static final String MEOW = "meow";
52    private static final String CAT = "cat.jpg";
53    private static final String DOG = "DOG.PDF";
54    private static final String BAR = "bar.png";
55
56    @Override
57    protected void setUp() throws Exception {
58        super.setUp();
59
60        final ContentResolver resolver = getContext().getContentResolver();
61        final List<UriPermission> perms = resolver.getPersistedUriPermissions();
62
63        if (perms.isEmpty()) {
64            throw new RuntimeException(
65                    "Failed to find outstanding grant; did you run the activity first?");
66        } else {
67            treeUri = perms.get(0).getUri();
68        }
69
70        root = Environment.getExternalStorageDirectory();
71        rootFoo = new File(root, FOO);
72        rootMeow = new File(root, MEOW);
73        rootMeowCat = new File(rootMeow, CAT);
74        rootMeowDog = new File(rootMeow, DOG);
75        rootMeowBar = new File(rootMeow, BAR);
76
77        resetRoot();
78    }
79
80    private void resetRoot() throws Exception {
81        final File tmp = new File(root, "bark.pdf");
82        FileUtils.deleteContents(tmp);
83        tmp.delete();
84
85        FileUtils.deleteContents(rootMeow);
86        rootMeow.mkdir();
87        rootMeowBar.mkdir();
88
89        writeInt(rootFoo, 12);
90        writeInt(rootMeowCat, 24);
91        writeInt(rootMeowDog, 48);
92    }
93
94    private interface DocumentTest {
95        public void exec(DocumentFile doc) throws Exception;
96    }
97
98    public void testSimple() throws Exception {
99        final DocumentTest test = new DocumentTest() {
100            @Override
101            public void exec(DocumentFile doc) throws Exception {
102                resetRoot();
103                assertTrue("isDirectory", doc.isDirectory());
104                assertFalse("isFile", doc.isFile());
105                assertTrue("canRead", doc.canRead());
106                assertTrue("canWrite", doc.canWrite());
107                assertTrue("exists", doc.exists());
108            }
109        };
110
111        test.exec(DocumentFile.fromFile(root));
112        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
113    }
114
115    public void testTraverse() throws Exception {
116        final DocumentTest test = new DocumentTest() {
117            @Override
118            public void exec(DocumentFile doc) throws Exception {
119                resetRoot();
120
121                // Root needs to at least contain our test file and dir
122                final DocumentFile foo = doc.findFile(FOO);
123                final DocumentFile meow = doc.findFile(MEOW);
124                assertTrue("isFile", foo.isFile());
125                assertTrue("isDirectory", meow.isDirectory());
126
127                // Traverse inside, and expect to find exact number of items
128                DocumentFile[] docs = meow.listFiles();
129                assertEquals("length", 3, docs.length);
130
131                final DocumentFile cat = meow.findFile(CAT);
132                final DocumentFile dog = meow.findFile(DOG);
133                final DocumentFile bar = meow.findFile(BAR);
134                assertTrue("isFile", cat.isFile());
135                assertTrue("isFile", dog.isFile());
136                assertTrue("isDirectory", bar.isDirectory());
137
138                // Empty directory is empty
139                assertEquals("length", 0, bar.listFiles().length);
140            }
141        };
142
143        test.exec(DocumentFile.fromFile(root));
144        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
145    }
146
147    public void testReadAndWrite() throws Exception {
148        final DocumentTest test = new DocumentTest() {
149            @Override
150            public void exec(DocumentFile doc) throws Exception {
151                resetRoot();
152
153                final DocumentFile foo = doc.findFile(FOO);
154                assertEquals("file", 12, readInt(rootFoo));
155                assertEquals("uri", 12, readInt(foo.getUri()));
156
157                // Underlying storage may not have sub-second resolution, so
158                // wait a few seconds.
159                SystemClock.sleep(2000);
160
161                // Ensure provider write makes its way to disk
162                final long beforeTime = foo.lastModified();
163                writeInt(foo.getUri(), 13);
164                final long afterTime = foo.lastModified();
165
166                assertEquals("file", 13, readInt(rootFoo));
167                assertEquals("uri", 13, readInt(foo.getUri()));
168
169                // Make sure we kicked time forward
170                assertTrue("lastModified", afterTime > beforeTime);
171            }
172        };
173
174        test.exec(DocumentFile.fromFile(root));
175        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
176    }
177
178    public void testMimes() throws Exception {
179        final DocumentTest test = new DocumentTest() {
180            @Override
181            public void exec(DocumentFile doc) throws Exception {
182                resetRoot();
183
184                final DocumentFile foo = doc.findFile(FOO);
185                final DocumentFile meow = doc.findFile(MEOW);
186                final DocumentFile cat = meow.findFile(CAT);
187                final DocumentFile dog = meow.findFile(DOG);
188                final DocumentFile bar = meow.findFile(BAR);
189
190                assertEquals(null, doc.getType());
191                assertEquals("application/octet-stream", foo.getType());
192                assertEquals(null, meow.getType());
193                assertEquals("image/jpeg", cat.getType());
194                assertEquals("application/pdf", dog.getType());
195                assertEquals(null, bar.getType());
196            }
197        };
198
199        test.exec(DocumentFile.fromFile(root));
200        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
201    }
202
203    public void testCreate() throws Exception {
204        final DocumentTest test = new DocumentTest() {
205            @Override
206            public void exec(DocumentFile doc) throws Exception {
207                resetRoot();
208
209                final DocumentFile meow = doc.findFile(MEOW);
210                assertEquals("length", 3, meow.listFiles().length);
211
212                // Create file with MIME
213                final DocumentFile newFile = meow.createFile("text/plain", "My New File");
214                assertEquals("My New File.txt", newFile.getName());
215                assertEquals("text/plain", newFile.getType());
216                assertTrue("isFile", newFile.isFile());
217                assertFalse("isDirectory", newFile.isDirectory());
218
219                assertEquals("length", 0, newFile.length());
220                writeInt(newFile.getUri(), 0);
221                assertEquals("length", 4, newFile.length());
222
223                // Create raw file
224                final DocumentFile newRaw = meow.createFile("application/octet-stream",
225                        "myrawfile");
226                assertEquals("myrawfile", newRaw.getName());
227                assertEquals("application/octet-stream", newRaw.getType());
228                assertTrue("isFile", newRaw.isFile());
229                assertFalse("isDirectory", newRaw.isDirectory());
230
231                // Create directory
232                final DocumentFile newDir = meow.createDirectory("My New Directory.png");
233                assertEquals("My New Directory.png", newDir.getName());
234                assertFalse("isFile", newDir.isFile());
235                assertTrue("isDirectory", newDir.isDirectory());
236                assertEquals("length", 0, newDir.listFiles().length);
237
238                // And overall dir grew
239                assertEquals("length", 6, meow.listFiles().length);
240            }
241        };
242
243        test.exec(DocumentFile.fromFile(root));
244        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
245    }
246
247    public void testDelete() throws Exception {
248        final DocumentTest test = new DocumentTest() {
249            @Override
250            public void exec(DocumentFile doc) throws Exception {
251                resetRoot();
252
253                final DocumentFile meow = doc.findFile(MEOW);
254                final DocumentFile cat = meow.findFile(CAT);
255                final DocumentFile dog = meow.findFile(DOG);
256
257                // Delete single file
258                assertTrue(cat.delete());
259                assertNull("cat", meow.findFile(CAT));
260
261                // Other file still exists
262                assertTrue("exists", dog.exists());
263
264                // Delete entire tree
265                assertTrue(meow.delete());
266                assertNull("meow", doc.findFile(MEOW));
267
268                // Nuking tree deleted other file
269                assertFalse("exists", dog.exists());
270            }
271        };
272
273        test.exec(DocumentFile.fromFile(root));
274        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
275    }
276
277    public void testRename() throws Exception {
278        final DocumentTest test = new DocumentTest() {
279            @Override
280            public void exec(DocumentFile doc) throws Exception {
281                resetRoot();
282
283                DocumentFile meow = doc.findFile(MEOW);
284                DocumentFile cat = meow.findFile(CAT);
285                DocumentFile dog = meow.findFile(DOG);
286                assertTrue(dog.exists());
287
288                // Rename a file
289                assertEquals("cat.jpg", cat.getName());
290                assertEquals("image/jpeg", cat.getType());
291
292                assertTrue(cat.renameTo("music.aAc"));
293                assertEquals("music.aAc", cat.getName());
294                assertEquals("audio/aac", cat.getType());
295
296                // Rename a directory
297                assertEquals("meow", meow.getName());
298                assertEquals(null, meow.getType());
299                assertTrue(meow.isDirectory());
300                assertEquals(3, meow.listFiles().length);
301
302                assertTrue(meow.renameTo("bark.pdf"));
303                assertEquals("bark.pdf", meow.getName());
304                assertEquals(null, meow.getType());
305                assertTrue(meow.isDirectory());
306                assertEquals(3, meow.listFiles().length);
307
308                // Current implementation of ExternalStorageProvider invalidates
309                // all children documents when directory is renamed.
310                assertFalse(dog.exists());
311
312                // But we can find it again
313                dog = meow.findFile(DOG);
314                assertTrue(dog.exists());
315            }
316        };
317
318        test.exec(DocumentFile.fromFile(root));
319        test.exec(DocumentFile.fromTreeUri(getContext(), treeUri));
320    }
321
322    private void writeInt(Uri uri, int value) throws IOException {
323        final DataOutputStream os = new DataOutputStream(
324                getContext().getContentResolver().openOutputStream(uri));
325        try {
326            os.writeInt(value);
327        } finally {
328            os.close();
329        }
330    }
331
332    private static void writeInt(File file, int value) throws IOException {
333        final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
334        try {
335            os.writeInt(value);
336        } finally {
337            os.close();
338        }
339    }
340
341    private int readInt(Uri uri) throws IOException {
342        final DataInputStream is = new DataInputStream(
343                getContext().getContentResolver().openInputStream(uri));
344        try {
345            return is.readInt();
346        } finally {
347            is.close();
348        }
349    }
350
351    private static int readInt(File file) throws IOException {
352        final DataInputStream is = new DataInputStream(new FileInputStream(file));
353        try {
354            return is.readInt();
355        } finally {
356            is.close();
357        }
358    }
359}
360