Merge "Add a move feature to DocumentsUI."
This commit is contained in:
@@ -37,4 +37,8 @@
|
||||
android:id="@+id/menu_copy"
|
||||
android:title="@string/menu_copy"
|
||||
android:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/menu_move"
|
||||
android:title="@string/menu_move"
|
||||
android:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
<string name="menu_select_all">Select All</string>
|
||||
<!-- Menu item title that copies the selected documents [CHAR LIMIT=24] -->
|
||||
<string name="menu_copy">Copy to\u2026</string>
|
||||
<!-- Menu item title that moves the selected documents [CHAR LIMIT=24] -->
|
||||
<string name="menu_move">Move to\u2026</string>
|
||||
|
||||
<!-- Menu item that reveals internal storage built into the device [CHAR LIMIT=24] -->
|
||||
<string name="menu_advanced_show" product="nosdcard">Show internal storage</string>
|
||||
@@ -124,6 +126,10 @@
|
||||
<item quantity="one">Copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
|
||||
<item quantity="other">Copying <xliff:g id="count" example="3">%1$d</xliff:g> files.</item>
|
||||
</plurals>
|
||||
<plurals name="move_begin">
|
||||
<item quantity="one">Moving <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
|
||||
<item quantity="other">Moving <xliff:g id="count" example="3">%1$d</xliff:g> files.</item>
|
||||
</plurals>
|
||||
<!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] -->
|
||||
<string name="copy_preparing">Preparing for copy\u2026</string>
|
||||
<!-- Title of the copy error notification [CHAR LIMIT=48] -->
|
||||
|
||||
@@ -269,14 +269,16 @@ abstract class BaseActivity extends Activity {
|
||||
/** Derived after loader */
|
||||
public int derivedSortOrder = SORT_ORDER_DISPLAY_NAME;
|
||||
|
||||
public boolean allowMultiple = false;
|
||||
public boolean showSize = false;
|
||||
public boolean localOnly = false;
|
||||
public boolean forceAdvanced = false;
|
||||
public boolean showAdvanced = false;
|
||||
public boolean stackTouched = false;
|
||||
public boolean restored = false;
|
||||
public boolean directoryCopy = false;
|
||||
public boolean allowMultiple;
|
||||
public boolean showSize;
|
||||
public boolean localOnly ;
|
||||
public boolean forceAdvanced ;
|
||||
public boolean showAdvanced ;
|
||||
public boolean stackTouched ;
|
||||
public boolean restored ;
|
||||
public boolean directoryCopy ;
|
||||
/** Transfer mode for file copy/move operations. */
|
||||
public int transferMode;
|
||||
|
||||
/** Current user navigation stack; empty implies recents. */
|
||||
public DocumentStack stack = new DocumentStack();
|
||||
|
||||
@@ -61,6 +61,11 @@ public class CopyService extends IntentService {
|
||||
public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
|
||||
public static final String EXTRA_STACK = "com.android.documentsui.STACK";
|
||||
public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
|
||||
public static final String EXTRA_TRANSFER_MODE = "com.android.documentsui.TRANSFER_MODE";
|
||||
|
||||
public static final int TRANSFER_MODE_NONE = 0;
|
||||
public static final int TRANSFER_MODE_COPY = 1;
|
||||
public static final int TRANSFER_MODE_MOVE = 2;
|
||||
|
||||
// TODO: Move it to a shared file when more operations are implemented.
|
||||
public static final int FAILURE_COPY = 1;
|
||||
@@ -101,15 +106,19 @@ public class CopyService extends IntentService {
|
||||
* @param srcDocs A list of src files to copy.
|
||||
* @param dstStack The copy destination stack.
|
||||
*/
|
||||
public static void start(Context context, List<DocumentInfo> srcDocs, DocumentStack dstStack) {
|
||||
public static void start(Context context, List<DocumentInfo> srcDocs, DocumentStack dstStack,
|
||||
int mode) {
|
||||
final Resources res = context.getResources();
|
||||
final Intent copyIntent = new Intent(context, CopyService.class);
|
||||
copyIntent.putParcelableArrayListExtra(
|
||||
EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
|
||||
copyIntent.putExtra(EXTRA_STACK, (Parcelable) dstStack);
|
||||
copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
|
||||
|
||||
int toastMessage = (mode == TRANSFER_MODE_COPY) ? R.plurals.copy_begin
|
||||
: R.plurals.move_begin;
|
||||
Toast.makeText(context,
|
||||
res.getQuantityString(R.plurals.copy_begin, srcDocs.size(), srcDocs.size()),
|
||||
res.getQuantityString(toastMessage, srcDocs.size(), srcDocs.size()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
context.startService(copyIntent);
|
||||
}
|
||||
@@ -131,6 +140,8 @@ public class CopyService extends IntentService {
|
||||
|
||||
final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
|
||||
final DocumentStack stack = intent.getParcelableExtra(EXTRA_STACK);
|
||||
// Copy by default.
|
||||
final int transferMode = intent.getIntExtra(EXTRA_TRANSFER_MODE, TRANSFER_MODE_COPY);
|
||||
|
||||
try {
|
||||
// Acquire content providers.
|
||||
@@ -142,7 +153,7 @@ public class CopyService extends IntentService {
|
||||
setupCopyJob(srcs, stack);
|
||||
|
||||
for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
|
||||
copy(srcs.get(i), stack.peek());
|
||||
copy(srcs.get(i), stack.peek(), transferMode);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Catch-all to prevent any copy errors from wedging the app.
|
||||
@@ -173,8 +184,6 @@ public class CopyService extends IntentService {
|
||||
.setAutoCancel(true);
|
||||
mNotificationManager.notify(mJobId, 0, errorBuilder.build());
|
||||
}
|
||||
|
||||
// TODO: Display a toast if the copy was cancelled.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +386,8 @@ public class CopyService extends IntentService {
|
||||
* @param dstDirInfo The destination directory.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo) throws RemoteException {
|
||||
private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
|
||||
throws RemoteException {
|
||||
final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirInfo.derivedUri,
|
||||
srcInfo.mimeType, srcInfo.displayName);
|
||||
if (dstUri == null) {
|
||||
@@ -388,9 +398,9 @@ public class CopyService extends IntentService {
|
||||
}
|
||||
|
||||
if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
|
||||
copyDirectoryHelper(srcInfo.derivedUri, dstUri);
|
||||
copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
|
||||
} else {
|
||||
copyFileHelper(srcInfo.derivedUri, dstUri);
|
||||
copyFileHelper(srcInfo.derivedUri, dstUri, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +413,8 @@ public class CopyService extends IntentService {
|
||||
* @param dstDirUri URI of the directory to copy to. Must be created beforehand.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri) throws RemoteException {
|
||||
private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri, int mode)
|
||||
throws RemoteException {
|
||||
// Recurse into directories. Copy children into the new subdirectory.
|
||||
final String queryColumns[] = new String[] {
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
@@ -424,9 +435,20 @@ public class CopyService extends IntentService {
|
||||
final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
|
||||
getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
|
||||
if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
|
||||
copyDirectoryHelper(childUri, dstUri);
|
||||
copyDirectoryHelper(childUri, dstUri, mode);
|
||||
} else {
|
||||
copyFileHelper(childUri, dstUri);
|
||||
copyFileHelper(childUri, dstUri, mode);
|
||||
}
|
||||
}
|
||||
if (mode == TRANSFER_MODE_MOVE) {
|
||||
try {
|
||||
DocumentsContract.deleteDocument(mSrcClient, srcDirUri);
|
||||
} catch (RemoteException e) {
|
||||
// RemoteExceptions usually signal that the connection is dead, so there's no
|
||||
// point attempting to continue. Propagate the exception up so the copy job is
|
||||
// cancelled.
|
||||
Log.w(TAG, "Failed to clean up after move: " + srcDirUri, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -441,7 +463,8 @@ public class CopyService extends IntentService {
|
||||
* @param dstUri URI of the *file* to copy to. Must be created beforehand.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
private void copyFileHelper(Uri srcUri, Uri dstUri) throws RemoteException {
|
||||
private void copyFileHelper(Uri srcUri, Uri dstUri, int mode)
|
||||
throws RemoteException {
|
||||
// Copy an individual file.
|
||||
CancellationSignal canceller = new CancellationSignal();
|
||||
ParcelFileDescriptor srcFile = null;
|
||||
@@ -484,7 +507,7 @@ public class CopyService extends IntentService {
|
||||
mFailedFiles.add(DocumentInfo.fromUri(getContentResolver(), srcUri));
|
||||
} catch (FileNotFoundException ignore) {
|
||||
Log.w(TAG, "Source file gone: " + srcUri, copyError);
|
||||
// The source file is gone.
|
||||
// The source file is gone.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,11 +517,19 @@ public class CopyService extends IntentService {
|
||||
try {
|
||||
DocumentsContract.deleteDocument(mDstClient, dstUri);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to clean up: " + srcUri, e);
|
||||
Log.w(TAG, "Failed to clean up after copy error: " + dstUri, e);
|
||||
// RemoteExceptions usually signal that the connection is dead, so there's no point
|
||||
// attempting to continue. Propagate the exception up so the copy job is cancelled.
|
||||
throw e;
|
||||
}
|
||||
} else if (mode == TRANSFER_MODE_MOVE) {
|
||||
// Clean up src files after a successful move.
|
||||
try {
|
||||
DocumentsContract.deleteDocument(mSrcClient, srcUri);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to clean up after move: " + srcUri, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +369,8 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
|
||||
CopyService.start(getActivity(), getDisplayState(this).selectedDocumentsForCopy,
|
||||
(DocumentStack) data.getParcelableExtra(CopyService.EXTRA_STACK));
|
||||
(DocumentStack) data.getParcelableExtra(CopyService.EXTRA_STACK),
|
||||
data.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_NONE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -502,6 +503,7 @@ public class DirectoryFragment extends Fragment {
|
||||
final MenuItem share = menu.findItem(R.id.menu_share);
|
||||
final MenuItem delete = menu.findItem(R.id.menu_delete);
|
||||
final MenuItem copy = menu.findItem(R.id.menu_copy);
|
||||
final MenuItem move = menu.findItem(R.id.menu_move);
|
||||
|
||||
final boolean manageOrBrowse = (state.action == ACTION_MANAGE
|
||||
|| state.action == ACTION_BROWSE || state.action == ACTION_BROWSE_ALL);
|
||||
@@ -511,7 +513,7 @@ public class DirectoryFragment extends Fragment {
|
||||
delete.setVisible(manageOrBrowse);
|
||||
// Disable copying from the Recents view.
|
||||
copy.setVisible(manageOrBrowse && mType != TYPE_RECENT_OPEN);
|
||||
|
||||
move.setVisible(SystemProperties.getBoolean("debug.documentsui.enable_move", false));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -536,7 +538,12 @@ public class DirectoryFragment extends Fragment {
|
||||
return true;
|
||||
|
||||
} else if (id == R.id.menu_copy) {
|
||||
onCopyDocuments(docs);
|
||||
onTransferDocuments(docs, CopyService.TRANSFER_MODE_COPY);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
} else if (id == R.id.menu_move) {
|
||||
onTransferDocuments(docs, CopyService.TRANSFER_MODE_MOVE);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
@@ -665,7 +672,7 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onCopyDocuments(List<DocumentInfo> docs) {
|
||||
private void onTransferDocuments(List<DocumentInfo> docs, int mode) {
|
||||
getDisplayState(this).selectedDocumentsForCopy = docs;
|
||||
|
||||
// Pop up a dialog to pick a destination. This is inadequate but works for now.
|
||||
@@ -683,6 +690,7 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
|
||||
intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
|
||||
startActivityForResult(intent, REQUEST_COPY_DESTINATION);
|
||||
}
|
||||
|
||||
@@ -1241,7 +1249,7 @@ public class DirectoryFragment extends Fragment {
|
||||
tmpStack = curStack;
|
||||
}
|
||||
|
||||
CopyService.start(getActivity(), srcDocs, tmpStack);
|
||||
CopyService.start(getActivity(), srcDocs, tmpStack, CopyService.TRANSFER_MODE_COPY);
|
||||
}
|
||||
|
||||
private List<DocumentInfo> getDocumentsFromClipData(ClipData clipData) {
|
||||
|
||||
@@ -242,6 +242,8 @@ public class DocumentsActivity extends BaseActivity {
|
||||
if (state.action == ACTION_OPEN_COPY_DESTINATION) {
|
||||
state.directoryCopy = intent.getBooleanExtra(
|
||||
BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
|
||||
state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
|
||||
CopyService.TRANSFER_MODE_NONE);
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -704,6 +706,7 @@ public class DocumentsActivity extends BaseActivity {
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
// TODO: Move passing the stack to the separate ACTION_COPY action once it's implemented.
|
||||
intent.putExtra(CopyService.EXTRA_STACK, (Parcelable)mState.stack);
|
||||
intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mState.transferMode);
|
||||
} else {
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
@@ -16,23 +16,18 @@
|
||||
|
||||
package com.android.documentsui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
|
||||
import com.android.documentsui.CopyService;
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
import com.android.documentsui.model.DocumentStack;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
@@ -43,10 +38,11 @@ public class FailureDialogFragment extends DialogFragment
|
||||
private static final String TAG = "FailureDialogFragment";
|
||||
|
||||
private int mFailure;
|
||||
private int mTransferMode;
|
||||
private ArrayList<DocumentInfo> mFailedSrcList;
|
||||
|
||||
public static void show(FragmentManager fm, int failure,
|
||||
ArrayList<DocumentInfo> failedSrcList, DocumentStack dstStack) {
|
||||
ArrayList<DocumentInfo> failedSrcList, DocumentStack dstStack, int transferMode) {
|
||||
// TODO: Add support for other failures than copy.
|
||||
if (failure != CopyService.FAILURE_COPY) {
|
||||
return;
|
||||
@@ -54,6 +50,7 @@ public class FailureDialogFragment extends DialogFragment
|
||||
|
||||
final Bundle args = new Bundle();
|
||||
args.putInt(CopyService.EXTRA_FAILURE, failure);
|
||||
args.putInt(CopyService.EXTRA_TRANSFER_MODE, transferMode);
|
||||
args.putParcelableArrayList(CopyService.EXTRA_SRC_LIST, failedSrcList);
|
||||
|
||||
final FragmentTransaction ft = fm.beginTransaction();
|
||||
@@ -66,11 +63,12 @@ public class FailureDialogFragment extends DialogFragment
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
if (whichButton == DialogInterface.BUTTON_POSITIVE) {
|
||||
CopyService.start(getActivity(), mFailedSrcList,
|
||||
(DocumentStack) getActivity().getIntent().getParcelableExtra(
|
||||
CopyService.EXTRA_STACK));
|
||||
}
|
||||
if (whichButton == DialogInterface.BUTTON_POSITIVE) {
|
||||
CopyService.start(getActivity(), mFailedSrcList,
|
||||
(DocumentStack) getActivity().getIntent().getParcelableExtra(
|
||||
CopyService.EXTRA_STACK),
|
||||
mTransferMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,6 +76,7 @@ public class FailureDialogFragment extends DialogFragment
|
||||
super.onCreate(inState);
|
||||
|
||||
mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE);
|
||||
mTransferMode = getArguments().getInt(CopyService.EXTRA_TRANSFER_MODE);
|
||||
mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
|
||||
|
||||
final StringBuilder list = new StringBuilder("<p>");
|
||||
@@ -89,9 +88,9 @@ public class FailureDialogFragment extends DialogFragment
|
||||
list.toString());
|
||||
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setMessage(Html.fromHtml(message))
|
||||
.setPositiveButton(R.string.retry, this)
|
||||
.setNegativeButton(android.R.string.cancel, this)
|
||||
.create();
|
||||
.setMessage(Html.fromHtml(message))
|
||||
.setPositiveButton(R.string.retry, this)
|
||||
.setNegativeButton(android.R.string.cancel, this)
|
||||
.create();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ public class StandaloneActivity extends BaseActivity {
|
||||
mDirectoryContainer = (DirectoryContainerView) findViewById(R.id.container_directory);
|
||||
|
||||
mState = (icicle != null)
|
||||
? icicle.<State>getParcelable(EXTRA_STATE)
|
||||
: buildDefaultState();
|
||||
? icicle.<State> getParcelable(EXTRA_STATE)
|
||||
: buildDefaultState();
|
||||
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
mToolbar.setTitleTextAppearance(context,
|
||||
@@ -111,10 +111,13 @@ public class StandaloneActivity extends BaseActivity {
|
||||
final Intent intent = getIntent();
|
||||
final DocumentStack dstStack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
|
||||
final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
|
||||
final int transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
|
||||
CopyService.TRANSFER_MODE_NONE);
|
||||
if (failure != 0) {
|
||||
final ArrayList<DocumentInfo> failedSrcList =
|
||||
intent.getParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST);
|
||||
FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList, dstStack);
|
||||
FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList, dstStack,
|
||||
transferMode);
|
||||
}
|
||||
} else {
|
||||
onCurrentDirectoryChanged(ANIM_NONE);
|
||||
@@ -281,6 +284,7 @@ public class StandaloneActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDocumentsPicked(List<DocumentInfo> docs) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
// Signal that the test is now waiting for files.
|
||||
mReadySignal.countDown();
|
||||
if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) {
|
||||
throw new TimeoutException("Timed out waiting for files to be copied.");
|
||||
throw new TimeoutException("Timed out waiting for file operations to complete.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
copyToDestination(Lists.newArrayList(testFile));
|
||||
startService(createCopyIntent(Lists.newArrayList(testFile)));
|
||||
|
||||
// 2 operations: file creation, then writing data.
|
||||
mResolver.waitForChanges(2);
|
||||
@@ -169,6 +169,28 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
assertCopied(srcPath);
|
||||
}
|
||||
|
||||
public void testMoveFile() throws Exception {
|
||||
String srcPath = "/test0.txt";
|
||||
String testContent = "The five boxing wizards jump quickly";
|
||||
Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", testContent.getBytes());
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
|
||||
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
|
||||
startService(moveIntent);
|
||||
|
||||
// 3 operations: file creation, writing data, deleting original.
|
||||
mResolver.waitForChanges(3);
|
||||
|
||||
// Verify that one file was moved; check file contents.
|
||||
assertDstFileCountEquals(1);
|
||||
assertDoesNotExist(SRC, srcPath);
|
||||
|
||||
byte[] dstContent = readFile(DST, srcPath);
|
||||
MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test copying multiple files.
|
||||
*/
|
||||
@@ -191,7 +213,7 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
// Copy all the test files.
|
||||
copyToDestination(testFiles);
|
||||
startService(createCopyIntent(testFiles));
|
||||
|
||||
// 3 file creations, 3 file writes.
|
||||
mResolver.waitForChanges(6);
|
||||
@@ -209,40 +231,190 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
copyToDestination(Lists.newArrayList(testDir));
|
||||
startService(createCopyIntent(Lists.newArrayList(testDir)));
|
||||
|
||||
// Just 1 operation: Directory creation.
|
||||
mResolver.waitForChanges(1);
|
||||
|
||||
assertDstFileCountEquals(1);
|
||||
|
||||
// Verify that the dst exists and is a directory.
|
||||
File dst = mStorage.getFile(DST, srcPath);
|
||||
assertTrue(dst.isDirectory());
|
||||
}
|
||||
|
||||
public void testReadErrors() throws Exception {
|
||||
public void testMoveEmptyDir() throws Exception {
|
||||
String srcPath = "/emptyDir";
|
||||
Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
|
||||
null);
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
|
||||
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
|
||||
startService(moveIntent);
|
||||
|
||||
// 2 operations: Directory creation, and removal of the original.
|
||||
mResolver.waitForChanges(2);
|
||||
|
||||
assertDstFileCountEquals(1);
|
||||
|
||||
// Verify that the dst exists and is a directory.
|
||||
File dst = mStorage.getFile(DST, srcPath);
|
||||
assertTrue(dst.isDirectory());
|
||||
|
||||
// Verify that the src was cleaned up.
|
||||
assertDoesNotExist(SRC, srcPath);
|
||||
}
|
||||
|
||||
public void testMovePopulatedDir() throws Exception {
|
||||
String testContent[] = {
|
||||
"The five boxing wizards jump quickly",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"Jackdaws love my big sphinx of quartz"
|
||||
};
|
||||
String srcDir = "/testdir";
|
||||
String srcFiles[] = {
|
||||
srcDir + "/test0.txt",
|
||||
srcDir + "/test1.txt",
|
||||
srcDir + "/test2.txt"
|
||||
};
|
||||
// Create test dir; put some files in it.
|
||||
Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
|
||||
null);
|
||||
mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
|
||||
mStorage.createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
|
||||
mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
|
||||
|
||||
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
|
||||
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
|
||||
startService(moveIntent);
|
||||
|
||||
// dir creation, then creation and writing of 3 files, then removal of src dir and 3 src
|
||||
// files.
|
||||
mResolver.waitForChanges(11);
|
||||
|
||||
// Check the content of the moved files.
|
||||
File dst = mStorage.getFile(DST, srcDir);
|
||||
assertTrue(dst.isDirectory());
|
||||
for (int i = 0; i < testContent.length; ++i) {
|
||||
byte[] dstContent = readFile(DST, srcFiles[i]);
|
||||
MoreAsserts.assertEquals("Copied file contents differ", testContent[i].getBytes(),
|
||||
dstContent);
|
||||
}
|
||||
|
||||
// Check that the src files were removed.
|
||||
assertDoesNotExist(SRC, srcDir);
|
||||
for (String srcFile : srcFiles) {
|
||||
assertDoesNotExist(SRC, srcFile);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCopyFileWithReadErrors() throws Exception {
|
||||
String srcPath = "/test0.txt";
|
||||
Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
|
||||
"The five boxing wizards jump quickly".getBytes());
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
mStorage.simulateReadErrors(true);
|
||||
mStorage.simulateReadErrorsForFile(testFile);
|
||||
|
||||
copyToDestination(Lists.newArrayList(testFile));
|
||||
startService(createCopyIntent(Lists.newArrayList(testFile)));
|
||||
|
||||
// 3 operations: file creation, writing, then deletion (due to failed copy).
|
||||
mResolver.waitForChanges(3);
|
||||
|
||||
// Verify that the failed copy was cleaned up.
|
||||
assertDstFileCountEquals(0);
|
||||
}
|
||||
|
||||
public void testMoveFileWithReadErrors() throws Exception {
|
||||
String srcPath = "/test0.txt";
|
||||
Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain",
|
||||
"The five boxing wizards jump quickly".getBytes());
|
||||
|
||||
assertDstFileCountEquals(0);
|
||||
|
||||
mStorage.simulateReadErrorsForFile(testFile);
|
||||
|
||||
Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
|
||||
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
|
||||
startService(moveIntent);
|
||||
|
||||
try {
|
||||
// There should be 3 operations: file creation, writing, then deletion (due to failed
|
||||
// copy). Wait for 4, in case the CopyService also attempts to do extra stuff (like
|
||||
// delete the src file). This should time out.
|
||||
mResolver.waitForChanges(4);
|
||||
} catch (TimeoutException e) {
|
||||
// Success path
|
||||
return;
|
||||
} finally {
|
||||
// Verify that the failed copy was cleaned up, and the src file wasn't removed.
|
||||
assertDstFileCountEquals(0);
|
||||
assertExists(SRC, srcPath);
|
||||
}
|
||||
// The asserts above didn't fail, but the CopyService did something unexpected.
|
||||
fail("Extra file operations were detected");
|
||||
}
|
||||
|
||||
public void testMoveDirectoryWithReadErrors() throws Exception {
|
||||
String testContent[] = {
|
||||
"The five boxing wizards jump quickly",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"Jackdaws love my big sphinx of quartz"
|
||||
};
|
||||
String srcDir = "/testdir";
|
||||
String srcFiles[] = {
|
||||
srcDir + "/test0.txt",
|
||||
srcDir + "/test1.txt",
|
||||
srcDir + "/test2.txt"
|
||||
};
|
||||
// Create test dir; put some files in it.
|
||||
Uri testDir = mStorage.createFile(SRC, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
|
||||
null);
|
||||
mStorage.createFile(SRC, srcFiles[0], "text/plain", testContent[0].getBytes());
|
||||
Uri errFile = mStorage
|
||||
.createFile(SRC, srcFiles[1], "text/plain", testContent[1].getBytes());
|
||||
mStorage.createFile(SRC, srcFiles[2], "text/plain", testContent[2].getBytes());
|
||||
|
||||
mStorage.simulateReadErrorsForFile(errFile);
|
||||
|
||||
Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
|
||||
moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
|
||||
startService(moveIntent);
|
||||
|
||||
// - dst dir creation,
|
||||
// - creation and writing of 2 files, removal of 2 src files
|
||||
// - creation and writing of 1 file, then removal of that file (due to error)
|
||||
mResolver.waitForChanges(10);
|
||||
|
||||
// Check that both the src and dst dirs exist. The src dir shouldn't have been removed,
|
||||
// because it should contain the one errFile.
|
||||
assertTrue(mStorage.getFile(SRC, srcDir).isDirectory());
|
||||
assertTrue(mStorage.getFile(DST, srcDir).isDirectory());
|
||||
|
||||
// Check the content of the moved files.
|
||||
MoreAsserts.assertEquals("Copied file contents differ", testContent[0].getBytes(),
|
||||
readFile(DST, srcFiles[0]));
|
||||
MoreAsserts.assertEquals("Copied file contents differ", testContent[2].getBytes(),
|
||||
readFile(DST, srcFiles[2]));
|
||||
|
||||
// Check that the src files were removed.
|
||||
assertDoesNotExist(SRC, srcFiles[0]);
|
||||
assertDoesNotExist(SRC, srcFiles[2]);
|
||||
|
||||
// Check that the error file was not copied over.
|
||||
assertDoesNotExist(DST, srcFiles[1]);
|
||||
assertExists(SRC, srcFiles[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given files to a pre-determined destination.
|
||||
*
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
private void copyToDestination(List<Uri> srcs) throws FileNotFoundException {
|
||||
private Intent createCopyIntent(List<Uri> srcs) throws FileNotFoundException {
|
||||
final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
|
||||
for (Uri src : srcs) {
|
||||
srcDocs.add(DocumentInfo.fromUri(mResolver, src));
|
||||
@@ -255,7 +427,8 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
|
||||
copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack);
|
||||
|
||||
startService(copyIntent);
|
||||
// startService(copyIntent);
|
||||
return copyIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,24 +448,34 @@ public class CopyTest extends ServiceTestCase<CopyService> {
|
||||
assertEquals("Incorrect file count after copy", expected, count);
|
||||
}
|
||||
|
||||
private void assertCopied(String path) throws Exception {
|
||||
File srcFile = mStorage.getFile(SRC, path);
|
||||
File dstFile = mStorage.getFile(DST, path);
|
||||
assertNotNull(dstFile);
|
||||
private void assertExists(String rootId, String path) throws Exception {
|
||||
assertNotNull("An expected file was not found: " + path + " on root " + rootId,
|
||||
mStorage.getFile(rootId, path));
|
||||
}
|
||||
|
||||
FileInputStream src = null;
|
||||
FileInputStream dst = null;
|
||||
private void assertDoesNotExist(String rootId, String path) throws Exception {
|
||||
assertNull("Unexpected file found: " + path + " on root " + rootId,
|
||||
mStorage.getFile(rootId, path));
|
||||
}
|
||||
|
||||
private byte[] readFile(String rootId, String path) throws Exception {
|
||||
File file = mStorage.getFile(rootId, path);
|
||||
byte[] buf = null;
|
||||
assertNotNull(file);
|
||||
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
src = new FileInputStream(srcFile);
|
||||
dst = new FileInputStream(dstFile);
|
||||
byte[] srcbuf = Streams.readFully(src);
|
||||
byte[] dstbuf = Streams.readFully(dst);
|
||||
|
||||
MoreAsserts.assertEquals(srcbuf, dstbuf);
|
||||
in = new FileInputStream(file);
|
||||
buf = Streams.readFully(in);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(src);
|
||||
IoUtils.closeQuietly(dst);
|
||||
IoUtils.closeQuietly(in);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void assertCopied(String path) throws Exception {
|
||||
MoreAsserts.assertEquals("Copied file contents differ", readFile(SRC, path),
|
||||
readFile(DST, path));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -72,7 +72,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
private String mAuthority;
|
||||
private SharedPreferences mPrefs;
|
||||
private Map<String, RootInfo> mRoots;
|
||||
private boolean mSimulateReadErrors;
|
||||
private String mSimulateReadErrors;
|
||||
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
@@ -176,6 +176,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
final StubDocument document = new StubDocument(file, mimeType, parentDocument);
|
||||
Log.d(TAG, "Created document " + document.documentId);
|
||||
notifyParentChanged(document.parentId);
|
||||
getContext().getContentResolver().notifyChange(
|
||||
DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
|
||||
@@ -193,7 +194,9 @@ public class StubProvider extends DocumentsProvider {
|
||||
throw new FileNotFoundException();
|
||||
synchronized (mWriteLock) {
|
||||
document.rootInfo.size -= fileSize;
|
||||
mStorage.remove(documentId);
|
||||
}
|
||||
Log.d(TAG, "Document deleted: " + documentId);
|
||||
notifyParentChanged(document.parentId);
|
||||
getContext().getContentResolver().notifyChange(
|
||||
DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
|
||||
@@ -239,7 +242,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
if ("r".equals(mode)) {
|
||||
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(document.file,
|
||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
if (mSimulateReadErrors) {
|
||||
if (docId.equals(mSimulateReadErrors)) {
|
||||
pfd = new ParcelFileDescriptor(pfd) {
|
||||
@Override
|
||||
public void checkError() throws IOException {
|
||||
@@ -257,8 +260,8 @@ public class StubProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void simulateReadErrors(boolean b) {
|
||||
mSimulateReadErrors = b;
|
||||
public void simulateReadErrorsForFile(Uri uri) {
|
||||
mSimulateReadErrors = DocumentsContract.getDocumentId(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,6 +287,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
InputStream inputStream = null;
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
Log.d(TAG, "Opening write stream on file " + document.documentId);
|
||||
inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPipe);
|
||||
outputStream = new FileOutputStream(document.file);
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
@@ -312,6 +316,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
} finally {
|
||||
IoUtils.closeQuietly(inputStream);
|
||||
IoUtils.closeQuietly(outputStream);
|
||||
Log.d(TAG, "Closing write stream on file " + document.documentId);
|
||||
notifyParentChanged(document.parentId);
|
||||
getContext().getContentResolver().notifyChange(
|
||||
DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
|
||||
@@ -408,6 +413,7 @@ public class StubProvider extends DocumentsProvider {
|
||||
@VisibleForTesting
|
||||
public Uri createFile(String rootId, String path, String mimeType, byte[] content)
|
||||
throws FileNotFoundException, IOException {
|
||||
Log.d(TAG, "Creating file " + rootId + ":" + path);
|
||||
StubDocument root = mRoots.get(rootId).rootDocument;
|
||||
if (root == null) {
|
||||
throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
|
||||
@@ -417,6 +423,9 @@ public class StubProvider extends DocumentsProvider {
|
||||
if (parent == null) {
|
||||
parent = mStorage.get(createFile(rootId, file.getParentFile().getPath(),
|
||||
DocumentsContract.Document.MIME_TYPE_DIR, null));
|
||||
Log.d(TAG, "Created parent " + parent.documentId);
|
||||
} else {
|
||||
Log.d(TAG, "Found parent " + parent.documentId);
|
||||
}
|
||||
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
|
||||
Reference in New Issue
Block a user