Loading packages/DocumentsUI/res/values/strings.xml +8 −1 Original line number Diff line number Diff line Loading @@ -158,6 +158,8 @@ <string name="copy_preparing">Preparing for copy\u2026</string> <!-- Text shown on the notification while DocumentsUI performs setup in preparation for moving files [CHAR LIMIT=32] --> <string name="move_preparing">Preparing for move\u2026</string> <!-- Text shown on the notification while DocumentsUI performs setup in preparation for deleting files [CHAR LIMIT=32] --> <string name="delete_preparing">Preparing for delete\u2026</string> <!-- Title of the copy error notification [CHAR LIMIT=48] --> <plurals name="copy_error_notification_title"> <item quantity="one">Couldn\'t copy <xliff:g id="count" example="1">%1$d</xliff:g> file</item> Loading @@ -168,6 +170,11 @@ <item quantity="one">Couldn\'t move <xliff:g id="count" example="1">%1$d</xliff:g> file</item> <item quantity="other">Couldn\'t move <xliff:g id="count" example="2">%1$d</xliff:g> files</item> </plurals> <!-- Title of the delete error notification [CHAR LIMIT=48] --> <plurals name="delete_error_notification_title"> <item quantity="one">Couldn\'t delete <xliff:g id="count" example="1">%1$d</xliff:g> file</item> <item quantity="other">Couldn\'t delete <xliff:g id="count" example="2">%1$d</xliff:g> files</item> </plurals> <!-- Second line for notifications saying that more information will be shown after touching [CHAR LIMIT=48] --> <string name="notification_touch_for_details">Touch to view details</string> <!-- Label of a dialog button for retrying a failed operation [CHAR LIMIT=24] --> Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +28 −24 Original line number Diff line number Diff line Loading @@ -125,10 +125,12 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi public static final int REQUEST_COPY_DESTINATION = 1; private static final String TAG = "DirectoryFragment"; static final boolean DEBUG_ENABLE_DND = true; private static final String TAG = "DirectoryFragment"; private static final int LOADER_ID = 42; static final boolean DEBUG_ENABLE_DND = true; private static final int DELETE_UNDO_TIMEOUT = 5000; private static final int DELETE_JOB_DELAY = 5500; private static final String EXTRA_TYPE = "type"; private static final String EXTRA_ROOT = "root"; Loading Loading @@ -328,7 +330,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi : MultiSelectManager.MODE_SINGLE); mSelectionManager.addCallback(new SelectionModeListener()); mModel = new Model(context); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); Loading Loading @@ -827,16 +829,32 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } private void deleteDocuments(final Selection selected) { Context context = getActivity(); String message = Shared.getQuantityString(context, R.plurals.deleting, selected.size()); checkArgument(!selected.isEmpty()); new GetDocumentsTask() { @Override void onDocumentsReady(List<DocumentInfo> docs) { // Hide the files in the UI. final SparseArray<String> toDelete = mAdapter.hide(selected.getAll()); final SparseArray<String> hidden = mAdapter.hide(selected.getAll()); checkState(DELETE_JOB_DELAY > DELETE_UNDO_TIMEOUT); String operationId = FileOperations.delete( getActivity(), docs, getDisplayState().stack, DELETE_JOB_DELAY); showDeleteSnackbar(hidden, operationId); } }.execute(selected); } private void showDeleteSnackbar(final SparseArray<String> hidden, final String jobId) { Context context = getActivity(); String message = Shared.getQuantityString(context, R.plurals.deleting, hidden.size()); // Show a snackbar informing the user that files will be deleted, and give them an option to // cancel. final Activity activity = getActivity(); Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG) Snackbars.makeSnackbar(activity, message, DELETE_UNDO_TIMEOUT) .setAction( R.string.undo, new View.OnClickListener() { Loading @@ -849,22 +867,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi public void onDismissed(Snackbar snackbar, int event) { if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // If the delete was cancelled, just unhide the files. mAdapter.unhide(toDelete); } else { // Actually kick off the delete. mModel.delete( selected, new Model.DeletionListener() { @Override public void onError() { Snackbars.makeSnackbar( activity, R.string.toast_failed_delete, Snackbar.LENGTH_LONG) .show(); } }); FileOperations.cancel(activity, jobId); mAdapter.unhide(hidden); } } }) Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java +0 −95 Original line number Diff line number Diff line Loading @@ -24,11 +24,7 @@ import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; import static com.android.internal.util.Preconditions.checkNotNull; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.os.Looper; import android.provider.DocumentsContract; Loading @@ -39,7 +35,6 @@ import android.util.Log; import com.android.documentsui.BaseActivity.SiblingProvider; import com.android.documentsui.DirectoryResult; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.RootCursorWrapper; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.model.DocumentInfo; Loading @@ -56,7 +51,6 @@ import java.util.Map; public class Model implements SiblingProvider { private static final String TAG = "Model"; private Context mContext; private boolean mIsLoading; private List<UpdateListener> mUpdateListeners = new ArrayList<>(); @Nullable private Cursor mCursor; Loading @@ -73,10 +67,6 @@ public class Model implements SiblingProvider { @Nullable String info; @Nullable String error; Model(Context context) { mContext = context; } /** * Generates a Model ID for a cursor entry that refers to a document. The Model ID is a unique * string that can be used to identify the document referred to by the cursor. Loading Loading @@ -406,91 +396,6 @@ public class Model implements SiblingProvider { return mCursor; } public void delete(Selection selected, DeletionListener listener) { final ContentResolver resolver = mContext.getContentResolver(); new DeleteFilesTask(resolver, listener).execute(selected); } /** * A Task which collects the DocumentInfo for documents that have been marked for deletion, * and actually deletes them. */ private class DeleteFilesTask extends AsyncTask<Selection, Void, Void> { private ContentResolver mResolver; private DeletionListener mListener; private boolean mHadTrouble; /** * @param resolver A ContentResolver for performing the actual file deletions. * @param errorCallback A Runnable that is executed in the event that one or more errors * occurred while copying files. Execution will occur on the UI thread. */ public DeleteFilesTask(ContentResolver resolver, DeletionListener listener) { mResolver = resolver; mListener = listener; } @Override protected Void doInBackground(Selection... selected) { List<DocumentInfo> toDelete = null; try { toDelete = getDocuments(selected[0]); } catch (NullPointerException e) { Log.w(TAG, "Failed to retrieve documents for delete."); mHadTrouble = true; return null; } for (DocumentInfo doc : toDelete) { if (!doc.isDeleteSupported()) { Log.w(TAG, doc + " could not be deleted. Skipping..."); mHadTrouble = true; continue; } ContentProviderClient client = null; try { if (DEBUG) Log.d(TAG, "Deleting: " + doc.displayName); client = DocumentsApplication.acquireUnstableProviderOrThrow( mResolver, doc.derivedUri.getAuthority()); DocumentsContract.deleteDocument(client, doc.derivedUri); } catch (Exception e) { Log.w(TAG, "Failed to delete " + doc, e); mHadTrouble = true; } finally { ContentProviderClient.releaseQuietly(client); } } return null; } @Override protected void onPostExecute(Void _) { if (mHadTrouble) { // TODO show which files failed? b/23720103 mListener.onError(); if (DEBUG) Log.d(TAG, "Deletion task completed. Some deletions failed."); } else { if (DEBUG) Log.d(TAG, "Deletion task completed successfully."); } mListener.onCompletion(); } } static class DeletionListener { /** * Called when deletion has completed (regardless of whether an error occurred). */ void onCompletion() {} /** * Called at the end of a deletion operation that produced one or more errors. */ void onError() {} } void addUpdateListener(UpdateListener listener) { mUpdateListeners.add(listener); } Loading packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java +76 −87 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.services; import static com.android.documentsui.Shared.DEBUG; import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; import android.app.Notification; import android.app.Notification.Builder; import android.content.Context; import android.os.RemoteException; import android.util.Log; import com.android.documentsui.R; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import java.util.List; final class DeleteJob extends Job { private static final String TAG = "DeleteJob"; private List<DocumentInfo> mSrcs; /** * Moves files to a destination identified by {@code destination}. * Performs most work by delegating to CopyJob, then deleting * a file after it has been copied. * * @see @link {@link Job} constructor for most param descriptions. * * @param srcs List of files to delete */ DeleteJob(Context service, Context appContext, Listener listener, String id, DocumentStack stack, List<DocumentInfo> srcs) { super(service, appContext, listener, OPERATION_DELETE, id, stack); this.mSrcs = srcs; } @Override Builder createProgressBuilder() { return super.createProgressBuilder( service.getString(R.string.move_notification_title), R.drawable.ic_menu_copy, service.getString(android.R.string.cancel), R.drawable.ic_cab_cancel); } @Override public Notification getSetupNotification() { return getSetupNotification(service.getString(R.string.delete_preparing)); } @Override Notification getFailureNotification() { return getFailureNotification( R.plurals.delete_error_notification_title, R.drawable.ic_menu_delete); } @Override void start() throws RemoteException { for (DocumentInfo doc : mSrcs) { if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri); if (!deleteDocument(doc)) { Log.w(TAG, "Failed to delete document @ " + doc.derivedUri); onFileFailed(doc); } } } @Override public String toString() { return new StringBuilder() .append("DeleteJob") .append("{") .append("id=" + id) .append("srcs=" + mSrcs) .append(", location=" + stack) .append("}") .toString(); } } Loading
packages/DocumentsUI/res/values/strings.xml +8 −1 Original line number Diff line number Diff line Loading @@ -158,6 +158,8 @@ <string name="copy_preparing">Preparing for copy\u2026</string> <!-- Text shown on the notification while DocumentsUI performs setup in preparation for moving files [CHAR LIMIT=32] --> <string name="move_preparing">Preparing for move\u2026</string> <!-- Text shown on the notification while DocumentsUI performs setup in preparation for deleting files [CHAR LIMIT=32] --> <string name="delete_preparing">Preparing for delete\u2026</string> <!-- Title of the copy error notification [CHAR LIMIT=48] --> <plurals name="copy_error_notification_title"> <item quantity="one">Couldn\'t copy <xliff:g id="count" example="1">%1$d</xliff:g> file</item> Loading @@ -168,6 +170,11 @@ <item quantity="one">Couldn\'t move <xliff:g id="count" example="1">%1$d</xliff:g> file</item> <item quantity="other">Couldn\'t move <xliff:g id="count" example="2">%1$d</xliff:g> files</item> </plurals> <!-- Title of the delete error notification [CHAR LIMIT=48] --> <plurals name="delete_error_notification_title"> <item quantity="one">Couldn\'t delete <xliff:g id="count" example="1">%1$d</xliff:g> file</item> <item quantity="other">Couldn\'t delete <xliff:g id="count" example="2">%1$d</xliff:g> files</item> </plurals> <!-- Second line for notifications saying that more information will be shown after touching [CHAR LIMIT=48] --> <string name="notification_touch_for_details">Touch to view details</string> <!-- Label of a dialog button for retrying a failed operation [CHAR LIMIT=24] --> Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +28 −24 Original line number Diff line number Diff line Loading @@ -125,10 +125,12 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi public static final int REQUEST_COPY_DESTINATION = 1; private static final String TAG = "DirectoryFragment"; static final boolean DEBUG_ENABLE_DND = true; private static final String TAG = "DirectoryFragment"; private static final int LOADER_ID = 42; static final boolean DEBUG_ENABLE_DND = true; private static final int DELETE_UNDO_TIMEOUT = 5000; private static final int DELETE_JOB_DELAY = 5500; private static final String EXTRA_TYPE = "type"; private static final String EXTRA_ROOT = "root"; Loading Loading @@ -328,7 +330,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi : MultiSelectManager.MODE_SINGLE); mSelectionManager.addCallback(new SelectionModeListener()); mModel = new Model(context); mModel = new Model(); mModel.addUpdateListener(mAdapter); mModel.addUpdateListener(mModelUpdateListener); Loading Loading @@ -827,16 +829,32 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi } private void deleteDocuments(final Selection selected) { Context context = getActivity(); String message = Shared.getQuantityString(context, R.plurals.deleting, selected.size()); checkArgument(!selected.isEmpty()); new GetDocumentsTask() { @Override void onDocumentsReady(List<DocumentInfo> docs) { // Hide the files in the UI. final SparseArray<String> toDelete = mAdapter.hide(selected.getAll()); final SparseArray<String> hidden = mAdapter.hide(selected.getAll()); checkState(DELETE_JOB_DELAY > DELETE_UNDO_TIMEOUT); String operationId = FileOperations.delete( getActivity(), docs, getDisplayState().stack, DELETE_JOB_DELAY); showDeleteSnackbar(hidden, operationId); } }.execute(selected); } private void showDeleteSnackbar(final SparseArray<String> hidden, final String jobId) { Context context = getActivity(); String message = Shared.getQuantityString(context, R.plurals.deleting, hidden.size()); // Show a snackbar informing the user that files will be deleted, and give them an option to // cancel. final Activity activity = getActivity(); Snackbars.makeSnackbar(activity, message, Snackbar.LENGTH_LONG) Snackbars.makeSnackbar(activity, message, DELETE_UNDO_TIMEOUT) .setAction( R.string.undo, new View.OnClickListener() { Loading @@ -849,22 +867,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi public void onDismissed(Snackbar snackbar, int event) { if (event == Snackbar.Callback.DISMISS_EVENT_ACTION) { // If the delete was cancelled, just unhide the files. mAdapter.unhide(toDelete); } else { // Actually kick off the delete. mModel.delete( selected, new Model.DeletionListener() { @Override public void onError() { Snackbars.makeSnackbar( activity, R.string.toast_failed_delete, Snackbar.LENGTH_LONG) .show(); } }); FileOperations.cancel(activity, jobId); mAdapter.unhide(hidden); } } }) Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java +0 −95 Original line number Diff line number Diff line Loading @@ -24,11 +24,7 @@ import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorString; import static com.android.internal.util.Preconditions.checkNotNull; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.os.Looper; import android.provider.DocumentsContract; Loading @@ -39,7 +35,6 @@ import android.util.Log; import com.android.documentsui.BaseActivity.SiblingProvider; import com.android.documentsui.DirectoryResult; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.RootCursorWrapper; import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.model.DocumentInfo; Loading @@ -56,7 +51,6 @@ import java.util.Map; public class Model implements SiblingProvider { private static final String TAG = "Model"; private Context mContext; private boolean mIsLoading; private List<UpdateListener> mUpdateListeners = new ArrayList<>(); @Nullable private Cursor mCursor; Loading @@ -73,10 +67,6 @@ public class Model implements SiblingProvider { @Nullable String info; @Nullable String error; Model(Context context) { mContext = context; } /** * Generates a Model ID for a cursor entry that refers to a document. The Model ID is a unique * string that can be used to identify the document referred to by the cursor. Loading Loading @@ -406,91 +396,6 @@ public class Model implements SiblingProvider { return mCursor; } public void delete(Selection selected, DeletionListener listener) { final ContentResolver resolver = mContext.getContentResolver(); new DeleteFilesTask(resolver, listener).execute(selected); } /** * A Task which collects the DocumentInfo for documents that have been marked for deletion, * and actually deletes them. */ private class DeleteFilesTask extends AsyncTask<Selection, Void, Void> { private ContentResolver mResolver; private DeletionListener mListener; private boolean mHadTrouble; /** * @param resolver A ContentResolver for performing the actual file deletions. * @param errorCallback A Runnable that is executed in the event that one or more errors * occurred while copying files. Execution will occur on the UI thread. */ public DeleteFilesTask(ContentResolver resolver, DeletionListener listener) { mResolver = resolver; mListener = listener; } @Override protected Void doInBackground(Selection... selected) { List<DocumentInfo> toDelete = null; try { toDelete = getDocuments(selected[0]); } catch (NullPointerException e) { Log.w(TAG, "Failed to retrieve documents for delete."); mHadTrouble = true; return null; } for (DocumentInfo doc : toDelete) { if (!doc.isDeleteSupported()) { Log.w(TAG, doc + " could not be deleted. Skipping..."); mHadTrouble = true; continue; } ContentProviderClient client = null; try { if (DEBUG) Log.d(TAG, "Deleting: " + doc.displayName); client = DocumentsApplication.acquireUnstableProviderOrThrow( mResolver, doc.derivedUri.getAuthority()); DocumentsContract.deleteDocument(client, doc.derivedUri); } catch (Exception e) { Log.w(TAG, "Failed to delete " + doc, e); mHadTrouble = true; } finally { ContentProviderClient.releaseQuietly(client); } } return null; } @Override protected void onPostExecute(Void _) { if (mHadTrouble) { // TODO show which files failed? b/23720103 mListener.onError(); if (DEBUG) Log.d(TAG, "Deletion task completed. Some deletions failed."); } else { if (DEBUG) Log.d(TAG, "Deletion task completed successfully."); } mListener.onCompletion(); } } static class DeletionListener { /** * Called when deletion has completed (regardless of whether an error occurred). */ void onCompletion() {} /** * Called at the end of a deletion operation that produced one or more errors. */ void onError() {} } void addUpdateListener(UpdateListener listener) { mUpdateListeners.add(listener); } Loading
packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java +76 −87 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.services; import static com.android.documentsui.Shared.DEBUG; import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; import android.app.Notification; import android.app.Notification.Builder; import android.content.Context; import android.os.RemoteException; import android.util.Log; import com.android.documentsui.R; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; import java.util.List; final class DeleteJob extends Job { private static final String TAG = "DeleteJob"; private List<DocumentInfo> mSrcs; /** * Moves files to a destination identified by {@code destination}. * Performs most work by delegating to CopyJob, then deleting * a file after it has been copied. * * @see @link {@link Job} constructor for most param descriptions. * * @param srcs List of files to delete */ DeleteJob(Context service, Context appContext, Listener listener, String id, DocumentStack stack, List<DocumentInfo> srcs) { super(service, appContext, listener, OPERATION_DELETE, id, stack); this.mSrcs = srcs; } @Override Builder createProgressBuilder() { return super.createProgressBuilder( service.getString(R.string.move_notification_title), R.drawable.ic_menu_copy, service.getString(android.R.string.cancel), R.drawable.ic_cab_cancel); } @Override public Notification getSetupNotification() { return getSetupNotification(service.getString(R.string.delete_preparing)); } @Override Notification getFailureNotification() { return getFailureNotification( R.plurals.delete_error_notification_title, R.drawable.ic_menu_delete); } @Override void start() throws RemoteException { for (DocumentInfo doc : mSrcs) { if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri); if (!deleteDocument(doc)) { Log.w(TAG, "Failed to delete document @ " + doc.derivedUri); onFileFailed(doc); } } } @Override public String toString() { return new StringBuilder() .append("DeleteJob") .append("{") .append("id=" + id) .append("srcs=" + mSrcs) .append(", location=" + stack) .append("}") .toString(); } }