Implement some tests for com.android.documentsui.CopyService.
- Add a unit test for CopyService. - Make some changes to StubProvider to make it more configurable, for testing. Change-Id: I3d726099feaf6b7a3fdd40bf2449f4ee3e848d77
This commit is contained in:
@@ -7,6 +7,7 @@ LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_JAVA_LIBRARIES := android.test.runner
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 mockito-target guava
|
||||
|
||||
LOCAL_PACKAGE_NAME := DocumentsUITests
|
||||
LOCAL_INSTRUMENTATION_FOR := DocumentsUI
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.documentsui;
|
||||
|
||||
import static com.android.documentsui.model.DocumentInfo.getCursorString;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.test.MoreAsserts;
|
||||
import android.test.ServiceTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
import com.android.documentsui.model.DocumentStack;
|
||||
import com.android.documentsui.model.RootInfo;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
import libcore.io.Streams;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
|
||||
public CopyTest() {
|
||||
super(CopyService.class);
|
||||
}
|
||||
|
||||
private static String TAG = "CopyTest";
|
||||
// This must match the authority for the StubProvider.
|
||||
private static String AUTHORITY = "com.android.documentsui.stubprovider";
|
||||
private List<RootInfo> mRoots;
|
||||
private Context mContext;
|
||||
private ContentResolver mResolver;
|
||||
private ContentProviderClient mClient;
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
setupTestContext();
|
||||
|
||||
mResolver = mContext.getContentResolver();
|
||||
mClient = mResolver.acquireContentProviderClient(AUTHORITY);
|
||||
|
||||
// Reset the stub provider's storage.
|
||||
mClient.call("clear", "", null);
|
||||
|
||||
mRoots = Lists.newArrayList();
|
||||
Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY);
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = mClient.query(queryUri, null, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
final RootInfo root = RootInfo.fromRootsCursor(AUTHORITY, cursor);
|
||||
final String id = root.rootId;
|
||||
mRoots.add(root);
|
||||
}
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
mClient.release();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public List<Uri> setupTestFiles() throws Exception {
|
||||
Uri rootUri = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(0).documentId);
|
||||
List<Uri> testFiles = Lists.newArrayList(
|
||||
DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test0.txt"),
|
||||
DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test1.txt"),
|
||||
DocumentsContract.createDocument(mClient, rootUri, "text/plain", "test2.txt")
|
||||
);
|
||||
String testContent[] = {
|
||||
"The five boxing wizards jump quickly",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"Jackdaws love my big sphinx of quartz"
|
||||
};
|
||||
for (int i = 0; i < testFiles.size(); ++i) {
|
||||
ParcelFileDescriptor pfd = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
pfd = mClient.openFile(testFiles.get(i), "w");
|
||||
out = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
|
||||
out.write(testContent[i].getBytes());
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
return testFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test copying a single file.
|
||||
*/
|
||||
public void testCopyFile() throws Exception {
|
||||
Uri testFile = setupTestFiles().get(0);
|
||||
|
||||
// Just copy one file.
|
||||
copyToDestination(Lists.newArrayList(testFile));
|
||||
|
||||
// A call to NotificationManager.cancel marks the end of the copy operation.
|
||||
Mockito.verify(mNotificationManager, Mockito.timeout(1000)).cancel(Mockito.anyString(),
|
||||
Mockito.anyInt());
|
||||
|
||||
// Verify that one file was copied; check file contents.
|
||||
assertDstFileCountEquals(1);
|
||||
assertCopied(testFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test copying multiple files.
|
||||
*/
|
||||
public void testCopyMultipleFiles() throws Exception {
|
||||
List<Uri> testFiles = setupTestFiles();
|
||||
// Copy all the test files.
|
||||
copyToDestination(testFiles);
|
||||
|
||||
// A call to NotificationManager.cancel marks the end of the copy operation.
|
||||
Mockito.verify(mNotificationManager, Mockito.timeout(1000)).cancel(Mockito.anyString(),
|
||||
Mockito.anyInt());
|
||||
|
||||
assertDstFileCountEquals(3);
|
||||
for (Uri testFile : testFiles) {
|
||||
assertCopied(testFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given files to a pre-determined destination.
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
private void copyToDestination(List<Uri> srcs) throws FileNotFoundException {
|
||||
final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
|
||||
for (Uri src : srcs) {
|
||||
srcDocs.add(DocumentInfo.fromUri(mResolver, src));
|
||||
}
|
||||
|
||||
final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId);
|
||||
DocumentStack stack = new DocumentStack();
|
||||
stack.push(DocumentInfo.fromUri(mResolver, dst));
|
||||
final Intent copyIntent = new Intent(mContext, CopyService.class);
|
||||
copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
|
||||
copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack);
|
||||
|
||||
startService(copyIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a count of the files in the given directory.
|
||||
*/
|
||||
private void assertDstFileCountEquals(int expected) throws RemoteException {
|
||||
final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
|
||||
mRoots.get(1).documentId);
|
||||
Cursor c = null;
|
||||
int count = 0;
|
||||
try {
|
||||
c = mClient.query(queryUri, null, null, null, null);
|
||||
count = c.getCount();
|
||||
} finally {
|
||||
IoUtils.closeQuietly(c);
|
||||
}
|
||||
assertEquals("Incorrect file count after copy", expected, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the file pointed to by the given URI was correctly copied to the destination.
|
||||
*/
|
||||
private void assertCopied(Uri src) throws Exception {
|
||||
Cursor cursor = null;
|
||||
String srcName = null;
|
||||
try {
|
||||
cursor = mClient.query(src, null, null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
srcName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||
}
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
Uri dst = getDstFileUri(srcName);
|
||||
|
||||
InputStream in0 = null;
|
||||
InputStream in1 = null;
|
||||
try {
|
||||
in0 = new ParcelFileDescriptor.AutoCloseInputStream(mClient.openFile(src, "r"));
|
||||
in1 = new ParcelFileDescriptor.AutoCloseInputStream(mClient.openFile(dst, "r"));
|
||||
|
||||
byte[] buffer0 = Streams.readFully(in0);
|
||||
byte[] buffer1 = Streams.readFully(in1);
|
||||
|
||||
MoreAsserts.assertEquals(buffer0, buffer1);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(in0);
|
||||
IoUtils.closeQuietly(in1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a file URI from a given filename. This assumes the file already exists in the
|
||||
* destination root.
|
||||
*/
|
||||
private Uri getDstFileUri(String filename) throws RemoteException {
|
||||
final Uri dstFileQuery = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
|
||||
mRoots.get(1).documentId);
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
// StubProvider doesn't seem to support query strings; filter the results manually.
|
||||
cursor = mClient.query(dstFileQuery, null, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
if (filename.equals(getCursorString(cursor, Document.COLUMN_DISPLAY_NAME))) {
|
||||
return DocumentsContract.buildDocumentUri(AUTHORITY,
|
||||
getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to
|
||||
* listen for notification events, to gauge copy progress.
|
||||
*/
|
||||
private void setupTestContext() {
|
||||
mContext = getSystemContext();
|
||||
System.setProperty("dexmaker.dexcache", mContext.getCacheDir().getPath());
|
||||
|
||||
mNotificationManager = Mockito.spy((NotificationManager) mContext
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE));
|
||||
|
||||
// Insert a stub NotificationManager that enables us to listen for when copying is complete.
|
||||
setContext(new ContextWrapper(mContext) {
|
||||
@Override
|
||||
public Object getSystemService(String name) {
|
||||
if (Context.NOTIFICATION_SERVICE.equals(name)) {
|
||||
return mNotificationManager;
|
||||
} else {
|
||||
return super.getSystemService(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,34 +17,45 @@
|
||||
package com.android.documentsui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor.RowBuilder;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.FileUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class StubProvider extends DocumentsProvider {
|
||||
private static int STORAGE_SIZE = 1024 * 1024; // 1 MB.
|
||||
private static final String EXTRA_SIZE = "com.android.documentsui.stubprovider.SIZE";
|
||||
private static final String EXTRA_ROOT = "com.android.documentsui.stubprovider.ROOT";
|
||||
private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
|
||||
private static int DEFAULT_SIZE = 1024 * 1024; // 1 MB.
|
||||
private static final String TAG = "StubProvider";
|
||||
private static final String MY_ROOT_ID = "myRoot";
|
||||
|
||||
private static final String MY_ROOT_ID = "sd0";
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
@@ -54,11 +65,11 @@ public class StubProvider extends DocumentsProvider {
|
||||
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
|
||||
};
|
||||
|
||||
private String mRootDocumentId;
|
||||
private HashMap<String, StubDocument> mStorage = new HashMap<String, StubDocument>();
|
||||
private int mStorageUsedBytes;
|
||||
private Object mWriteLock = new Object();
|
||||
private String mAuthority;
|
||||
private SharedPreferences mPrefs;
|
||||
private Map<String, RootInfo> mRoots;
|
||||
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
@@ -68,29 +79,61 @@ public class StubProvider extends DocumentsProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
clearCacheAndBuildRoots();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void clearCacheAndBuildRoots() {
|
||||
final File cacheDir = getContext().getCacheDir();
|
||||
removeRecursively(cacheDir);
|
||||
final StubDocument document = new StubDocument(cacheDir, Document.MIME_TYPE_DIR, null);
|
||||
mRootDocumentId = document.documentId;
|
||||
mStorage.put(mRootDocumentId, document);
|
||||
return true;
|
||||
mStorage.clear();
|
||||
|
||||
mPrefs = getContext().getSharedPreferences(
|
||||
"com.android.documentsui.stubprovider.preferences", Context.MODE_PRIVATE);
|
||||
Collection<String> rootIds = mPrefs.getStringSet("roots", null);
|
||||
if (rootIds == null) {
|
||||
rootIds = Arrays.asList(new String[] {
|
||||
"sd0", "sd1"
|
||||
});
|
||||
}
|
||||
// Create new roots.
|
||||
mRoots = Maps.newHashMap();
|
||||
for (String rootId : rootIds) {
|
||||
final RootInfo rootInfo = new RootInfo(rootId, getSize(rootId));
|
||||
mRoots.put(rootId, rootInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Storage size, in bytes.
|
||||
*/
|
||||
private long getSize(String rootId) {
|
||||
final String key = STORAGE_SIZE_KEY + "." + rootId;
|
||||
return mPrefs.getLong(key, DEFAULT_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
final RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(Root.COLUMN_TITLE, "Foobar SD 4GB");
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, mRootDocumentId);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, STORAGE_SIZE - mStorageUsedBytes);
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_ROOT_PROJECTION);
|
||||
for (Map.Entry<String, RootInfo> entry : mRoots.entrySet()) {
|
||||
final String id = entry.getKey();
|
||||
final RootInfo info = entry.getValue();
|
||||
final RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, id);
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(Root.COLUMN_TITLE, id);
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, info.rootDocument.documentId);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
public Cursor queryDocument(String documentId, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_DOCUMENT_PROJECTION);
|
||||
final StubDocument file = mStorage.get(documentId);
|
||||
if (file == null) {
|
||||
throw new FileNotFoundException();
|
||||
@@ -123,14 +166,12 @@ public class StubProvider extends DocumentsProvider {
|
||||
if (!file.createNewFile()) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
final StubDocument document = new StubDocument(file, mimeType, parentDocument);
|
||||
mStorage.put(document.documentId, document);
|
||||
notifyParentChanged(document.parentId);
|
||||
return document.documentId;
|
||||
}
|
||||
@@ -143,7 +184,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
if (document == null || !document.file.delete())
|
||||
throw new FileNotFoundException();
|
||||
synchronized (mWriteLock) {
|
||||
mStorageUsedBytes -= fileSize;
|
||||
document.rootInfo.size -= fileSize;
|
||||
}
|
||||
notifyParentChanged(document.parentId);
|
||||
}
|
||||
@@ -155,12 +196,13 @@ public class StubProvider extends DocumentsProvider {
|
||||
if (parentDocument == null || parentDocument.file.isFile()) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_DOCUMENT_PROJECTION);
|
||||
result.setNotificationUri(getContext().getContentResolver(),
|
||||
DocumentsContract.buildChildDocumentsUri(mAuthority, parentDocumentId));
|
||||
StubDocument document;
|
||||
for (File file : parentDocument.file.listFiles()) {
|
||||
document = mStorage.get(StubDocument.getDocumentIdForFile(file));
|
||||
document = mStorage.get(getDocumentIdForFile(file));
|
||||
if (document != null) {
|
||||
includeDocument(result, document);
|
||||
}
|
||||
@@ -171,7 +213,9 @@ public class StubProvider extends DocumentsProvider {
|
||||
@Override
|
||||
public Cursor queryRecentDocuments(String rootId, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
throw new FileNotFoundException();
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection
|
||||
: DEFAULT_DOCUMENT_PROJECTION);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,8 +246,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
ParcelFileDescriptor[] pipe;
|
||||
try {
|
||||
pipe = ParcelFileDescriptor.createReliablePipe();
|
||||
}
|
||||
catch (IOException exception) {
|
||||
} catch (IOException exception) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
final ParcelFileDescriptor readPipe = pipe[0];
|
||||
@@ -212,15 +255,19 @@ public class StubProvider extends DocumentsProvider {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
InputStream inputStream = null;
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
final FileInputStream inputStream = new FileInputStream(readPipe.getFileDescriptor());
|
||||
final FileOutputStream outputStream = new FileOutputStream(document.file);
|
||||
inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPipe);
|
||||
outputStream = new FileOutputStream(document.file);
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
int bytesToRead;
|
||||
int bytesRead = 0;
|
||||
while (bytesRead != -1) {
|
||||
synchronized (mWriteLock) {
|
||||
bytesToRead = Math.min(STORAGE_SIZE - mStorageUsedBytes, buffer.length);
|
||||
// This cast is safe because the max possible value is buffer.length.
|
||||
bytesToRead = (int) Math.min(document.rootInfo.getRemainingCapacity(),
|
||||
buffer.length);
|
||||
if (bytesToRead == 0) {
|
||||
closePipeWithErrorSilently(readPipe, "Not enough space.");
|
||||
break;
|
||||
@@ -230,15 +277,14 @@ public class StubProvider extends DocumentsProvider {
|
||||
break;
|
||||
}
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
mStorageUsedBytes += bytesRead;
|
||||
document.rootInfo.size += bytesRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
closePipeWithErrorSilently(readPipe, e.getMessage());
|
||||
}
|
||||
finally {
|
||||
closePipeSilently(readPipe);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(inputStream);
|
||||
IoUtils.closeQuietly(outputStream);
|
||||
notifyParentChanged(document.parentId);
|
||||
}
|
||||
}
|
||||
@@ -250,24 +296,38 @@ public class StubProvider extends DocumentsProvider {
|
||||
private void closePipeWithErrorSilently(ParcelFileDescriptor pipe, String error) {
|
||||
try {
|
||||
pipe.closeWithError(error);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private void closePipeSilently(ParcelFileDescriptor pipe) {
|
||||
try {
|
||||
pipe.close();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
@Override
|
||||
public Bundle call(String method, String arg, Bundle extras) {
|
||||
Log.d(TAG, "call: " + method + arg);
|
||||
switch (method) {
|
||||
case "clear":
|
||||
clearCacheAndBuildRoots();
|
||||
return null;
|
||||
case "configure":
|
||||
configure(arg, extras);
|
||||
return null;
|
||||
default:
|
||||
return super.call(method, arg, extras);
|
||||
}
|
||||
}
|
||||
|
||||
private void configure(String arg, Bundle extras) {
|
||||
Log.d(TAG, "Configure " + arg);
|
||||
String rootName = extras.getString(EXTRA_ROOT, MY_ROOT_ID);
|
||||
long rootSize = extras.getLong(EXTRA_SIZE, 1) * 1024 * 1024;
|
||||
setSize(rootName, rootSize);
|
||||
}
|
||||
|
||||
private void notifyParentChanged(String parentId) {
|
||||
getContext().getContentResolver().notifyChange(
|
||||
DocumentsContract.buildChildDocumentsUri(mAuthority, parentId), null, false);
|
||||
// Notify also about possible change in remaining space on the root.
|
||||
getContext().getContentResolver().notifyChange(DocumentsContract.buildRootsUri(mAuthority), null, false);
|
||||
getContext().getContentResolver().notifyChange(DocumentsContract.buildRootsUri(mAuthority),
|
||||
null, false);
|
||||
}
|
||||
|
||||
private void includeDocument(MatrixCursor result, StubDocument document) {
|
||||
@@ -295,22 +355,102 @@ public class StubProvider extends DocumentsProvider {
|
||||
childFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StubDocument {
|
||||
public final File file;
|
||||
public final String mimeType;
|
||||
public final String documentId;
|
||||
public final String parentId;
|
||||
public void setSize(String rootId, long rootSize) {
|
||||
RootInfo root = mRoots.get(rootId);
|
||||
if (root != null) {
|
||||
final String key = STORAGE_SIZE_KEY + "." + rootId;
|
||||
Log.d(TAG, "Set size of " + key + " : " + rootSize);
|
||||
|
||||
StubDocument(File file, String mimeType, StubDocument parent) {
|
||||
this.file = file;
|
||||
this.mimeType = mimeType;
|
||||
this.documentId = getDocumentIdForFile(file);
|
||||
this.parentId = parent != null ? parent.documentId : null;
|
||||
// Persist the size.
|
||||
SharedPreferences.Editor editor = mPrefs.edit();
|
||||
editor.putLong(key, rootSize);
|
||||
editor.apply();
|
||||
// Apply the size in the current instance of this provider.
|
||||
root.capacity = rootSize;
|
||||
getContext().getContentResolver().notifyChange(
|
||||
DocumentsContract.buildRootsUri(mAuthority),
|
||||
null, false);
|
||||
} else {
|
||||
Log.e(TAG, "Attempt to configure non-existent root: " + rootId);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDocumentIdForFile(File file) {
|
||||
public File createFile(String rootId, File parent, String mimeType, String name)
|
||||
throws IOException {
|
||||
StubDocument parentDoc = null;
|
||||
if (parent == null) {
|
||||
// Use the root dir as the parent, if one wasn't specified.
|
||||
parentDoc = mRoots.get(rootId).rootDocument;
|
||||
} else {
|
||||
// Verify that the parent exists and is a directory.
|
||||
parentDoc = mStorage.get(getDocumentIdForFile(parent));
|
||||
if (parentDoc == null) {
|
||||
throw new IllegalArgumentException("Parent file not found.");
|
||||
}
|
||||
if (!Document.MIME_TYPE_DIR.equals(parentDoc.mimeType)) {
|
||||
throw new IllegalArgumentException("Parent file must be a directory.");
|
||||
}
|
||||
}
|
||||
File file = new File(parentDoc.file, name);
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
file.mkdir();
|
||||
} else {
|
||||
file.createNewFile();
|
||||
}
|
||||
new StubDocument(file, mimeType, parentDoc);
|
||||
return file;
|
||||
}
|
||||
|
||||
final class RootInfo {
|
||||
public final String name;
|
||||
public final StubDocument rootDocument;
|
||||
public long capacity;
|
||||
public long size;
|
||||
|
||||
RootInfo(String name, long capacity) {
|
||||
this.name = name;
|
||||
this.capacity = 1024 * 1024;
|
||||
// Make a subdir in the cache dir for each root.
|
||||
File rootDir = new File(getContext().getCacheDir(), name);
|
||||
rootDir.mkdir();
|
||||
this.rootDocument = new StubDocument(rootDir, Document.MIME_TYPE_DIR, this);
|
||||
this.capacity = capacity;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
public long getRemainingCapacity() {
|
||||
return capacity - size;
|
||||
}
|
||||
}
|
||||
|
||||
final class StubDocument {
|
||||
public final File file;
|
||||
public final String mimeType;
|
||||
public final String documentId;
|
||||
public final String parentId;
|
||||
public final RootInfo rootInfo;
|
||||
|
||||
StubDocument(File file, String mimeType, StubDocument parent) {
|
||||
this.file = file;
|
||||
this.mimeType = mimeType;
|
||||
this.documentId = getDocumentIdForFile(file);
|
||||
this.parentId = parent.documentId;
|
||||
this.rootInfo = parent.rootInfo;
|
||||
mStorage.put(this.documentId, this);
|
||||
}
|
||||
|
||||
StubDocument(File file, String mimeType, RootInfo rootInfo) {
|
||||
this.file = file;
|
||||
this.mimeType = mimeType;
|
||||
this.documentId = getDocumentIdForFile(file);
|
||||
this.parentId = null;
|
||||
this.rootInfo = rootInfo;
|
||||
mStorage.put(this.documentId, this);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDocumentIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user