1 /*
2  * Copyright (C) 2009 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 android.provider.cts;
18 
19 import static android.provider.DocumentsContract.buildChildDocumentsUri;
20 import static android.provider.DocumentsContract.buildDocumentUri;
21 import static android.provider.DocumentsContract.buildDocumentUriUsingTree;
22 import static android.provider.DocumentsContract.buildRecentDocumentsUri;
23 import static android.provider.DocumentsContract.buildRootUri;
24 import static android.provider.DocumentsContract.buildRootsUri;
25 import static android.provider.DocumentsContract.buildSearchDocumentsUri;
26 import static android.provider.DocumentsContract.buildTreeDocumentUri;
27 import static android.provider.DocumentsContract.copyDocument;
28 import static android.provider.DocumentsContract.createDocument;
29 import static android.provider.DocumentsContract.createWebLinkIntent;
30 import static android.provider.DocumentsContract.deleteDocument;
31 import static android.provider.DocumentsContract.ejectRoot;
32 import static android.provider.DocumentsContract.findDocumentPath;
33 import static android.provider.DocumentsContract.getDocumentMetadata;
34 import static android.provider.DocumentsContract.isChildDocument;
35 import static android.provider.DocumentsContract.moveDocument;
36 import static android.provider.DocumentsContract.removeDocument;
37 import static android.provider.DocumentsContract.renameDocument;
38 
39 import static org.junit.Assert.assertArrayEquals;
40 import static org.junit.Assert.assertEquals;
41 import static org.junit.Assert.assertFalse;
42 import static org.junit.Assert.assertTrue;
43 import static org.junit.Assert.fail;
44 import static org.mockito.ArgumentMatchers.any;
45 import static org.mockito.ArgumentMatchers.anyInt;
46 import static org.mockito.Mockito.CALLS_REAL_METHODS;
47 import static org.mockito.Mockito.RETURNS_DEFAULTS;
48 import static org.mockito.Mockito.doNothing;
49 import static org.mockito.Mockito.doReturn;
50 import static org.mockito.Mockito.doThrow;
51 import static org.mockito.Mockito.mock;
52 
53 import android.app.PendingIntent;
54 import android.content.ContentResolver;
55 import android.content.Context;
56 import android.content.Intent;
57 import android.content.IntentSender;
58 import android.content.pm.PackageManager;
59 import android.content.pm.ProviderInfo;
60 import android.content.pm.ResolveInfo;
61 import android.content.res.AssetFileDescriptor;
62 import android.database.Cursor;
63 import android.database.MatrixCursor;
64 import android.graphics.Bitmap;
65 import android.graphics.Canvas;
66 import android.graphics.Color;
67 import android.graphics.Point;
68 import android.net.Uri;
69 import android.os.Bundle;
70 import android.os.ParcelFileDescriptor;
71 import android.provider.DocumentsContract;
72 import android.provider.DocumentsContract.Path;
73 import android.provider.DocumentsProvider;
74 
75 import androidx.test.InstrumentationRegistry;
76 import androidx.test.runner.AndroidJUnit4;
77 
78 import org.junit.Before;
79 import org.junit.Test;
80 import org.junit.runner.RunWith;
81 
82 import java.io.File;
83 import java.io.FileOutputStream;
84 import java.io.OutputStream;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.List;
88 
89 @RunWith(AndroidJUnit4.class)
90 public class DocumentsContractTest {
91     private static final String AUTHORITY = "com.example";
92 
93     private static final String DOC_RED = "red";
94     private static final String DOC_GREEN = "green";
95     private static final String DOC_BLUE = "blue";
96     private static final String DOC_RESULT = "result";
97 
98     private static final Uri URI_RED = buildDocumentUri(AUTHORITY, DOC_RED);
99     private static final Uri URI_GREEN = buildDocumentUri(AUTHORITY, DOC_GREEN);
100     private static final Uri URI_BLUE = buildDocumentUri(AUTHORITY, DOC_BLUE);
101     private static final Uri URI_RESULT = buildDocumentUri(AUTHORITY, DOC_RESULT);
102 
103     private static final String MIME_TYPE = "application/octet-stream";
104     private static final String DISPLAY_NAME = "My Test";
105 
106     private Context mContext;
107     private DocumentsProvider mProvider;
108     private ContentResolver mResolver;
109 
110     @Before
setUp()111     public void setUp() {
112         mContext = mock(Context.class, RETURNS_DEFAULTS);
113         mProvider = mock(DocumentsProvider.class, CALLS_REAL_METHODS);
114 
115         final ProviderInfo pi = new ProviderInfo();
116         pi.authority = AUTHORITY;
117         pi.exported = true;
118         pi.grantUriPermissions = true;
119         pi.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS;
120         pi.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS;
121         mProvider.attachInfo(mContext, pi);
122 
123         mResolver = ContentResolver.wrap(mProvider);
124     }
125 
126     @Test
testRootUri()127     public void testRootUri() {
128         final String auth = "com.example";
129         final String rootId = "rootId";
130         final PackageManager pm = mock(PackageManager.class);
131         final ProviderInfo providerInfo = new ProviderInfo();
132         final ResolveInfo resolveInfo = new ResolveInfo();
133         final List<ResolveInfo> infoList = new ArrayList<>();
134 
135         providerInfo.authority = auth;
136         resolveInfo.providerInfo = providerInfo;
137         infoList.add(resolveInfo);
138 
139         doReturn(pm).when(mContext).getPackageManager();
140         doReturn(infoList).when(pm).queryIntentContentProviders(any(Intent.class), anyInt());
141 
142         final Uri uri = DocumentsContract.buildRootUri(auth, rootId);
143 
144         assertEquals(auth, uri.getAuthority());
145         assertEquals(rootId, DocumentsContract.getRootId(uri));
146         assertTrue(DocumentsContract.isRootUri(mContext, uri));
147     }
148 
149     @Test
testRootUri_returnFalse()150     public void testRootUri_returnFalse() {
151         final String auth = "com.example";
152         final String rootId = "rootId";
153         final PackageManager pm = mock(PackageManager.class);
154         final List<ResolveInfo> infoList = new ArrayList<>();
155 
156         doReturn(pm).when(mContext).getPackageManager();
157         doReturn(infoList).when(pm).queryIntentContentProviders(any(Intent.class), anyInt());
158 
159         final Uri uri = DocumentsContract.buildRootUri(auth, rootId);
160 
161         assertEquals(auth, uri.getAuthority());
162         assertEquals(rootId, DocumentsContract.getRootId(uri));
163         assertFalse(DocumentsContract.isRootUri(mContext, uri));
164     }
165 
166     @Test
testRootsUri()167     public void testRootsUri() {
168         final String auth = "com.example";
169         final PackageManager pm = mock(PackageManager.class);
170         final ProviderInfo providerInfo = new ProviderInfo();
171         final ResolveInfo resolveInfo = new ResolveInfo();
172         final List<ResolveInfo> infoList = new ArrayList<>();
173 
174         providerInfo.authority = auth;
175         resolveInfo.providerInfo = providerInfo;
176         infoList.add(resolveInfo);
177 
178         doReturn(pm).when(mContext).getPackageManager();
179         doReturn(infoList).when(pm).queryIntentContentProviders(any(Intent.class), anyInt());
180 
181         final Uri uri = DocumentsContract.buildRootsUri(auth);
182         assertEquals(auth, uri.getAuthority());
183         assertTrue(DocumentsContract.isRootsUri(mContext, uri));
184     }
185 
186     @Test
testRootsUri_returnsFalse()187     public void testRootsUri_returnsFalse() {
188         final String auth = "com.example";
189         final PackageManager pm = mock(PackageManager.class);
190         final List<ResolveInfo> infoList = new ArrayList<>();
191 
192         doReturn(pm).when(mContext).getPackageManager();
193         doReturn(infoList).when(pm).queryIntentContentProviders(any(Intent.class), anyInt());
194 
195         final Uri uri = DocumentsContract.buildRootsUri(auth);
196         assertEquals(auth, uri.getAuthority());
197         assertFalse(DocumentsContract.isRootsUri(mContext, uri));
198     }
199 
200     @Test
testDocumentUri()201     public void testDocumentUri() {
202         final String auth = "com.example";
203         final String docId = "doc:12";
204 
205         final Uri uri = DocumentsContract.buildDocumentUri(auth, docId);
206         assertEquals(auth, uri.getAuthority());
207         assertEquals(docId, DocumentsContract.getDocumentId(uri));
208         assertFalse(DocumentsContract.isTreeUri(uri));
209     }
210 
211     @Test
testTreeDocumentUri()212     public void testTreeDocumentUri() {
213         final String auth = "com.example";
214         final String treeId = "doc:12";
215         final String leafId = "doc:24";
216 
217         final Uri treeUri = DocumentsContract.buildTreeDocumentUri(auth, treeId);
218         assertEquals(auth, treeUri.getAuthority());
219         assertEquals(treeId, DocumentsContract.getTreeDocumentId(treeUri));
220         assertTrue(DocumentsContract.isTreeUri(treeUri));
221 
222         final Uri leafUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, leafId);
223         assertEquals(auth, leafUri.getAuthority());
224         assertEquals(treeId, DocumentsContract.getTreeDocumentId(leafUri));
225         assertEquals(leafId, DocumentsContract.getDocumentId(leafUri));
226         assertTrue(DocumentsContract.isTreeUri(leafUri));
227     }
228 
229     @Test
testCreateDocument()230     public void testCreateDocument() throws Exception {
231         doReturn(DOC_RESULT).when(mProvider).createDocument(DOC_RED, MIME_TYPE, DISPLAY_NAME);
232         assertEquals(URI_RESULT, createDocument(mResolver, URI_RED, MIME_TYPE, DISPLAY_NAME));
233     }
234 
235     @Test
testRenameDocument()236     public void testRenameDocument() throws Exception {
237         doReturn(DOC_RESULT).when(mProvider).renameDocument(DOC_RED, DISPLAY_NAME);
238         assertEquals(URI_RESULT, renameDocument(mResolver, URI_RED, DISPLAY_NAME));
239     }
240 
241     @Test
testDeleteDocument()242     public void testDeleteDocument() throws Exception {
243         doNothing().when(mProvider).deleteDocument(DOC_RED);
244         assertEquals(true, deleteDocument(mResolver, URI_RED));
245     }
246 
247     @Test
testDeleteDocument_Failure()248     public void testDeleteDocument_Failure() throws Exception {
249         doThrow(new RuntimeException()).when(mProvider).deleteDocument(DOC_RED);
250         try {
251             deleteDocument(mResolver, URI_RED);
252             fail();
253         } catch (RuntimeException expected) {
254         }
255     }
256 
257     @Test
testCopyDocument()258     public void testCopyDocument() throws Exception {
259         doReturn(DOC_RESULT).when(mProvider).copyDocument(DOC_RED, DOC_GREEN);
260         assertEquals(URI_RESULT, copyDocument(mResolver, URI_RED, URI_GREEN));
261     }
262 
263     @Test
testMoveDocument()264     public void testMoveDocument() throws Exception {
265         doReturn(DOC_RESULT).when(mProvider).moveDocument(DOC_RED, DOC_GREEN, DOC_BLUE);
266         assertEquals(URI_RESULT, moveDocument(mResolver, URI_RED, URI_GREEN, URI_BLUE));
267     }
268 
269     @Test
testIsChildDocument()270     public void testIsChildDocument() throws Exception {
271         doReturn(true).when(mProvider).isChildDocument(DOC_RED, DOC_GREEN);
272         assertEquals(true, isChildDocument(mResolver, URI_RED, URI_GREEN));
273     }
274 
275     @Test
testIsChildDocument_Failure()276     public void testIsChildDocument_Failure() throws Exception {
277         doReturn(false).when(mProvider).isChildDocument(DOC_RED, DOC_GREEN);
278         assertEquals(false, isChildDocument(mResolver, URI_RED, URI_GREEN));
279     }
280 
281     @Test
testRemoveDocument()282     public void testRemoveDocument() throws Exception {
283         doNothing().when(mProvider).removeDocument(DOC_RED, DOC_GREEN);
284         assertEquals(true, removeDocument(mResolver, URI_RED, URI_GREEN));
285     }
286 
287     @Test
testRemoveDocument_Failure()288     public void testRemoveDocument_Failure() throws Exception {
289         doThrow(new RuntimeException()).when(mProvider).removeDocument(DOC_RED, DOC_GREEN);
290         try {
291             removeDocument(mResolver, URI_RED, URI_GREEN);
292             fail();
293         } catch (RuntimeException expected) {
294         }
295     }
296 
297     @Test
testEjectRoot()298     public void testEjectRoot() throws Exception {
299         doNothing().when(mProvider).ejectRoot("r00t");
300         ejectRoot(mResolver, buildRootUri(AUTHORITY, "r00t"));
301     }
302 
303     @Test
testEjectRoot_Failure()304     public void testEjectRoot_Failure() throws Exception {
305         doThrow(new RuntimeException()).when(mProvider).ejectRoot("r00t");
306         ejectRoot(mResolver, buildRootUri(AUTHORITY, "r00t"));
307     }
308 
309     @Test
testFindDocumentPath()310     public void testFindDocumentPath() throws Exception {
311         final Path res = new Path(null, Arrays.asList("red", "blue"));
312 
313         doReturn(true).when(mProvider).isChildDocument(DOC_RED, DOC_BLUE);
314         doReturn(res).when(mProvider).findDocumentPath(DOC_RED, DOC_BLUE);
315         assertEquals(res, findDocumentPath(mResolver,
316                 buildDocumentUriUsingTree(buildTreeDocumentUri(AUTHORITY, DOC_RED), DOC_BLUE)));
317     }
318 
319     @Test
testCreateWebLinkIntent()320     public void testCreateWebLinkIntent() throws Exception {
321         final IntentSender res = PendingIntent
322                 .getActivity(InstrumentationRegistry.getTargetContext(), 0,
323                         new Intent(Intent.ACTION_VIEW), 0)
324                 .getIntentSender();
325 
326         doReturn(res).when(mProvider).createWebLinkIntent(DOC_RED, Bundle.EMPTY);
327         assertEquals(res, createWebLinkIntent(mResolver, URI_RED, Bundle.EMPTY));
328     }
329 
330     @Test
testGetDocumentMetadata()331     public void testGetDocumentMetadata() throws Exception {
332         doReturn(Bundle.EMPTY).when(mProvider).getDocumentMetadata(DOC_RED);
333         assertEquals(Bundle.EMPTY, getDocumentMetadata(mResolver, URI_RED));
334     }
335 
336     @Test
testGetDocumentStreamTypes()337     public void testGetDocumentStreamTypes() throws Exception {
338         final String[] res = new String[] { MIME_TYPE };
339 
340         doReturn(res).when(mProvider).getDocumentStreamTypes(DOC_RED, MIME_TYPE);
341         assertArrayEquals(res, mResolver.getStreamTypes(URI_RED, MIME_TYPE));
342     }
343 
344     @Test
testGetDocumentType()345     public void testGetDocumentType() throws Exception {
346         doReturn(MIME_TYPE).when(mProvider).getDocumentType(DOC_RED);
347         assertEquals(MIME_TYPE, mResolver.getType(URI_RED));
348     }
349 
350     @Test
testOpenDocument()351     public void testOpenDocument() throws Exception {
352         final ParcelFileDescriptor res = ParcelFileDescriptor.open(new File("/dev/null"),
353                 ParcelFileDescriptor.MODE_READ_ONLY);
354 
355         doReturn(res).when(mProvider).openDocument(DOC_RED, "r", null);
356         assertEquals(res, mResolver.openFile(URI_RED, "r", null));
357     }
358 
359     @Test
testOpenDocumentThumbnail()360     public void testOpenDocumentThumbnail() throws Exception {
361         final AssetFileDescriptor res = new AssetFileDescriptor(
362                 ParcelFileDescriptor.open(new File("/dev/null"),
363                         ParcelFileDescriptor.MODE_READ_ONLY),
364                 0, AssetFileDescriptor.UNKNOWN_LENGTH);
365 
366         final Point size = new Point(32, 32);
367         final Bundle opts = new Bundle();
368         opts.putParcelable(ContentResolver.EXTRA_SIZE, size);
369 
370         doReturn(res).when(mProvider).openDocumentThumbnail(DOC_RED, size, null);
371         assertEquals(res, mResolver.openTypedAssetFile(URI_RED, MIME_TYPE, opts, null));
372     }
373 
374     @Test
testOpenTypedDocument()375     public void testOpenTypedDocument() throws Exception {
376         final AssetFileDescriptor res = new AssetFileDescriptor(
377                 ParcelFileDescriptor.open(new File("/dev/null"),
378                         ParcelFileDescriptor.MODE_READ_ONLY),
379                 0, AssetFileDescriptor.UNKNOWN_LENGTH);
380 
381         doReturn("image/png").when(mProvider).getDocumentType(DOC_RED);
382         doReturn(res).when(mProvider).openTypedDocument(DOC_RED, "audio/*", null, null);
383         assertEquals(res, mResolver.openTypedAssetFile(URI_RED, "audio/*", null, null));
384     }
385 
386     @Test
testQueryDocument()387     public void testQueryDocument() throws Exception {
388         final Cursor res = new MatrixCursor(new String[0]);
389 
390         doReturn(res).when(mProvider).queryDocument(DOC_RED, null);
391         assertEquals(res, mResolver.query(URI_RED, null, null, null));
392     }
393 
394     @Test
testQueryRoots()395     public void testQueryRoots() throws Exception {
396         final Cursor res = new MatrixCursor(new String[0]);
397 
398         doReturn(res).when(mProvider).queryRoots(null);
399         assertEquals(res, mResolver.query(buildRootsUri(AUTHORITY), null, null, null));
400     }
401 
402     @Test
testQueryChildDocuments()403     public void testQueryChildDocuments() throws Exception {
404         final Cursor res = new MatrixCursor(new String[0]);
405 
406         doReturn(res).when(mProvider).queryChildDocuments(DOC_RED, null, Bundle.EMPTY);
407         assertEquals(res, mResolver.query(buildChildDocumentsUri(AUTHORITY, DOC_RED), null,
408                 Bundle.EMPTY, null));
409     }
410 
411     @Test
testQueryRecentDocuments()412     public void testQueryRecentDocuments() throws Exception {
413         final Cursor res = new MatrixCursor(new String[0]);
414 
415         doReturn(res).when(mProvider).queryRecentDocuments(DOC_RED, null, Bundle.EMPTY, null);
416         assertEquals(res, mResolver.query(buildRecentDocumentsUri(AUTHORITY, DOC_RED), null,
417                 Bundle.EMPTY, null));
418     }
419 
420     @Test
testQuerySearchDocuments()421     public void testQuerySearchDocuments() throws Exception {
422         final Cursor res = new MatrixCursor(new String[0]);
423 
424         doReturn(res).when(mProvider).querySearchDocuments(DOC_RED, null, Bundle.EMPTY);
425         assertEquals(res, mResolver.query(buildSearchDocumentsUri(AUTHORITY, DOC_RED, "moo"), null,
426                 Bundle.EMPTY, null));
427     }
428 
429     @Test
testGetDocumentThumbnail()430     public void testGetDocumentThumbnail() throws Exception {
431         // create file and image
432         final String testImagePath =
433                 InstrumentationRegistry.getTargetContext().getExternalCacheDir().getPath()
434                         + "/testimage.jpg";
435         final int imageSize = 128;
436         final int thumbnailSize = 32;
437         File file = new File(testImagePath);
438         try (FileOutputStream out = new FileOutputStream(file)) {
439             writeImage(imageSize, imageSize, Color.RED, out);
440         }
441 
442         final AssetFileDescriptor res = new AssetFileDescriptor(
443                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
444                 0, AssetFileDescriptor.UNKNOWN_LENGTH);
445         final Point size = new Point(thumbnailSize, thumbnailSize);
446         final Bundle opts = new Bundle();
447         opts.putParcelable(ContentResolver.EXTRA_SIZE, size);
448 
449         doReturn(res).when(mProvider).openDocumentThumbnail(DOC_RED, size, null);
450         Bitmap bitmap = DocumentsContract.getDocumentThumbnail(mResolver, URI_RED, size, null);
451 
452         // A provider may return a thumbnail of a different size, but never more than double the
453         // requested size.
454         assertFalse(bitmap.getWidth() > thumbnailSize * 2);
455         assertFalse(bitmap.getHeight() > thumbnailSize * 2);
456         assertColorMostlyEquals(Color.RED,
457                 bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 2));
458 
459         // clean up
460         file.delete();
461         bitmap.recycle();
462     }
463 
writeImage(int width, int height, int color, OutputStream out)464     private static void writeImage(int width, int height, int color, OutputStream out) {
465         final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
466         final Canvas canvas = new Canvas(bitmap);
467         canvas.drawColor(color);
468         bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
469     }
470 
471     /**
472      * Since thumbnails might be bounced through a compression pass, we're okay
473      * if they're mostly equal.
474      */
assertColorMostlyEquals(int expected, int actual)475     private static void assertColorMostlyEquals(int expected, int actual) {
476         assertEquals(Integer.toHexString(expected & 0xF0F0F0F0),
477                 Integer.toHexString(actual & 0xF0F0F0F0));
478     }
479 }
480