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
17package com.android.documentsui;
18
19import static android.provider.DocumentsContract.buildChildDocumentsUri;
20import static android.provider.DocumentsContract.buildDocumentUri;
21import static android.provider.DocumentsContract.buildRootsUri;
22import static com.android.documentsui.base.DocumentInfo.getCursorString;
23import static com.android.internal.util.Preconditions.checkArgument;
24import static junit.framework.Assert.assertEquals;
25import static junit.framework.Assert.assertNotNull;
26import static junit.framework.Assert.fail;
27
28import android.content.ContentProviderClient;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.ParcelFileDescriptor;
33import android.os.ParcelFileDescriptor.AutoCloseInputStream;
34import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
35import android.os.RemoteException;
36import android.provider.DocumentsContract;
37import android.provider.DocumentsContract.Document;
38import android.provider.DocumentsContract.Root;
39import android.support.annotation.Nullable;
40import android.test.MoreAsserts;
41import android.text.TextUtils;
42
43import com.android.documentsui.base.DocumentInfo;
44import com.android.documentsui.base.RootInfo;
45import com.android.documentsui.roots.RootCursorWrapper;
46
47import com.google.android.collect.Lists;
48
49import libcore.io.IoUtils;
50import libcore.io.Streams;
51
52import java.io.IOException;
53import java.util.ArrayList;
54import java.util.List;
55
56/**
57 * Provides support for creation of documents in a test settings.
58 */
59public class DocumentsProviderHelper {
60
61    private final String mAuthority;
62    private final ContentProviderClient mClient;
63
64    public DocumentsProviderHelper(String authority, ContentProviderClient client) {
65        checkArgument(!TextUtils.isEmpty(authority));
66        mAuthority = authority;
67        mClient = client;
68    }
69
70    public RootInfo getRoot(String documentId) throws RemoteException {
71        final Uri rootsUri = buildRootsUri(mAuthority);
72        Cursor cursor = null;
73        try {
74            cursor = mClient.query(rootsUri, null, null, null, null);
75            while (cursor.moveToNext()) {
76                if (documentId.equals(getCursorString(cursor, Root.COLUMN_ROOT_ID))) {
77                    return RootInfo.fromRootsCursor(mAuthority, cursor);
78                }
79            }
80            throw new IllegalArgumentException("Can't find matching root for id=" + documentId);
81        } catch (Exception e) {
82            throw new RuntimeException("Can't load root for id=" + documentId , e);
83        } finally {
84            IoUtils.closeQuietly(cursor);
85        }
86    }
87
88    public Uri createDocument(Uri parentUri, String mimeType, String name) {
89        if (name.contains("/")) {
90            throw new IllegalArgumentException("Name and mimetype probably interposed.");
91        }
92        try {
93            Uri uri = DocumentsContract.createDocument(mClient, parentUri, mimeType, name);
94            return uri;
95        } catch (RemoteException e) {
96            throw new RuntimeException("Couldn't create document: " + name + " with mimetype "
97                    + mimeType, e);
98        }
99    }
100
101    public Uri createDocument(String parentId, String mimeType, String name) {
102        Uri parentUri = buildDocumentUri(mAuthority, parentId);
103        return createDocument(parentUri, mimeType, name);
104    }
105
106    public Uri createDocument(RootInfo root, String mimeType, String name) {
107        return createDocument(root.documentId, mimeType, name);
108    }
109
110    public Uri createDocumentWithFlags(String documentId, String mimeType, String name, int flags,
111            String... streamTypes)
112            throws RemoteException {
113        Bundle in = new Bundle();
114        in.putInt(StubProvider.EXTRA_FLAGS, flags);
115        in.putString(StubProvider.EXTRA_PARENT_ID, documentId);
116        in.putString(Document.COLUMN_MIME_TYPE, mimeType);
117        in.putString(Document.COLUMN_DISPLAY_NAME, name);
118        in.putStringArrayList(StubProvider.EXTRA_STREAM_TYPES, Lists.newArrayList(streamTypes));
119
120        Bundle out = mClient.call("createDocumentWithFlags", null, in);
121        Uri uri = out.getParcelable(DocumentsContract.EXTRA_URI);
122        return uri;
123    }
124
125    public Uri createFolder(Uri parentUri, String name) {
126        return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
127    }
128
129    public Uri createFolder(String parentId, String name) {
130        Uri parentUri = buildDocumentUri(mAuthority, parentId);
131        return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
132    }
133
134    public Uri createFolder(RootInfo root, String name) {
135        return createDocument(root, Document.MIME_TYPE_DIR, name);
136    }
137
138    public void writeDocument(Uri documentUri, byte[] contents)
139            throws RemoteException, IOException {
140        ParcelFileDescriptor file = mClient.openFile(documentUri, "w", null);
141        try (AutoCloseOutputStream out = new AutoCloseOutputStream(file)) {
142            out.write(contents, 0, contents.length);
143        }
144        waitForWrite();
145    }
146
147    public void waitForWrite() throws RemoteException {
148        mClient.call("waitForWrite", null, null);
149    }
150
151    public byte[] readDocument(Uri documentUri) throws RemoteException, IOException {
152        ParcelFileDescriptor file = mClient.openFile(documentUri, "r", null);
153        byte[] buf = null;
154        try (AutoCloseInputStream in = new AutoCloseInputStream(file)) {
155            buf = Streams.readFully(in);
156        }
157        return buf;
158    }
159
160    public void assertChildCount(Uri parentUri, int expected) throws Exception {
161        List<DocumentInfo> children = listChildren(parentUri);
162        assertEquals("Incorrect file count after copy", expected, children.size());
163    }
164
165    public void assertChildCount(String parentId, int expected) throws Exception {
166        List<DocumentInfo> children = listChildren(parentId, -1);
167        assertEquals("Incorrect file count after copy", expected, children.size());
168    }
169
170    public void assertChildCount(RootInfo root, int expected) throws Exception {
171        assertChildCount(root.documentId, expected);
172    }
173
174    public void assertHasFile(Uri parentUri, String name) throws Exception {
175        List<DocumentInfo> children = listChildren(parentUri);
176        for (DocumentInfo child : children) {
177            if (name.equals(child.displayName) && !child.isDirectory()) {
178                return;
179            }
180        }
181        fail("Could not find file named=" + name + " in children " + children);
182    }
183
184    public void assertHasFile(String parentId, String name) throws Exception {
185        Uri parentUri = buildDocumentUri(mAuthority, parentId);
186        assertHasFile(parentUri, name);
187    }
188
189    public void assertHasFile(RootInfo root, String name) throws Exception {
190        assertHasFile(root.documentId, name);
191    }
192
193    public void assertHasDirectory(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 name=" + name + " in children " + children);
201    }
202
203    public void assertHasDirectory(String parentId, String name) throws Exception {
204        Uri parentUri = buildDocumentUri(mAuthority, parentId);
205        assertHasDirectory(parentUri, name);
206    }
207
208    public void assertHasDirectory(RootInfo root, String name) throws Exception {
209        assertHasDirectory(root.documentId, name);
210    }
211
212    public void assertDoesNotExist(Uri parentUri, String name) throws Exception {
213        List<DocumentInfo> children = listChildren(parentUri);
214        for (DocumentInfo child : children) {
215            if (name.equals(child.displayName)) {
216                fail("Found name=" + name + " in children " + children);
217            }
218        }
219    }
220
221    public void assertDoesNotExist(String parentId, String name) throws Exception {
222        Uri parentUri = buildDocumentUri(mAuthority, parentId);
223        assertDoesNotExist(parentUri, name);
224    }
225
226    public void assertDoesNotExist(RootInfo root, String name) throws Exception {
227        assertDoesNotExist(root.getUri(), name);
228    }
229
230    public @Nullable DocumentInfo findFile(String parentId, String name)
231            throws Exception {
232        List<DocumentInfo> children = listChildren(parentId);
233        for (DocumentInfo child : children) {
234            if (name.equals(child.displayName)) {
235                return child;
236            }
237        }
238        return null;
239    }
240
241    public DocumentInfo findDocument(String parentId, String name) throws Exception {
242        List<DocumentInfo> children = listChildren(parentId);
243        for (DocumentInfo child : children) {
244            if (name.equals(child.displayName)) {
245                return child;
246            }
247        }
248        return null;
249    }
250
251    public DocumentInfo findDocument(Uri parentUri, String name) throws Exception {
252        List<DocumentInfo> children = listChildren(parentUri);
253        for (DocumentInfo child : children) {
254            if (name.equals(child.displayName)) {
255                return child;
256            }
257        }
258        return null;
259    }
260
261    public List<DocumentInfo> listChildren(Uri parentUri) throws Exception {
262        String id = DocumentsContract.getDocumentId(parentUri);
263        return listChildren(id);
264    }
265
266    public List<DocumentInfo> listChildren(String documentId) throws Exception {
267        return listChildren(documentId, 100);
268    }
269
270    public List<DocumentInfo> listChildren(Uri parentUri, int maxCount) throws Exception {
271        String id = DocumentsContract.getDocumentId(parentUri);
272        return listChildren(id, maxCount);
273    }
274
275    public List<DocumentInfo> listChildren(String documentId, int maxCount) throws Exception {
276        Uri uri = buildChildDocumentsUri(mAuthority, documentId);
277        List<DocumentInfo> children = new ArrayList<>();
278        try (Cursor cursor = mClient.query(uri, null, null, null, null, null)) {
279            Cursor wrapper = new RootCursorWrapper(mAuthority, "totally-fake", cursor, maxCount);
280            while (wrapper.moveToNext()) {
281                children.add(DocumentInfo.fromDirectoryCursor(wrapper));
282            }
283        }
284        return children;
285    }
286
287    public void assertFileContents(Uri documentUri, byte[] expected) throws Exception {
288        MoreAsserts.assertEquals(
289                "Copied file contents differ",
290                expected, readDocument(documentUri));
291    }
292
293    public void assertFileContents(String parentId, String fileName, byte[] expected)
294            throws Exception {
295        DocumentInfo file = findFile(parentId, fileName);
296        assertNotNull(file);
297        assertFileContents(file.derivedUri, expected);
298    }
299
300    /**
301     * A helper method for StubProvider only. Won't work with other providers.
302     * @throws RemoteException
303     */
304    public Uri createVirtualFile(
305            RootInfo root, String path, String mimeType, byte[] content, String... streamTypes)
306                    throws RemoteException {
307
308        Bundle args = new Bundle();
309        args.putString(StubProvider.EXTRA_ROOT, root.rootId);
310        args.putString(StubProvider.EXTRA_PATH, path);
311        args.putString(Document.COLUMN_MIME_TYPE, mimeType);
312        args.putStringArrayList(StubProvider.EXTRA_STREAM_TYPES, Lists.newArrayList(streamTypes));
313        args.putByteArray(StubProvider.EXTRA_CONTENT, content);
314
315        Bundle result = mClient.call("createVirtualFile", null, args);
316        String documentId = result.getString(Document.COLUMN_DOCUMENT_ID);
317
318        return DocumentsContract.buildDocumentUri(mAuthority, documentId);
319    }
320
321    public void setLoadingDuration(long duration) throws RemoteException {
322        final Bundle extra = new Bundle();
323        extra.putLong(DocumentsContract.EXTRA_LOADING, duration);
324        mClient.call("setLoadingDuration", null, extra);
325    }
326
327    public void configure(String args, Bundle configuration) throws RemoteException {
328        mClient.call("configure", args, configuration);
329    }
330}
331