Add free space precondition check for copy job.
Bug: 24948755
Change-Id: I210395bdf339d630604e90e867ffddbbd3cf4bea
diff --git a/src/com/android/documentsui/RootsCache.java b/src/com/android/documentsui/RootsCache.java
index 88eeb49..117bb01 100644
--- a/src/com/android/documentsui/RootsCache.java
+++ b/src/com/android/documentsui/RootsCache.java
@@ -36,6 +36,7 @@
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
+import android.provider.DocumentsProvider;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -350,11 +351,20 @@
* waiting for all the other roots to come back.
*/
public RootInfo getRootOneshot(String authority, String rootId) {
+ return getRootOneshot(authority, rootId, false);
+ }
+
+ /**
+ * Return the requested {@link RootInfo}, but only loading the roots of the requested authority.
+ * It always fetches from {@link DocumentsProvider} if forceRefresh is true, which is used to
+ * get the most up-to-date free space before starting copy operations.
+ */
+ public RootInfo getRootOneshot(String authority, String rootId, boolean forceRefresh) {
synchronized (mLock) {
- RootInfo root = getRootLocked(authority, rootId);
+ RootInfo root = forceRefresh ? null : getRootLocked(authority, rootId);
if (root == null) {
- mRoots.putAll(authority,
- loadRootsForAuthority(mContext.getContentResolver(), authority, false));
+ mRoots.putAll(authority, loadRootsForAuthority(
+ mContext.getContentResolver(), authority, forceRefresh));
root = getRootLocked(authority, rootId);
}
return root;
diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java
index 390656c..54ccc2a 100644
--- a/src/com/android/documentsui/services/CopyJob.java
+++ b/src/com/android/documentsui/services/CopyJob.java
@@ -51,9 +51,11 @@
import android.util.Log;
import android.webkit.MimeTypeMap;
-import com.android.documentsui.UrisSupplier;
+import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Metrics;
import com.android.documentsui.R;
+import com.android.documentsui.RootsCache;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
@@ -210,7 +212,6 @@
@Override
boolean setUp() {
-
try {
buildDocumentList();
} catch (ResourceException e) {
@@ -218,6 +219,7 @@
return false;
}
+ // Check if user has canceled this task.
if (isCanceled()) {
return false;
}
@@ -229,7 +231,15 @@
mBatchSize = -1;
}
- return true;
+ // Check if user has canceled this task. We should check it again here as user cancels
+ // tasks in main thread, but this is running in a worker thread. calculateSize() may
+ // take a long time during which user can cancel this task, and we don't want to waste
+ // resources doing useless large chunk of work.
+ if (isCanceled()) {
+ return false;
+ }
+
+ return checkSpace();
}
@Override
@@ -286,6 +296,44 @@
return !root.isDownloads() || !doc.isDirectory();
}
+ /**
+ * Checks whether the destination folder has enough space to take all source files.
+ * @return true if the root has enough space or doesn't provide free space info; otherwise false
+ */
+ boolean checkSpace() {
+ return checkSpace(mBatchSize);
+ }
+
+ /**
+ * Checks whether the destination folder has enough space to take files of batchSize
+ * @param batchSize the total size of files
+ * @return true if the root has enough space or doesn't provide free space info; otherwise false
+ */
+ final boolean checkSpace(long batchSize) {
+ // Default to be true because if batchSize or available space is invalid, we still let the
+ // copy start anyway.
+ boolean result = true;
+ if (batchSize >= 0) {
+ RootsCache cache = DocumentsApplication.getRootsCache(appContext);
+
+ // Query root info here instead of using stack.root because the number there may be
+ // stale.
+ RootInfo root = cache.getRootOneshot(stack.root.authority, stack.root.rootId, true);
+ if (root.availableBytes >= 0) {
+ result = (batchSize <= root.availableBytes);
+ } else {
+ Log.w(TAG, root.toString() + " doesn't provide available bytes.");
+ }
+ }
+
+ if (!result) {
+ failedFileCount += mSrcs.size();
+ failedFiles.addAll(mSrcs);
+ }
+
+ return result;
+ }
+
@Override
boolean hasWarnings() {
return !convertedFiles.isEmpty();
@@ -585,7 +633,7 @@
result += calculateFileSizesRecursively(getClient(src), src.derivedUri);
} catch (RemoteException e) {
throw new ResourceException("Failed to obtain the client for %s.",
- src.derivedUri);
+ src.derivedUri, e);
}
} else {
result += src.size;
@@ -603,7 +651,7 @@
*
* @throws ResourceException
*/
- private long calculateFileSizesRecursively(
+ long calculateFileSizesRecursively(
ContentProviderClient client, Uri uri) throws ResourceException {
final String authority = uri.getAuthority();
final Uri queryUri = buildChildDocumentsUri(authority, getDocumentId(uri));
diff --git a/src/com/android/documentsui/services/MoveJob.java b/src/com/android/documentsui/services/MoveJob.java
index 5e9d5cc..beae9a8 100644
--- a/src/com/android/documentsui/services/MoveJob.java
+++ b/src/com/android/documentsui/services/MoveJob.java
@@ -29,8 +29,8 @@
import android.provider.DocumentsContract.Document;
import android.util.Log;
-import com.android.documentsui.UrisSupplier;
import com.android.documentsui.R;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -96,9 +96,35 @@
return super.setUp();
}
+ /**
+ * {@inheritDoc}
+ *
+ * Only check space for moves across authorities. For now we don't know if the doc in
+ * {@link #mSrcs} is in the same root of destination, and if it's optimized move in the same
+ * root it should succeed regardless of free space, but it's for sure a failure if there is no
+ * enough free space if docs are moved from another authority.
+ */
@Override
- public void start() {
- super.start();
+ boolean checkSpace() {
+ long size = 0;
+ for (DocumentInfo src : mSrcs) {
+ if (!src.authority.equals(stack.root.authority)) {
+ if (src.isDirectory()) {
+ try {
+ size += calculateFileSizesRecursively(getClient(src), src.derivedUri);
+ } catch (RemoteException|ResourceException e) {
+ Log.w(TAG, "Failed to obtain client for %s" + src.derivedUri + ".", e);
+
+ // Failed to calculate size, but move may still succeed.
+ return true;
+ }
+ } else {
+ size += src.size;
+ }
+ }
+ }
+
+ return checkSpace(size);
}
void processDocument(DocumentInfo src, DocumentInfo srcParent, DocumentInfo dest)