浏览代码

Merge pull request #3905 from jmue/storageprovider

enhance DocumentsProvider
Andy Scherzinger 5 年之前
父节点
当前提交
75dd657ba8

+ 8 - 0
src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -62,6 +62,7 @@ import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
+import android.provider.DocumentsContract;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -1768,6 +1769,13 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             setAccountAuthenticatorResult(intent.getExtras());
             setResult(RESULT_OK, intent);
 
+            // notify Document Provider
+            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                String authority = getResources().getString(R.string.document_provider_authority);
+                Uri rootsUri = DocumentsContract.buildRootsUri(authority);
+                getContentResolver().notifyChange(rootsUri, null);
+            }
+
             return true;
         }
     }

+ 10 - 0
src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java

@@ -30,6 +30,9 @@ import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
 import android.text.TextUtils;
 
 import com.evernote.android.job.Job;
@@ -142,6 +145,13 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
                 new RemoteWipeSuccessRemoteOperation(authToken).execute(client);
             }
 
+            // notify Document Provider
+            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                String authority = context.getResources().getString(R.string.document_provider_authority);
+                Uri rootsUri = DocumentsContract.buildRootsUri(authority);
+                context.getContentResolver().notifyChange(rootsUri, null);
+            }
+
             return Result.SUCCESS;
         } else {
             return Result.FAILURE;

+ 367 - 213
src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -34,17 +34,19 @@ import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.util.Log;
+import android.util.SparseArray;
 import android.widget.Toast;
 
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.Device;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -55,12 +57,12 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.files.services.FileDownloader;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.MoveFileOperation;
@@ -68,7 +70,6 @@ import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.ui.activity.SettingsActivity;
 import com.owncloud.android.utils.FileStorageUtils;
@@ -81,9 +82,11 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
@@ -91,16 +94,25 @@ import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 @TargetApi(Build.VERSION_CODES.KITKAT)
 public class DocumentsStorageProvider extends DocumentsProvider {
 
-    private static final String TAG = "DocumentsStorageProvider";
+    private static final String TAG = DocumentsStorageProvider.class.getSimpleName();
 
-    private FileDataStorageManager currentStorageManager;
-    private Map<Long, FileDataStorageManager> rootIdToStorageManager;
-    private OwnCloudClient client;
+    private static final long CACHE_EXPIRATION = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
 
     UserAccountManager accountManager;
 
+    private static final String DOCUMENTID_SEPARATOR = "/";
+    private static final int DOCUMENTID_PARTS = 2;
+    private final SparseArray<FileDataStorageManager> rootIdToStorageManager = new SparseArray<>();
+
+    private final Executor executor = Executors.newCachedThreadPool();
+
     @Override
-    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+    public Cursor queryRoots(String[] projection) {
+
+        // always recreate storage manager collection, as it will change after account creation/removal
+        // and we need to serve document(tree)s with persist permissions
+        initiateStorageMap();
+
         Context context = MainApp.getAppContext();
         AppPreferences preferences = AppPreferencesImpl.fromContext(context);
         if (SettingsActivity.LOCK_PASSCODE.equals(preferences.getLockPreference()) ||
@@ -108,12 +120,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             return new FileCursor();
         }
 
-        initiateStorageMap();
-
         final RootCursor result = new RootCursor(projection);
-
-        for (Account account : accountManager.getAccounts()) {
-            result.addRoot(account, getContext());
+        for(int i = 0; i < rootIdToStorageManager.size(); i++) {
+            result.addRoot(new Document(rootIdToStorageManager.valueAt(i), ROOT_PATH), getContext());
         }
 
         return result;
@@ -121,28 +130,12 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
     @Override
     public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
-        final long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
-
-        if (currentStorageManager == null) {
+        Log.d(TAG, "queryDocument(), id=" + documentId);
 
-            for (Map.Entry<Long, FileDataStorageManager> entry : rootIdToStorageManager.entrySet()) {
-                if (entry.getValue().getFileById(docId) != null) {
-                    currentStorageManager = entry.getValue();
-                    break;
-                }
-            }
-        }
-
-        if (currentStorageManager == null) {
-            throw new FileNotFoundException("File with id " + documentId + " not found");
-        }
+        Document document = toDocument(documentId);
 
         final FileCursor result = new FileCursor(projection);
-        OCFile file = currentStorageManager.getFileById(docId);
-        if (file != null) {
-            result.addFile(file);
-        }
+        result.addFile(document);
 
         return result;
     }
@@ -151,34 +144,37 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
         throws FileNotFoundException {
-
-        final long folderId = Long.parseLong(parentDocumentId);
-        updateCurrentStorageManagerIfNeeded(folderId);
+        Log.d(TAG, "queryChildDocuments(), id=" + parentDocumentId);
 
         Context context = getContext();
-
         if (context == null) {
             throw new FileNotFoundException("Context may not be null");
         }
 
-        Account account = currentStorageManager.getAccount();
-        final OCFile browsedDir = currentStorageManager.getFileById(folderId);
-        if (Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
-            RemoteOperationResult result = new RefreshFolderOperation(browsedDir, System.currentTimeMillis(), false,
-                                                                      false, true, currentStorageManager, account,
-                                                                      getContext()).execute(client);
+        Document parentFolder = toDocument(parentDocumentId);
 
-            if (!result.isSuccess()) {
-                throw new FileNotFoundException("Failed to update document " + parentDocumentId);
-            }
-        }
+        FileDataStorageManager storageManager = parentFolder.getStorageManager();
 
         final FileCursor resultCursor = new FileCursor(projection);
 
-        for (OCFile file : currentStorageManager.getFolderContent(browsedDir, false)) {
-            resultCursor.addFile(file);
+        for (OCFile file : storageManager.getFolderContent(parentFolder.getFile(), false)) {
+            resultCursor.addFile(new Document(storageManager, file));
         }
 
+        boolean isLoading = false;
+        if (parentFolder.isExpired()) {
+            final ReloadFolderDocumentTask task = new ReloadFolderDocumentTask(parentFolder, result -> {
+                getContext().getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false);
+            });
+            task.executeOnExecutor(executor);
+            resultCursor.setLoadingTask(task);
+            isLoading = true;
+        }
+
+        final Bundle extra = new Bundle();
+        extra.putBoolean(DocumentsContract.EXTRA_LOADING, isLoading);
+        resultCursor.setExtras(extra);
+        resultCursor.setNotificationUri(getContext().getContentResolver(), toNotifyUri(parentFolder));
         return resultCursor;
     }
 
@@ -186,22 +182,18 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal cancellationSignal)
             throws FileNotFoundException {
-        final long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "openDocument(), id=" + documentId);
 
-        OCFile ocFile = currentStorageManager.getFileById(docId);
-
-        if (ocFile == null) {
-            throw new FileNotFoundException("File not found: " + documentId);
-        }
+        Document document = toDocument(documentId);
 
         Context context = getContext();
-
         if (context == null) {
             throw new FileNotFoundException("Context may not be null!");
         }
 
-        Account account = currentStorageManager.getAccount();
+        OCFile ocFile = document.getFile();
+        Account account = document.getAccount();
+
         if (!ocFile.isDown()) {
             Intent i = new Intent(getContext(), FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
@@ -216,7 +208,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                 if (!waitOrGetCancelled(cancellationSignal)) {
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
                 }
-                ocFile = currentStorageManager.getFileById(docId);
+                ocFile = document.getFile();
 
                 if (ocFile == null) {
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
@@ -226,11 +218,10 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             OCFile finalFile = ocFile;
             Thread syncThread = new Thread(() -> {
                 try {
-                    FileDataStorageManager storageManager =
-                            new FileDataStorageManager(account, context.getContentResolver());
-                    SynchronizeFileOperation sfo =
-                            new SynchronizeFileOperation(finalFile, null, account, true, context);
-                    RemoteOperationResult result = sfo.execute(storageManager, context);
+                    FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
+                    RemoteOperationResult result = new SynchronizeFileOperation(finalFile, null, account,
+                                                                                true, context)
+                        .execute(storageManager, context);
                     if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
                         // ISSUE 5: if the user is not running the app (this is a service!),
                         // this can be very intrusive; a notification should be preferred
@@ -271,7 +262,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                 return ParcelFileDescriptor.open(file, accessMode, handler, l -> {
                     RemoteOperationResult result = new SynchronizeFileOperation(newFile, oldFile, account, true,
                                                                                 context)
-                        .execute(client, currentStorageManager);
+                        .execute(document.getClient(), document.getStorageManager());
 
                     boolean success = result.isSuccess();
 
@@ -297,6 +288,11 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     public boolean onCreate() {
         accountManager = UserAccountManagerImpl.fromContext(getContext());
+
+        // initiate storage manager collection, because we need to serve document(tree)s
+        // with persist permissions
+        initiateStorageMap();
+
         return true;
     }
 
@@ -305,141 +301,140 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                                                      Point sizeHint,
                                                      CancellationSignal signal)
             throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "openDocumentThumbnail(), id=" + documentId);
 
-        OCFile file = currentStorageManager.getFileById(docId);
-
-        if (file == null) {
-            throw new FileNotFoundException("File with id " + documentId + " not found!");
-        }
+        Document document = toDocument(documentId);
 
         Context context = getContext();
-
         if (context == null) {
             throw new FileNotFoundException("Context may not be null!");
         }
 
         boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL
-                                                                   + file.getRemoteId());
+                                                                   + document.getFile().getRemoteId());
 
         if (!exists) {
-            ThumbnailsCacheManager.generateThumbnailFromOCFile(file);
+            ThumbnailsCacheManager.generateThumbnailFromOCFile(document.getFile());
         }
 
         Uri uri = Uri.parse(UriUtils.URI_CONTENT_SCHEME + context.getResources().getString(
-            R.string.image_cache_provider_authority) + file.getRemotePath());
+            R.string.image_cache_provider_authority) + document.getRemotePath());
+        Log.d(TAG, "open thumbnail, uri=" + uri);
         return context.getContentResolver().openAssetFileDescriptor(uri, "r");
     }
 
     @Override
     public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "renameDocument(), id=" + documentId);
 
-        OCFile file = currentStorageManager.getFileById(docId);
-
-        if (file == null) {
-            throw new FileNotFoundException("File " + documentId + " not found!");
+        Context context = getContext();
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
         }
 
-        RemoteOperationResult result = new RenameFileOperation(file.getRemotePath(), displayName)
-            .execute(client, currentStorageManager);
+        Document document = toDocument(documentId);
+
+        RemoteOperationResult result = new RenameFileOperation(document.getRemotePath(), displayName)
+            .execute(document.getClient(), document.getStorageManager());
 
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to rename document with documentId " + documentId + ": " +
                                                 result.getException());
         }
 
+        context.getContentResolver().notifyChange(toNotifyUri(document.getParent()), null, false);
+
         return null;
     }
 
     @Override
     public String copyDocument(String sourceDocumentId, String targetParentDocumentId) throws FileNotFoundException {
-        long sourceId = Long.parseLong(sourceDocumentId);
-
-        updateCurrentStorageManagerIfNeeded(sourceId);
+        Log.d(TAG, "copyDocument(), id=" + sourceDocumentId);
 
-        OCFile file = currentStorageManager.getFileById(sourceId);
-        if (file == null) {
-            throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
+        Context context = getContext();
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
         }
 
-        long targetId = Long.parseLong(targetParentDocumentId);
-        OCFile targetFolder = currentStorageManager.getFileById(targetId);
-        if (targetFolder == null) {
-            throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
-        }
+        Document document = toDocument(sourceDocumentId);
+
+        FileDataStorageManager storageManager = document.getStorageManager();
+        Document targetFolder = toDocument(targetParentDocumentId);
 
-        RemoteOperationResult result = new CopyFileOperation(file.getRemotePath(), targetFolder.getRemotePath())
-            .execute(client, currentStorageManager);
+        RemoteOperationResult result = new CopyFileOperation(document.getRemotePath(), targetFolder.getRemotePath())
+            .execute(document.getClient(), storageManager);
 
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
         }
 
-        Account account = currentStorageManager.getAccount();
+        Account account = document.getAccount();
 
-        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder, System.currentTimeMillis(),
-                                                                        false, false, true, currentStorageManager,
-                                                                        account, getContext()).execute(client);
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
+                                                                        false, false, true, storageManager,
+                                                                        account, context)
+            .execute(targetFolder.getClient());
 
         if (!updateParent.isSuccess()) {
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
         }
 
-        String newPath = targetFolder.getRemotePath() + file.getFileName();
+        String newPath = targetFolder.getRemotePath() + document.getFile().getFileName();
 
-        if (file.isFolder()) {
+        if (document.getFile().isFolder()) {
             newPath = newPath + PATH_SEPARATOR;
         }
-        OCFile newFile = currentStorageManager.getFileByPath(newPath);
+        Document newFile = new Document(storageManager, newPath);
+
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
 
-        return String.valueOf(newFile.getFileId());
+        return newFile.getDocumentId();
     }
 
     @Override
     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
         throws FileNotFoundException {
-        long sourceId = Long.parseLong(sourceDocumentId);
-
-        updateCurrentStorageManagerIfNeeded(sourceId);
+        Log.d(TAG, "moveDocument(), id=" + sourceDocumentId);
 
-        OCFile file = currentStorageManager.getFileById(sourceId);
-
-        if (file == null) {
-            throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
+        Context context = getContext();
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
         }
 
-        long targetId = Long.parseLong(targetParentDocumentId);
-        OCFile targetFolder = currentStorageManager.getFileById(targetId);
+        Document document = toDocument(sourceDocumentId);
+        Document targetFolder = toDocument(targetParentDocumentId);
 
-        if (targetFolder == null) {
-            throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
-        }
-
-        RemoteOperationResult result = new MoveFileOperation(file.getRemotePath(), targetFolder.getRemotePath())
-            .execute(client, currentStorageManager);
+        RemoteOperationResult result = new MoveFileOperation(document.getRemotePath(), targetFolder.getRemotePath())
+            .execute(document.getClient(), document.getStorageManager());
 
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to move document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
         }
 
-        return String.valueOf(file.getFileId());
+        Document sourceFolder = toDocument(sourceParentDocumentId);
+
+        getContext().getContentResolver().notifyChange(toNotifyUri(sourceFolder), null, false);
+        getContext().getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
+
+        return sourceDocumentId;
     }
 
     @Override
     public Cursor querySearchDocuments(String rootId, String query, String[] projection) {
-        updateCurrentStorageManagerIfNeeded(rootId);
+        Log.d(TAG, "querySearchDocuments(), rootId=" + rootId);
 
-        OCFile root = currentStorageManager.getFileByPath(ROOT_PATH);
         FileCursor result = new FileCursor(projection);
 
-        for (OCFile f : findFiles(root, query)) {
-            result.addFile(f);
+        FileDataStorageManager storageManager = getStorageManager(rootId);
+        if (storageManager == null) {
+            return result;
+        }
+
+        for (Document d : findFiles(new Document(storageManager, ROOT_PATH), query)) {
+            result.addFile(d);
         }
 
         return result;
@@ -447,53 +442,73 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
     @Override
     public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "createDocument(), id=" + documentId);
 
-        OCFile parent = currentStorageManager.getFileById(docId);
-
-        if (parent == null) {
-            throw new FileNotFoundException("Parent file not found");
-        }
+        Document folderDocument = toDocument(documentId);
 
-        if ("vnd.android.document/directory".equalsIgnoreCase(mimeType)) {
-            return createFolder(parent, displayName, documentId);
+        if (DocumentsContract.Document.MIME_TYPE_DIR.equalsIgnoreCase(mimeType)) {
+            return createFolder(folderDocument, displayName);
         } else {
-            return createFile(parent, displayName, documentId);
+            return createFile(folderDocument, displayName);
         }
     }
 
-    private String createFolder(OCFile parent, String displayName, String documentId) throws FileNotFoundException {
+    private String createFolder(Document targetFolder, String displayName) throws FileNotFoundException {
 
-        CreateFolderOperation createFolderOperation = new CreateFolderOperation(parent.getRemotePath() + displayName
-                                                                                    + PATH_SEPARATOR, true);
-        RemoteOperationResult result = createFolderOperation.execute(client, currentStorageManager);
+        Context context = getContext();
+
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
 
+        String newDirPath = targetFolder.getRemotePath() + displayName + PATH_SEPARATOR;
+        FileDataStorageManager storageManager = targetFolder.getStorageManager();
+
+        RemoteOperationResult result = new CreateFolderOperation(newDirPath, true)
+            .execute(targetFolder.getClient(), storageManager);
 
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to create document with name " +
-                                                displayName + " and documentId " + documentId);
+                                                displayName + " and documentId " + targetFolder.getDocumentId());
+        }
+
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
+                                                                        false, false, true, storageManager,
+                                                                        targetFolder.getAccount(), context)
+            .execute(targetFolder.getClient());
+
+        if (!updateParent.isSuccess()) {
+            throw new FileNotFoundException("Failed to create document with documentId " + targetFolder.getDocumentId());
         }
 
+        Document newFolder = new Document(storageManager, newDirPath);
 
-        String newDirPath = parent.getRemotePath() + displayName + PATH_SEPARATOR;
-        OCFile newFolder = currentStorageManager.getFileByPath(newDirPath);
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
 
-        return String.valueOf(newFolder.getFileId());
+        return newFolder.getDocumentId();
     }
 
-    private String createFile(OCFile parent, String displayName, String documentId) throws FileNotFoundException {
+    private String createFile(Document targetFolder, String displayName) throws FileNotFoundException {
         Context context = getContext();
-
         if (context == null) {
             throw new FileNotFoundException("Context may not be null!");
         }
 
-        Account account = currentStorageManager.getAccount();
+        Account account = targetFolder.getAccount();
 
         // create dummy file
         File tempDir = new File(FileStorageUtils.getTemporalPath(account.name));
+
+        if (!tempDir.exists() && !tempDir.mkdirs()) {
+            throw new FileNotFoundException("Temp folder could not be created: " + tempDir.getAbsolutePath());
+        }
+
         File emptyFile = new File(tempDir, displayName);
+
+        if (emptyFile.exists() && !emptyFile.delete()) {
+            throw new FileNotFoundException("Previous file could not be deleted");
+        }
+
         try {
             if (!emptyFile.createNewFile()) {
                 throw new FileNotFoundException("File could not be created");
@@ -502,30 +517,40 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             throw new FileNotFoundException("File could not be created");
         }
 
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(getContext(), account, new String[]{emptyFile.getAbsolutePath()},
-                                new String[]{parent.getRemotePath() + displayName}, null,
-                                FileUploader.LOCAL_BEHAVIOUR_MOVE, true, UploadFileOperation.CREATED_BY_USER, false,
-                                false);
+        String newFilePath = targetFolder.getRemotePath() + displayName;
 
-        try {
-            Thread.sleep(2000);
-        } catch (InterruptedException e) {
-            Log_OC.e(TAG, "Thread interruption error");
+        // perform the upload, no need for chunked operation as we have a empty file
+        OwnCloudClient client = targetFolder.getClient();
+        RemoteOperationResult result = new UploadFileRemoteOperation(emptyFile.getAbsolutePath(),
+                                                                     newFilePath,
+                                                                     null,
+                                                                     "",
+                                                                     String.valueOf(System.currentTimeMillis() / 1000))
+            .execute(client);
+
+        if (!result.isSuccess()) {
+            throw new FileNotFoundException("Failed to upload document with path " + newFilePath);
         }
 
-        RemoteOperationResult updateParent = new RefreshFolderOperation(parent, System.currentTimeMillis(),
-                                                                        false, false, true, currentStorageManager,
-                                                                        account, getContext()).execute(client);
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(),
+                                                                        System.currentTimeMillis(),
+                                                                        false,
+                                                                        false,
+                                                                        true,
+                                                                        targetFolder.getStorageManager(),
+                                                                        account,
+                                                                        context)
+            .execute(client);
 
         if (!updateParent.isSuccess()) {
-            throw new FileNotFoundException("Failed to create document with documentId " + documentId);
+            throw new FileNotFoundException("Failed to create document with documentId " + targetFolder.getDocumentId());
         }
 
-        String newFilePath = parent.getRemotePath() + displayName;
-        OCFile newFile = currentStorageManager.getFileByPath(newFilePath);
+        Document newFile = new Document(targetFolder.getStorageManager(), newFilePath);
+
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
 
-        return String.valueOf(newFile.getFileId());
+        return newFile.getDocumentId();
     }
 
     @Override
@@ -535,76 +560,84 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
     @Override
     public void deleteDocument(String documentId) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "deleteDocument(), id=" + documentId);
 
-        OCFile file = currentStorageManager.getFileById(docId);
-
-        if (file == null) {
-            throw new FileNotFoundException("File " + documentId + " not found!");
+        Context context = getContext();
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
         }
-        Account account = currentStorageManager.getAccount();
 
-        RemoveFileOperation removeFileOperation = new RemoveFileOperation(file.getRemotePath(), false, account, true,
-                                                                          getContext());
+        Document document = toDocument(documentId);
+
+        recursiveRevokePermission(document);
 
-        RemoteOperationResult result = removeFileOperation.execute(client, currentStorageManager);
+        RemoteOperationResult result = new RemoveFileOperation(document.getRemotePath(), false,
+                                                               document.getAccount(), true, context)
+            .execute(document.getClient(), document.getStorageManager());
 
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to delete document with documentId " + documentId);
         }
+
+        Document parentFolder = document.getParent();
+        context.getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false);
     }
 
-    @SuppressLint("LongLogTag")
-    private void updateCurrentStorageManagerIfNeeded(long docId) {
-        if (rootIdToStorageManager == null) {
-            try {
-                queryRoots(FileCursor.DEFAULT_DOCUMENT_PROJECTION);
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to query roots");
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void recursiveRevokePermission(Document document) {
+        FileDataStorageManager storageManager = document.getStorageManager();
+        OCFile file = document.getFile();
+        if (file.isFolder()) {
+            for (OCFile child : storageManager.getFolderContent(file, false)) {
+                recursiveRevokePermission(new Document(storageManager, child));
             }
         }
 
-        if (currentStorageManager == null ||
-            rootIdToStorageManager.containsKey(docId) && currentStorageManager != rootIdToStorageManager.get(docId)) {
-            currentStorageManager = rootIdToStorageManager.get(docId);
-        }
+        revokeDocumentPermission(document.getDocumentId());
+    }
+
+    @Override
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        Log.d(TAG, "isChildDocument(), parent=" + parentDocumentId + ", id=" + documentId);
 
         try {
-            Account account = currentStorageManager.getAccount();
-            OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext());
-            client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
-        } catch (OperationCanceledException | IOException | AuthenticatorException |
-            com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
-            Log_OC.e(TAG, "Failed to set client", e);
+            Document currentDocument = toDocument(documentId);
+            Document parentDocument = toDocument(parentDocumentId);
+
+            while (!ROOT_PATH.equals(currentDocument.getRemotePath())) {
+                currentDocument = currentDocument.getParent();
+                if (parentDocument.getFile().equals(currentDocument.getFile())) {
+                    return true;
+                }
+            }
+
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "failed to check for child document", e);
         }
+
+        return false;
     }
 
-    private void updateCurrentStorageManagerIfNeeded(String rootId) {
-        for (FileDataStorageManager data : rootIdToStorageManager.values()) {
-            if (data.getAccount().name.equals(rootId)) {
-                currentStorageManager = data;
+    private FileDataStorageManager getStorageManager(String rootId) {
+        for(int i = 0; i < rootIdToStorageManager.size(); i++) {
+            FileDataStorageManager storageManager = rootIdToStorageManager.valueAt(i);
+            if (storageManager.getAccount().name.equals(rootId)) {
+                return storageManager;
             }
         }
-    }
 
-    @SuppressLint("UseSparseArrays")
-    private void initiateStorageMap() throws FileNotFoundException {
+        return null;
+    }
 
-        Context context = getContext();
+    private void initiateStorageMap() {
 
-        final Account[] allAccounts = accountManager.getAccounts();
-        rootIdToStorageManager = new HashMap<>(allAccounts.length);
+        rootIdToStorageManager.clear();
 
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
+        ContentResolver contentResolver = getContext().getContentResolver();
 
-        final ContentResolver contentResolver = context.getContentResolver();
-        for (Account account : allAccounts) {
+        for (Account account : accountManager.getAccounts()) {
             final FileDataStorageManager storageManager = new FileDataStorageManager(account, contentResolver);
-            final OCFile rootDir = storageManager.getFileByPath(ROOT_PATH);
-            rootIdToStorageManager.put(rootDir.getFileId(), storageManager);
+            rootIdToStorageManager.put(account.hashCode(), storageManager);
         }
     }
 
@@ -618,15 +651,136 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         return !(cancellationSignal != null && cancellationSignal.isCanceled());
     }
 
-    List<OCFile> findFiles(OCFile root, String query) {
-        List<OCFile> result = new ArrayList<>();
-        for (OCFile f : currentStorageManager.getFolderContent(root, false)) {
+    private List<Document> findFiles(Document root, String query) {
+        FileDataStorageManager storageManager = root.getStorageManager();
+        List<Document> result = new ArrayList<>();
+        for (OCFile f : storageManager.getFolderContent(root.getFile(), false)) {
             if (f.isFolder()) {
-                result.addAll(findFiles(f, query));
+                result.addAll(findFiles(new Document(storageManager, f), query));
             } else if (f.getFileName().contains(query)) {
-                result.add(f);
+                result.add(new Document(storageManager, f));
             }
         }
         return result;
     }
+
+    private Uri toNotifyUri(Document document) {
+        return DocumentsContract.buildDocumentUri(
+            getContext().getString(R.string.document_provider_authority),
+            document.getDocumentId());
+    }
+
+    private Document toDocument(String documentId) throws FileNotFoundException {
+        String[] separated = documentId.split(DOCUMENTID_SEPARATOR, DOCUMENTID_PARTS);
+        if (separated.length != DOCUMENTID_PARTS) {
+            throw new FileNotFoundException("Invalid documentID " + documentId + "!");
+        }
+
+        FileDataStorageManager storageManager = rootIdToStorageManager.get(Integer.parseInt(separated[0]));
+        if (storageManager == null) {
+            throw new FileNotFoundException("No storage manager associated for " + documentId + "!");
+        }
+
+        return new Document(storageManager, Long.parseLong(separated[1]));
+    }
+
+    public interface OnTaskFinishedCallback {
+        void onTaskFinished(RemoteOperationResult result);
+    }
+
+    static class ReloadFolderDocumentTask extends AsyncTask<Void, Void, RemoteOperationResult> {
+
+        private final Document folder;
+        private final OnTaskFinishedCallback callback;
+
+        ReloadFolderDocumentTask(Document folder, OnTaskFinishedCallback callback) {
+            this.folder = folder;
+            this.callback = callback;
+        }
+
+        @Override
+        public final RemoteOperationResult doInBackground(Void... params) {
+            Log.d(TAG, "run ReloadFolderDocumentTask(), id=" + folder.getDocumentId());
+            return new RefreshFolderOperation(folder.getFile(), System.currentTimeMillis(), false,
+                                              false, true, folder.getStorageManager(), folder.getAccount(),
+                                              MainApp.getAppContext())
+                .execute(folder.getClient());
+        }
+
+        @Override
+        public final void onPostExecute(RemoteOperationResult result) {
+            if (callback != null) {
+                callback.onTaskFinished(result);
+            }
+        }
+    }
+
+    public class Document {
+        private final FileDataStorageManager storageManager;
+        private final long fileId;
+
+        Document(FileDataStorageManager storageManager, long fileId) {
+            this.storageManager = storageManager;
+            this.fileId = fileId;
+        }
+
+        Document(FileDataStorageManager storageManager, OCFile file) {
+            this.storageManager = storageManager;
+            this.fileId = file.getFileId();
+        }
+
+        Document(FileDataStorageManager storageManager, String filePath) {
+            this.storageManager = storageManager;
+            this.fileId = storageManager.getFileByPath(filePath).getFileId();
+        }
+
+        public String getDocumentId() {
+            for(int i = 0; i < rootIdToStorageManager.size(); i++) {
+                if (Objects.equals(storageManager, rootIdToStorageManager.valueAt(i))) {
+                    return rootIdToStorageManager.keyAt(i) + DOCUMENTID_SEPARATOR + fileId;
+                }
+            }
+            return null;
+        }
+
+        FileDataStorageManager getStorageManager() {
+            return storageManager;
+        }
+
+        public Account getAccount() {
+            return getStorageManager().getAccount();
+        }
+
+        public OCFile getFile() {
+            return getStorageManager().getFileById(fileId);
+        }
+
+        public String getRemotePath() {
+            return getFile().getRemotePath();
+        }
+
+        OwnCloudClient getClient() {
+            try {
+                OwnCloudAccount ocAccount = new OwnCloudAccount(getAccount(), MainApp.getAppContext());
+                return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
+            } catch (OperationCanceledException | IOException | AuthenticatorException |
+                com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
+                Log_OC.e(TAG, "Failed to set client", e);
+            }
+            return null;
+        }
+
+        boolean isExpired() {
+            return getFile().getLastSyncDateForData() + CACHE_EXPIRATION < System.currentTimeMillis();
+        }
+
+        Document getParent() {
+            long parentId = getFile().getParentId();
+            if (parentId <= 0) {
+                return null;
+            }
+
+            return new Document(getStorageManager(), parentId);
+        }
+    }
 }

+ 33 - 3
src/main/java/org/nextcloud/providers/cursors/FileCursor.java

@@ -22,10 +22,13 @@ package org.nextcloud.providers.cursors;
 
 import android.annotation.TargetApi;
 import android.database.MatrixCursor;
+import android.os.AsyncTask;
 import android.os.Build;
+import android.os.Bundle;
 import android.provider.DocumentsContract.Document;
 
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.providers.DocumentsStorageProvider;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 @TargetApi(Build.VERSION_CODES.KITKAT)
@@ -37,15 +40,42 @@ public class FileCursor extends MatrixCursor {
             Document.COLUMN_FLAGS, Document.COLUMN_LAST_MODIFIED
     };
 
+    private Bundle extra;
+    private AsyncTask<?, ?, ?> loadingTask;
+
     public FileCursor(String... projection) {
         super(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
     }
 
-    public void addFile(OCFile file) {
-        if (file == null) {
+    public void setLoadingTask(AsyncTask<?, ?, ?> task) {
+        this.loadingTask = task;
+    }
+
+    @Override
+    public void setExtras(Bundle extras) {
+        this.extra = extras;
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return extra;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        if (loadingTask != null && loadingTask.getStatus() != AsyncTask.Status.FINISHED) {
+            loadingTask.cancel(false);
+        }
+    }
+
+    public void addFile(DocumentsStorageProvider.Document document) {
+        if (document == null) {
             return;
         }
 
+        OCFile file = document.getFile();
+
         final int iconRes = MimeTypeUtil.getFileTypeIconId(file.getMimeType(), file.getFileName());
         final String mimeType = file.isFolder() ? Document.MIME_TYPE_DIR : file.getMimeType();
         int flags = Document.FLAG_SUPPORTS_DELETE |
@@ -64,7 +94,7 @@ public class FileCursor extends MatrixCursor {
             flags = Document.FLAG_SUPPORTS_RENAME | flags;
         }
 
-        newRow().add(Document.COLUMN_DOCUMENT_ID, Long.toString(file.getFileId()))
+        newRow().add(Document.COLUMN_DOCUMENT_ID, document.getDocumentId())
                 .add(Document.COLUMN_DISPLAY_NAME, file.getFileName())
                 .add(Document.COLUMN_LAST_MODIFIED, file.getModificationTimestamp())
                 .add(Document.COLUMN_SIZE, file.getFileLength())

+ 10 - 10
src/main/java/org/nextcloud/providers/cursors/RootCursor.java

@@ -28,10 +28,7 @@ import android.os.Build;
 import android.provider.DocumentsContract.Root;
 
 import com.owncloud.android.R;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-
-import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
+import com.owncloud.android.providers.DocumentsStorageProvider;
 
 
 @TargetApi(Build.VERSION_CODES.KITKAT)
@@ -47,16 +44,19 @@ public class RootCursor extends MatrixCursor {
         super(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
     }
 
-    public void addRoot(Account account, Context context) {
-        final FileDataStorageManager manager = new FileDataStorageManager(account, context.getContentResolver());
-        final OCFile mainDir = manager.getFileByPath(ROOT_PATH);
+    public void addRoot(DocumentsStorageProvider.Document document, Context context) {
+        Account account = document.getAccount();
+
+        int rootFlags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH;
+        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
+            rootFlags = rootFlags | Root.FLAG_SUPPORTS_IS_CHILD;
+        }
 
         newRow().add(Root.COLUMN_ROOT_ID, account.name)
-            .add(Root.COLUMN_DOCUMENT_ID, mainDir.getFileId())
+            .add(Root.COLUMN_DOCUMENT_ID, document.getDocumentId())
             .add(Root.COLUMN_SUMMARY, account.name)
             .add(Root.COLUMN_TITLE, context.getString(R.string.app_name))
             .add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
-            .add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS |
-                Root.FLAG_SUPPORTS_SEARCH);
+            .add(Root.COLUMN_FLAGS, rootFlags);
     }
 }