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