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