2 * Copyright (C) 2012 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.cyanogenmod.filemanager.ui.policy;
19 import android.app.AlertDialog;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.text.Html;
23 import android.text.Spanned;
25 import com.cyanogenmod.filemanager.R;
26 import com.cyanogenmod.filemanager.console.Console;
27 import com.cyanogenmod.filemanager.console.NoSuchFileOrDirectory;
28 import com.cyanogenmod.filemanager.console.RelaunchableException;
29 import com.cyanogenmod.filemanager.listeners.OnRequestRefreshListener;
30 import com.cyanogenmod.filemanager.listeners.OnSelectionListener;
31 import com.cyanogenmod.filemanager.model.FileSystemObject;
32 import com.cyanogenmod.filemanager.preferences.Bookmarks;
33 import com.cyanogenmod.filemanager.util.CommandHelper;
34 import com.cyanogenmod.filemanager.util.DialogHelper;
35 import com.cyanogenmod.filemanager.util.ExceptionUtil;
36 import com.cyanogenmod.filemanager.util.ExceptionUtil.OnRelaunchCommandResult;
37 import com.cyanogenmod.filemanager.util.FileHelper;
40 import java.util.ArrayList;
41 import java.util.List;
44 * A class with the convenience methods for resolve copy/move related actions
46 public final class CopyMoveActionPolicy extends ActionsPolicy {
51 private enum COPY_MOVE_OPERATION {
60 * A class that holds a relationship between a source {@link File} and
61 * his destination {@link File}
63 public static class LinkedResource implements Comparable<LinkedResource> {
68 * Constructor of <code>LinkedResource</code>
70 * @param src The source file system object
71 * @param dst The destination file system object
73 public LinkedResource(File src, File dst) {
83 public int compareTo(LinkedResource another) {
84 return this.mSrc.compareTo(another.mSrc);
89 * Method that remove an existing file system object.
91 * @param ctx The current context
92 * @param fso The file system object
93 * @param newName The new name of the object
94 * @param onSelectionListener The listener for obtain selection information (required)
95 * @param onRequestRefreshListener The listener for request a refresh (optional)
97 public static void renameFileSystemObject(
99 final FileSystemObject fso,
100 final String newName,
101 final OnSelectionListener onSelectionListener,
102 final OnRequestRefreshListener onRequestRefreshListener) {
104 // Create the destination filename
105 File dst = new File(fso.getParent(), newName);
106 File src = new File(fso.getFullPath());
109 LinkedResource linkRes = new LinkedResource(src, dst);
110 List<LinkedResource> files = new ArrayList<LinkedResource>(1);
114 copyOrMoveFileSystemObjects(
116 COPY_MOVE_OPERATION.RENAME,
119 onRequestRefreshListener);
123 * Method that copy an existing file system object.
125 * @param ctx The current context
126 * @param fso The file system object
127 * @param onSelectionListener The listener for obtain selection information (required)
128 * @param onRequestRefreshListener The listener for request a refresh (optional)
130 public static void createCopyFileSystemObject(
132 final FileSystemObject fso,
133 final OnSelectionListener onSelectionListener,
134 final OnRequestRefreshListener onRequestRefreshListener) {
136 // Create a non-existing name
137 List<FileSystemObject> curFiles = onSelectionListener.onRequestCurrentItems();
139 FileHelper.createNonExistingName(
140 ctx, curFiles, fso.getName(), R.string.create_copy_regexp);
141 final File dst = new File(fso.getParent(), newName);
142 File src = new File(fso.getFullPath());
145 LinkedResource linkRes = new LinkedResource(src, dst);
146 List<LinkedResource> files = new ArrayList<LinkedResource>(1);
150 copyOrMoveFileSystemObjects(
152 COPY_MOVE_OPERATION.CREATE_COPY,
155 onRequestRefreshListener);
159 * Method that copy an existing file system object.
161 * @param ctx The current context
162 * @param files The list of files to copy
163 * @param onSelectionListener The listener for obtain selection information (required)
164 * @param onRequestRefreshListener The listener for request a refresh (optional)
166 public static void copyFileSystemObjects(
168 final List<LinkedResource> files,
169 final OnSelectionListener onSelectionListener,
170 final OnRequestRefreshListener onRequestRefreshListener) {
172 copyOrMoveFileSystemObjects(
174 COPY_MOVE_OPERATION.COPY,
177 onRequestRefreshListener);
181 * Method that copy an existing file system object.
183 * @param ctx The current context
184 * @param files The list of files to move
185 * @param onSelectionListener The listener for obtain selection information (required)
186 * @param onRequestRefreshListener The listener for request a refresh (optional)
188 public static void moveFileSystemObjects(
190 final List<LinkedResource> files,
191 final OnSelectionListener onSelectionListener,
192 final OnRequestRefreshListener onRequestRefreshListener) {
194 copyOrMoveFileSystemObjects(
196 COPY_MOVE_OPERATION.MOVE,
199 onRequestRefreshListener);
203 * Method that copy an existing file system object.
205 * @param ctx The current context
206 * @param operation Indicates the operation to do
207 * @param files The list of source/destination files to copy
208 * @param onSelectionListener The listener for obtain selection information (required)
209 * @param onRequestRefreshListener The listener for request a refresh (optional)
211 private static void copyOrMoveFileSystemObjects(
213 final COPY_MOVE_OPERATION operation,
214 final List<LinkedResource> files,
215 final OnSelectionListener onSelectionListener,
216 final OnRequestRefreshListener onRequestRefreshListener) {
218 // Some previous checks prior to execute
219 // 1.- Listener couldn't be null
220 if (onSelectionListener == null) {
222 DialogHelper.createErrorDialog(ctx,
223 R.string.error_title,
224 R.string.msgs_illegal_argument);
225 DialogHelper.delegateDialogShow(ctx, dialog);
228 // 2.- All the destination files must have the same parent and it must be currentDirectory,
230 final String currentDirectory = onSelectionListener.onRequestCurrentDir();
231 int cc = files.size();
232 for (int i = 0; i < cc; i++) {
233 LinkedResource linkedRes = files.get(i);
234 if (linkedRes.mSrc == null || linkedRes.mDst == null) {
236 DialogHelper.createErrorDialog(ctx,
237 R.string.error_title,
238 R.string.msgs_illegal_argument);
239 DialogHelper.delegateDialogShow(ctx, dialog);
242 if (linkedRes.mDst.getParent() == null ||
243 linkedRes.mDst.getParent().compareTo(currentDirectory) != 0) {
245 DialogHelper.createErrorDialog(ctx,
246 R.string.error_title,
247 R.string.msgs_illegal_argument);
248 DialogHelper.delegateDialogShow(ctx, dialog);
252 // 3.- Check the operation consistency
253 if (operation.equals(COPY_MOVE_OPERATION.MOVE)
254 || operation.equals(COPY_MOVE_OPERATION.COPY)) {
255 if (!checkCopyOrMoveConsistency(ctx, files, currentDirectory, operation)) {
260 // The callable interface
261 final BackgroundCallable callable = new BackgroundCallable() {
263 private int mCurrent = 0;
264 final Context mCtx = ctx;
265 final COPY_MOVE_OPERATION mOperation = operation;
266 final List<LinkedResource> mFiles = files;
267 final OnRequestRefreshListener mOnRequestRefreshListener = onRequestRefreshListener;
269 final Object mSync = new Object();
273 public int getDialogTitle() {
274 return this.mOperation.equals(COPY_MOVE_OPERATION.MOVE)
275 || this.mOperation.equals(COPY_MOVE_OPERATION.RENAME) ?
276 R.string.waiting_dialog_moving_title :
277 R.string.waiting_dialog_copying_title;
280 public int getDialogIcon() {
284 public boolean isDialogCancellable() {
289 public Spanned requestProgress() {
290 File src = this.mFiles.get(this.mCurrent).mSrc;
291 File dst = this.mFiles.get(this.mCurrent).mDst;
293 // Return the current operation
295 this.mCtx.getResources().
297 this.mOperation.equals(COPY_MOVE_OPERATION.MOVE)
298 || this.mOperation.equals(COPY_MOVE_OPERATION.RENAME) ?
299 R.string.waiting_dialog_moving_msg :
300 R.string.waiting_dialog_copying_msg,
301 src.getAbsolutePath(),
302 dst.getAbsolutePath());
303 return Html.fromHtml(progress);
306 private void refreshUIAfterCompletion() {
307 // Remove orphan bookmark paths
309 for (LinkedResource linkedFiles : files) {
310 Bookmarks.deleteOrphanBookmarks(ctx, linkedFiles.mSrc.getAbsolutePath());
314 //Operation complete. Refresh
315 if (this.mOnRequestRefreshListener != null) {
316 // The reference is not the same, so refresh the complete navigation view
317 this.mOnRequestRefreshListener.onRequestRefresh(null, true);
322 public void onSuccess() {
323 refreshUIAfterCompletion();
324 ActionsPolicy.showOperationSuccessMsg(ctx);
328 public void doInBackground(Object... params) throws Throwable {
331 // This method expect to receive
332 // 1.- BackgroundAsyncTask
333 BackgroundAsyncTask task = (BackgroundAsyncTask)params[0];
335 int cc2 = this.mFiles.size();
336 for (int i = 0; i < cc2; i++) {
337 File src = this.mFiles.get(i).mSrc;
338 File dst = this.mFiles.get(i).mDst;
340 doOperation(this.mCtx, src, dst, this.mOperation);
344 if (this.mCurrent < this.mFiles.size()) {
345 task.onRequestProgress();
351 public void onCancel() {
352 if (mSrcConsole != null) {
353 mSrcConsole.onCancel();
355 if (mDstConsole != null) {
356 mDstConsole.onCancel();
358 if (mOnRequestRefreshListener != null) {
359 mOnRequestRefreshListener.onCancel();
361 refreshUIAfterCompletion();
364 // Handles required for issuing command death to the consoles
365 private Console mSrcConsole;
366 private Console mDstConsole;
369 * Method that copy or move the file to another location
371 * @param ctx The current context
372 * @param src The source file
373 * @param dst The destination file
374 * @param operation Indicates the operation to do
376 private void doOperation(
377 Context ctx, File src, File dst, COPY_MOVE_OPERATION operation)
379 // If the source is the same as destiny then don't do the operation
380 if (src.compareTo(dst) == 0) return;
383 // Be sure to append a / if source is a folder (otherwise system crashes
384 // under using absolute paths) Issue: CYAN-2791
385 String source = src.getAbsolutePath() +
386 (src.isDirectory() ? File.separator : "");
387 String dest = dst.getAbsolutePath() +
388 (dst.isDirectory() ? File.separator : "");
391 There is a possibility that the src and dst can have different consoles.
393 - src is from sd card and dst is secure storage
394 This could happen with anything that goes from a real console to a virtual
395 console or visa versa. Here we grab a handle on the console such that we
396 may explicitly kill the actions happening in both consoles.
398 // Need to derive the console for the source
399 mSrcConsole = CommandHelper.ensureConsoleForFile(ctx, null, source);
400 // Need to derive the console for the destination
401 mDstConsole = CommandHelper.ensureConsoleForFile(ctx, null, dest);
404 if (operation.equals(COPY_MOVE_OPERATION.MOVE)
405 || operation.equals(COPY_MOVE_OPERATION.RENAME)) {
409 dst.getAbsolutePath(),
415 dst.getAbsolutePath(),
418 } catch (Exception e) {
419 // Need to be relaunched?
420 if (e instanceof RelaunchableException) {
421 OnRelaunchCommandResult rl = new OnRelaunchCommandResult() {
423 @SuppressWarnings("unqualified-field-access")
424 public void onSuccess() {
425 synchronized (mSync) {
431 @SuppressWarnings("unqualified-field-access")
432 public void onFailed(Throwable cause) {
434 synchronized (mSync) {
439 @SuppressWarnings("unqualified-field-access")
440 public void onCancelled() {
441 synchronized (mSync) {
447 // Translate the exception (and wait for the result)
448 ExceptionUtil.translateException(ctx, e, false, true, rl);
449 synchronized (this.mSync) {
453 // Persist the exception?
454 if (this.mCause != null) {
455 // The exception must be elevated
460 // The exception must be elevated
465 // Check that the operation was completed retrieving the fso modified
466 FileSystemObject fso =
467 CommandHelper.getFileInfo(ctx, dst.getAbsolutePath(), false, null);
469 throw new NoSuchFileOrDirectory(dst.getAbsolutePath());
473 final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);
475 // Prior to execute, we need to check if some of the files will be overwritten
476 List<FileSystemObject> curFiles = onSelectionListener.onRequestCurrentItems();
477 if (curFiles != null) {
478 // Is necessary to ask the user?
479 if (isOverwriteNeeded(files, curFiles)) {
480 //Show a dialog asking the user for overwrite the files
482 DialogHelper.createTwoButtonsQuestionDialog(
484 android.R.string.cancel,
486 R.string.confirm_overwrite,
487 ctx.getString(R.string.msgs_overwrite_files),
488 new DialogInterface.OnClickListener() {
490 public void onClick(DialogInterface alertDialog, int which) {
491 // NEGATIVE (overwrite) POSITIVE (cancel)
492 if (which == DialogInterface.BUTTON_NEGATIVE) {
493 // Execute background task
498 DialogHelper.delegateDialogShow(ctx, dialog);
503 // Execute background task
508 * Method that check if is needed to prompt the user for overwrite prior to do
511 * @param files The list of source/destination files.
512 * @param currentFiles The list of the current files in the destination directory.
513 * @return boolean If is needed to prompt the user for overwrite
515 private static boolean isOverwriteNeeded(
516 List<LinkedResource> files, List<FileSystemObject> currentFiles) {
517 boolean askUser = false;
518 int cc = currentFiles.size();
519 for (int i = 0; i < cc; i++) {
520 int cc2 = files.size();
521 for (int j = 0; j < cc2; j++) {
522 FileSystemObject dst1 = currentFiles.get(i);
523 File dst2 = files.get(j).mDst;
525 // The file exists in the destination directory
526 if (dst1.getFullPath().compareTo(dst2.getAbsolutePath()) == 0) {
538 * Method that check the consistency of copy or move operations.<br/>
540 * The method checks the following rules:<br/>
542 * <li>Any of the files of the copy or move operation can not include the
543 * current directory.</li>
544 * <li>Any of the files of the copy or move operation can not include the
545 * current directory.</li>
548 * @param ctx The current context
549 * @param files The list of source/destination files
550 * @param currentDirectory The current directory
551 * @param operation the operation is copy or move
552 * @return boolean If the consistency is validate successfully
554 private static boolean checkCopyOrMoveConsistency(Context ctx, List<LinkedResource> files,
555 String currentDirectory, final COPY_MOVE_OPERATION operation) {
556 int cc = files.size();
557 for (int i = 0; i < cc; i++) {
558 LinkedResource linkRes = files.get(i);
559 String src = linkRes.mSrc.getAbsolutePath();
560 String dst = linkRes.mDst.getAbsolutePath();
562 // 1.- Current directory can't be moved
563 if (operation.equals(COPY_MOVE_OPERATION.MOVE) &&
564 currentDirectory != null && currentDirectory.startsWith(src)) {
565 // Operation not allowed
567 DialogHelper.createErrorDialog(
569 R.string.error_title,
570 R.string.msgs_unresolved_inconsistencies);
571 DialogHelper.delegateDialogShow(ctx, dialog);
575 // 2.- Destination can't be a child of source
576 if (dst.startsWith(src)) {
577 // Operation not allowed
579 DialogHelper.createErrorDialog(
581 R.string.error_title,
582 R.string.msgs_operation_not_allowed_in_current_directory);
583 DialogHelper.delegateDialogShow(ctx, dialog);