DO NOT MERGE: Don't create DocumentInfo instances in background.

Cursor access is not threadsafe resulting in operations on incorrect files.

Bug: 30082168
Change-Id: Ib36d5b7acdee9463b6ebd2553172e5ecb4d70857
This commit is contained in:
Steve McKay
2016-08-23 15:35:53 -07:00
parent dd056b9422
commit b818058f4e

View File

@@ -753,21 +753,17 @@ public class DirectoryFragment extends Fragment
private void openDocuments(final Selection selected) { private void openDocuments(final Selection selected) {
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_OPEN);
new GetDocumentsTask() { // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
@Override List<DocumentInfo> docs = mModel.getDocuments(selected);
void onDocumentsReady(List<DocumentInfo> docs) {
// TODO: Implement support in Files activity for opening multiple docs. // TODO: Implement support in Files activity for opening multiple docs.
BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs); BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
} }
}.execute(selected);
}
private void shareDocuments(final Selection selected) { private void shareDocuments(final Selection selected) {
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SHARE); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SHARE);
new GetDocumentsTask() { // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
@Override List<DocumentInfo> docs = mModel.getDocuments(selected);
void onDocumentsReady(List<DocumentInfo> docs) {
Intent intent; Intent intent;
// Filter out directories and virtual files - those can't be shared. // Filter out directories and virtual files - those can't be shared.
@@ -809,8 +805,6 @@ public class DirectoryFragment extends Fragment
intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via)); intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
startActivity(intent); startActivity(intent);
} }
}.execute(selected);
}
private String generateDeleteMessage(final List<DocumentInfo> docs) { private String generateDeleteMessage(final List<DocumentInfo> docs) {
String message; String message;
@@ -855,9 +849,9 @@ public class DirectoryFragment extends Fragment
assert(!selected.isEmpty()); assert(!selected.isEmpty());
final DocumentInfo srcParent = getDisplayState().stack.peek(); final DocumentInfo srcParent = getDisplayState().stack.peek();
new GetDocumentsTask() {
@Override // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
void onDocumentsReady(final List<DocumentInfo> docs) { List<DocumentInfo> docs = mModel.getDocuments(selected);
TextView message = TextView message =
(TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null); (TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null);
@@ -878,6 +872,7 @@ public class DirectoryFragment extends Fragment
.setPositiveButton( .setPositiveButton(
android.R.string.yes, android.R.string.yes,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
// Finish selection mode first which clears selection so we // Finish selection mode first which clears selection so we
// don't end up trying to deselect deleted documents. // don't end up trying to deselect deleted documents.
@@ -900,8 +895,6 @@ public class DirectoryFragment extends Fragment
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show(); .show();
} }
}.execute(selected);
}
private void transferDocuments(final Selection selected, final @OpType int mode) { private void transferDocuments(final Selection selected, final @OpType int mode) {
if(mode == FileOperationService.OPERATION_COPY) { if(mode == FileOperationService.OPERATION_COPY) {
@@ -934,9 +927,8 @@ public class DirectoryFragment extends Fragment
? R.string.menu_move : R.string.menu_copy; ? R.string.menu_move : R.string.menu_copy;
intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId)); intent.putExtra(DocumentsContract.EXTRA_PROMPT, getResources().getString(drawerTitleId));
new GetDocumentsTask() { // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
@Override List<DocumentInfo> docs = mModel.getDocuments(selected);
void onDocumentsReady(List<DocumentInfo> docs) {
// TODO: Can this move to Fragment bundle state? // TODO: Can this move to Fragment bundle state?
getDisplayState().selectedDocumentsForCopy = docs; getDisplayState().selectedDocumentsForCopy = docs;
@@ -952,9 +944,6 @@ public class DirectoryFragment extends Fragment
startActivityForResult(intent, REQUEST_COPY_DESTINATION); startActivityForResult(intent, REQUEST_COPY_DESTINATION);
} }
}.execute(selected);
}
private static boolean hasDirectory(List<DocumentInfo> docs) { private static boolean hasDirectory(List<DocumentInfo> docs) {
for (DocumentInfo info : docs) { for (DocumentInfo info : docs) {
if (Document.MIME_TYPE_DIR.equals(info.mimeType)) { if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
@@ -971,13 +960,10 @@ public class DirectoryFragment extends Fragment
// Rename option is only available in menu when 1 document selected // Rename option is only available in menu when 1 document selected
assert(selected.size() == 1); assert(selected.size() == 1);
new GetDocumentsTask() { // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
@Override List<DocumentInfo> docs = mModel.getDocuments(selected);
void onDocumentsReady(List<DocumentInfo> docs) {
RenameDocumentFragment.show(getFragmentManager(), docs.get(0)); RenameDocumentFragment.show(getFragmentManager(), docs.get(0));
} }
}.execute(selected);
}
@Override @Override
public void initDocumentHolder(DocumentHolder holder) { public void initDocumentHolder(DocumentHolder holder) {
@@ -1135,11 +1121,11 @@ public class DirectoryFragment extends Fragment
} }
} }
void copySelectionToClipboard(Selection selection) { void copySelectionToClipboard(Selection selected) {
assert(!selection.isEmpty()); assert(!selected.isEmpty());
new GetDocumentsTask() {
@Override // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
void onDocumentsReady(List<DocumentInfo> docs) { List<DocumentInfo> docs = mModel.getDocuments(selected);
mClipper.clipDocuments(docs); mClipper.clipDocuments(docs);
Activity activity = getActivity(); Activity activity = getActivity();
Snackbars.makeSnackbar(activity, Snackbars.makeSnackbar(activity,
@@ -1147,8 +1133,6 @@ public class DirectoryFragment extends Fragment
R.plurals.clipboard_files_clipped, docs.size(), docs.size()), R.plurals.clipboard_files_clipped, docs.size(), docs.size()),
Snackbar.LENGTH_SHORT).show(); Snackbar.LENGTH_SHORT).show();
} }
}.execute(selection);
}
public void pasteFromClipboard() { public void pasteFromClipboard() {
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD); Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);
@@ -1456,25 +1440,6 @@ public class DirectoryFragment extends Fragment
mShadowView.draw(canvas); mShadowView.draw(canvas);
} }
} }
/**
* Abstract task providing support for loading documents *off*
* the main thread. And if it isn't obvious, creating a list
* of documents (especially large lists) can be pretty expensive.
*/
private abstract class GetDocumentsTask
extends AsyncTask<Selection, Void, List<DocumentInfo>> {
@Override
protected final List<DocumentInfo> doInBackground(Selection... selected) {
return mModel.getDocuments(selected[0]);
}
@Override
protected final void onPostExecute(List<DocumentInfo> docs) {
onDocumentsReady(docs);
}
abstract void onDocumentsReady(List<DocumentInfo> docs);
}
@Override @Override
public boolean isSelected(String modelId) { public boolean isSelected(String modelId) {