1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui;
18 
19 import static android.content.ContentResolver.wrap;
20 import static android.provider.DocumentsContract.buildChildDocumentsUri;
21 import static android.provider.DocumentsContract.buildDocumentUri;
22 import static android.provider.DocumentsContract.buildRootsUri;
23 
24 import static androidx.core.util.Preconditions.checkArgument;
25 
26 import static com.android.documentsui.base.DocumentInfo.getCursorString;
27 
28 import static junit.framework.Assert.assertEquals;
29 import static junit.framework.Assert.assertNotNull;
30 import static junit.framework.Assert.fail;
31 
32 import android.content.ContentProviderClient;
33 import android.content.Context;
34 import android.database.Cursor;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.FileUtils;
38 import android.os.ParcelFileDescriptor;
39 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
40 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
41 import android.os.RemoteException;
42 import android.provider.DocumentsContract;
43 import android.provider.DocumentsContract.Document;
44 import android.provider.DocumentsContract.Root;
45 import android.test.MoreAsserts;
46 import android.text.TextUtils;
47 
48 import androidx.annotation.Nullable;
49 
50 import com.android.documentsui.base.DocumentInfo;
51 import com.android.documentsui.base.RootInfo;
52 import com.android.documentsui.base.UserId;
53 import com.android.documentsui.roots.RootCursorWrapper;
54 
55 import com.google.common.collect.Lists;
56 
57 import libcore.io.Streams;
58 
59 import java.io.FileNotFoundException;
60 import java.io.IOException;
61 import java.util.ArrayList;
62 import java.util.List;
63 
64 /**
65  * Provides support for creation of documents in a test settings.
66  */
67 public class DocumentsProviderHelper {
68 
69     private final UserId mUserId;
70     private final String mAuthority;
71     private final ContentProviderClient mClient;
72 
DocumentsProviderHelper(UserId userId, String authority, Context context, String name)73     public DocumentsProviderHelper(UserId userId, String authority, Context context, String name) {
74         checkArgument(!TextUtils.isEmpty(authority));
75         mUserId = userId;
76         mAuthority = authority;
77         mClient = userId.getContentResolver(context).acquireContentProviderClient(name);
78     }
79 
getRoot(String documentId)80     public RootInfo getRoot(String documentId) throws RemoteException {
81         final Uri rootsUri = buildRootsUri(mAuthority);
82         Cursor cursor = null;
83         try {
84             cursor = mClient.query(rootsUri, null, null, null, null);
85             while (cursor.moveToNext()) {
86                 if (documentId.equals(getCursorString(cursor, Root.COLUMN_ROOT_ID))) {
87                     return RootInfo.fromRootsCursor(mUserId, mAuthority, cursor);
88                 }
89             }
90             throw new IllegalArgumentException("Can't find matching root for id=" + documentId);
91         } catch (Exception e) {
92             throw new RuntimeException("Can't load root for id=" + documentId , e);
93         } finally {
94             FileUtils.closeQuietly(cursor);
95         }
96     }
97 
createDocument(Uri parentUri, String mimeType, String name)98     public Uri createDocument(Uri parentUri, String mimeType, String name) {
99         if (name.contains("/")) {
100             throw new IllegalArgumentException("Name and mimetype probably interposed.");
101         }
102         try {
103             Uri uri = DocumentsContract.createDocument(wrap(mClient), parentUri, mimeType, name);
104             return uri;
105         } catch (FileNotFoundException e) {
106             throw new RuntimeException("Couldn't create document: " + name + " with mimetype "
107                     + mimeType, e);
108         }
109     }
110 
createDocument(String parentId, String mimeType, String name)111     public Uri createDocument(String parentId, String mimeType, String name) {
112         Uri parentUri = buildDocumentUri(mAuthority, parentId);
113         return createDocument(parentUri, mimeType, name);
114     }
115 
createDocument(RootInfo root, String mimeType, String name)116     public Uri createDocument(RootInfo root, String mimeType, String name) {
117         return createDocument(root.documentId, mimeType, name);
118     }
119 
createDocumentWithFlags(String documentId, String mimeType, String name, int flags, String... streamTypes)120     public Uri createDocumentWithFlags(String documentId, String mimeType, String name, int flags,
121             String... streamTypes)
122             throws RemoteException {
123         Bundle in = new Bundle();
124         in.putInt(StubProvider.EXTRA_FLAGS, flags);
125         in.putString(StubProvider.EXTRA_PARENT_ID, documentId);
126         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
127         in.putString(Document.COLUMN_DISPLAY_NAME, name);
128         in.putStringArrayList(StubProvider.EXTRA_STREAM_TYPES, Lists.newArrayList(streamTypes));
129 
130         Bundle out = mClient.call("createDocumentWithFlags", null, in);
131         Uri uri = out.getParcelable(DocumentsContract.EXTRA_URI);
132         return uri;
133     }
134 
createFolder(Uri parentUri, String name)135     public Uri createFolder(Uri parentUri, String name) {
136         return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
137     }
138 
createFolder(String parentId, String name)139     public Uri createFolder(String parentId, String name) {
140         Uri parentUri = buildDocumentUri(mAuthority, parentId);
141         return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
142     }
143 
createFolder(RootInfo root, String name)144     public Uri createFolder(RootInfo root, String name) {
145         return createDocument(root, Document.MIME_TYPE_DIR, name);
146     }
147 
writeDocument(Uri documentUri, byte[] contents)148     public void writeDocument(Uri documentUri, byte[] contents)
149             throws RemoteException, IOException {
150         ParcelFileDescriptor file = mClient.openFile(documentUri, "w", null);
151         try (AutoCloseOutputStream out = new AutoCloseOutputStream(file)) {
152             out.write(contents, 0, contents.length);
153         }
154         waitForWrite();
155     }
156 
writeAppendDocument(Uri documentUri, byte[] contents, int length)157     public void writeAppendDocument(Uri documentUri, byte[] contents, int length)
158             throws RemoteException, IOException {
159         ParcelFileDescriptor file = mClient.openFile(documentUri, "wa", null);
160         try (AutoCloseOutputStream out = new AutoCloseOutputStream(file)) {
161             out.write(contents, 0, length);
162         }
163         waitForWrite();
164     }
165 
waitForWrite()166     public void waitForWrite() throws RemoteException {
167         mClient.call("waitForWrite", null, null);
168     }
169 
readDocument(Uri documentUri)170     public byte[] readDocument(Uri documentUri) throws RemoteException, IOException {
171         ParcelFileDescriptor file = mClient.openFile(documentUri, "r", null);
172         byte[] buf = null;
173         try (AutoCloseInputStream in = new AutoCloseInputStream(file)) {
174             buf = Streams.readFully(in);
175         }
176         return buf;
177     }
178 
assertChildCount(Uri parentUri, int expected)179     public void assertChildCount(Uri parentUri, int expected) throws Exception {
180         List<DocumentInfo> children = listChildren(parentUri);
181         assertEquals("Incorrect file count after copy", expected, children.size());
182     }
183 
assertChildCount(String parentId, int expected)184     public void assertChildCount(String parentId, int expected) throws Exception {
185         List<DocumentInfo> children = listChildren(parentId, -1);
186         assertEquals("Incorrect file count after copy", expected, children.size());
187     }
188 
assertChildCount(RootInfo root, int expected)189     public void assertChildCount(RootInfo root, int expected) throws Exception {
190         assertChildCount(root.documentId, expected);
191     }
192 
assertHasFile(Uri parentUri, String name)193     public void assertHasFile(Uri parentUri, String name) throws Exception {
194         List<DocumentInfo> children = listChildren(parentUri);
195         for (DocumentInfo child : children) {
196             if (name.equals(child.displayName) && !child.isDirectory()) {
197                 return;
198             }
199         }
200         fail("Could not find file named=" + name + " in children " + children);
201     }
202 
assertHasFile(String parentId, String name)203     public void assertHasFile(String parentId, String name) throws Exception {
204         Uri parentUri = buildDocumentUri(mAuthority, parentId);
205         assertHasFile(parentUri, name);
206     }
207 
assertHasFile(RootInfo root, String name)208     public void assertHasFile(RootInfo root, String name) throws Exception {
209         assertHasFile(root.documentId, name);
210     }
211 
assertHasDirectory(Uri parentUri, String name)212     public void assertHasDirectory(Uri parentUri, String name) throws Exception {
213         List<DocumentInfo> children = listChildren(parentUri);
214         for (DocumentInfo child : children) {
215             if (name.equals(child.displayName) && child.isDirectory()) {
216                 return;
217             }
218         }
219         fail("Could not find name=" + name + " in children " + children);
220     }
221 
assertHasDirectory(String parentId, String name)222     public void assertHasDirectory(String parentId, String name) throws Exception {
223         Uri parentUri = buildDocumentUri(mAuthority, parentId);
224         assertHasDirectory(parentUri, name);
225     }
226 
assertHasDirectory(RootInfo root, String name)227     public void assertHasDirectory(RootInfo root, String name) throws Exception {
228         assertHasDirectory(root.documentId, name);
229     }
230 
assertDoesNotExist(Uri parentUri, String name)231     public void assertDoesNotExist(Uri parentUri, String name) throws Exception {
232         List<DocumentInfo> children = listChildren(parentUri);
233         for (DocumentInfo child : children) {
234             if (name.equals(child.displayName)) {
235                 fail("Found name=" + name + " in children " + children);
236             }
237         }
238     }
239 
assertDoesNotExist(String parentId, String name)240     public void assertDoesNotExist(String parentId, String name) throws Exception {
241         Uri parentUri = buildDocumentUri(mAuthority, parentId);
242         assertDoesNotExist(parentUri, name);
243     }
244 
assertDoesNotExist(RootInfo root, String name)245     public void assertDoesNotExist(RootInfo root, String name) throws Exception {
246         assertDoesNotExist(root.getUri(), name);
247     }
248 
findFile(String parentId, String name)249     public @Nullable DocumentInfo findFile(String parentId, String name)
250             throws Exception {
251         List<DocumentInfo> children = listChildren(parentId);
252         for (DocumentInfo child : children) {
253             if (name.equals(child.displayName)) {
254                 return child;
255             }
256         }
257         return null;
258     }
259 
findDocument(String parentId, String name)260     public DocumentInfo findDocument(String parentId, String name) throws Exception {
261         List<DocumentInfo> children = listChildren(parentId);
262         for (DocumentInfo child : children) {
263             if (name.equals(child.displayName)) {
264                 return child;
265             }
266         }
267         return null;
268     }
269 
findDocument(Uri parentUri, String name)270     public DocumentInfo findDocument(Uri parentUri, String name) throws Exception {
271         List<DocumentInfo> children = listChildren(parentUri);
272         for (DocumentInfo child : children) {
273             if (name.equals(child.displayName)) {
274                 return child;
275             }
276         }
277         return null;
278     }
279 
listChildren(Uri parentUri)280     public List<DocumentInfo> listChildren(Uri parentUri) throws Exception {
281         String id = DocumentsContract.getDocumentId(parentUri);
282         return listChildren(id);
283     }
284 
listChildren(String documentId)285     public List<DocumentInfo> listChildren(String documentId) throws Exception {
286         return listChildren(documentId, 100);
287     }
288 
listChildren(Uri parentUri, int maxCount)289     public List<DocumentInfo> listChildren(Uri parentUri, int maxCount) throws Exception {
290         String id = DocumentsContract.getDocumentId(parentUri);
291         return listChildren(id, maxCount);
292     }
293 
listChildren(String documentId, int maxCount)294     public List<DocumentInfo> listChildren(String documentId, int maxCount) throws Exception {
295         Uri uri = buildChildDocumentsUri(mAuthority, documentId);
296         List<DocumentInfo> children = new ArrayList<>();
297         try (Cursor cursor = mClient.query(uri, null, null, null, null, null)) {
298             Cursor wrapper = new RootCursorWrapper(mUserId, mAuthority, "totally-fake", cursor,
299                     maxCount);
300             while (wrapper.moveToNext()) {
301                 children.add(DocumentInfo.fromDirectoryCursor(wrapper));
302             }
303         }
304         return children;
305     }
306 
assertFileContents(Uri documentUri, byte[] expected)307     public void assertFileContents(Uri documentUri, byte[] expected) throws Exception {
308         MoreAsserts.assertEquals(
309                 "Copied file contents differ",
310                 expected, readDocument(documentUri));
311     }
312 
assertFileContents(String parentId, String fileName, byte[] expected)313     public void assertFileContents(String parentId, String fileName, byte[] expected)
314             throws Exception {
315         DocumentInfo file = findFile(parentId, fileName);
316         assertNotNull(file);
317         assertFileContents(file.derivedUri, expected);
318     }
319 
320     /**
321      * A helper method for StubProvider only. Won't work with other providers.
322      * @throws RemoteException
323      */
createVirtualFile( RootInfo root, String path, String mimeType, byte[] content, String... streamTypes)324     public Uri createVirtualFile(
325             RootInfo root, String path, String mimeType, byte[] content, String... streamTypes)
326                     throws RemoteException {
327 
328         Bundle args = new Bundle();
329         args.putString(StubProvider.EXTRA_ROOT, root.rootId);
330         args.putString(StubProvider.EXTRA_PATH, path);
331         args.putString(Document.COLUMN_MIME_TYPE, mimeType);
332         args.putStringArrayList(StubProvider.EXTRA_STREAM_TYPES, Lists.newArrayList(streamTypes));
333         args.putByteArray(StubProvider.EXTRA_CONTENT, content);
334 
335         Bundle result = mClient.call("createVirtualFile", null, args);
336         String documentId = result.getString(Document.COLUMN_DOCUMENT_ID);
337 
338         return DocumentsContract.buildDocumentUri(mAuthority, documentId);
339     }
340 
setLoadingDuration(long duration)341     public void setLoadingDuration(long duration) throws RemoteException {
342         final Bundle extra = new Bundle();
343         extra.putLong(DocumentsContract.EXTRA_LOADING, duration);
344         mClient.call("setLoadingDuration", null, extra);
345     }
346 
configure(String args, Bundle configuration)347     public void configure(String args, Bundle configuration) throws RemoteException {
348         mClient.call("configure", args, configuration);
349     }
350 
simulateReadErrorsForFile(String args, Bundle configuration)351     public void simulateReadErrorsForFile(String args, Bundle configuration)
352             throws RemoteException {
353         mClient.call("simulateReadErrorsForFile", args, configuration);
354     }
355 
clear(String args, Bundle configuration)356     public void clear(String args, Bundle configuration) throws RemoteException {
357         mClient.call("clear", args, configuration);
358     }
359 
getRootList()360     public List<RootInfo> getRootList() throws RemoteException {
361         List<RootInfo> list = new ArrayList<>();
362         final Uri rootsUri = DocumentsContract.buildRootsUri(mAuthority);
363         Cursor cursor = null;
364         try {
365             cursor = mClient.query(rootsUri, null, null, null, null);
366             while (cursor.moveToNext()) {
367                 RootInfo rootInfo = RootInfo.fromRootsCursor(mUserId, mAuthority, cursor);
368                 if (rootInfo != null) {
369                     list.add(rootInfo);
370                 }
371             }
372         } catch (Exception e) {
373             throw new RuntimeException("Can't load rootInfo list", e);
374         } finally {
375             FileUtils.closeQuietly(cursor);
376         }
377         return list;
378     }
379 
cleanUp()380     public void cleanUp() {
381         mClient.close();
382     }
383 }
384