OSDN Git Service

Fixed ANR associated with SecureStorage
[android-x86/packages-apps-CMFileManager.git] / src / com / cyanogenmod / filemanager / ui / policy / CopyMoveActionPolicy.java
1 /*
2  * Copyright (C) 2012 The CyanogenMod Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.cyanogenmod.filemanager.ui.policy;
18
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;
24
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;
38
39 import java.io.File;
40 import java.util.ArrayList;
41 import java.util.List;
42
43 /**
44  * A class with the convenience methods for resolve copy/move related actions
45  */
46 public final class CopyMoveActionPolicy extends ActionsPolicy {
47
48     /**
49      * @hide
50      */
51     private enum COPY_MOVE_OPERATION {
52         COPY,
53         MOVE,
54         RENAME,
55         CREATE_COPY,
56     }
57
58
59     /**
60      * A class that holds a relationship between a source {@link File} and
61      * his destination {@link File}
62      */
63     public static class LinkedResource implements Comparable<LinkedResource> {
64         final File mSrc;
65         final File mDst;
66
67         /**
68          * Constructor of <code>LinkedResource</code>
69          *
70          * @param src The source file system object
71          * @param dst The destination file system object
72          */
73         public LinkedResource(File src, File dst) {
74             super();
75             this.mSrc = src;
76             this.mDst = dst;
77         }
78
79         /**
80          * {@inheritDoc}
81          */
82         @Override
83         public int compareTo(LinkedResource another) {
84             return this.mSrc.compareTo(another.mSrc);
85         }
86     }
87
88     /**
89      * Method that remove an existing file system object.
90      *
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)
96      */
97     public static void renameFileSystemObject(
98             final Context ctx,
99             final FileSystemObject fso,
100             final String newName,
101             final OnSelectionListener onSelectionListener,
102             final OnRequestRefreshListener onRequestRefreshListener) {
103
104         // Create the destination filename
105         File dst = new File(fso.getParent(), newName);
106         File src = new File(fso.getFullPath());
107
108         // Create arguments
109         LinkedResource linkRes = new LinkedResource(src, dst);
110         List<LinkedResource> files = new ArrayList<LinkedResource>(1);
111         files.add(linkRes);
112
113         // Internal copy
114         copyOrMoveFileSystemObjects(
115                 ctx,
116                 COPY_MOVE_OPERATION.RENAME,
117                 files,
118                 onSelectionListener,
119                 onRequestRefreshListener);
120     }
121
122     /**
123      * Method that copy an existing file system object.
124      *
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)
129      */
130     public static void createCopyFileSystemObject(
131             final Context ctx,
132             final FileSystemObject fso,
133             final OnSelectionListener onSelectionListener,
134             final OnRequestRefreshListener onRequestRefreshListener) {
135
136         // Create a non-existing name
137         List<FileSystemObject> curFiles = onSelectionListener.onRequestCurrentItems();
138         String  newName =
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());
143
144         // Create arguments
145         LinkedResource linkRes = new LinkedResource(src, dst);
146         List<LinkedResource> files = new ArrayList<LinkedResource>(1);
147         files.add(linkRes);
148
149         // Internal copy
150         copyOrMoveFileSystemObjects(
151                 ctx,
152                 COPY_MOVE_OPERATION.CREATE_COPY,
153                 files,
154                 onSelectionListener,
155                 onRequestRefreshListener);
156     }
157
158     /**
159      * Method that copy an existing file system object.
160      *
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)
165      */
166     public static void copyFileSystemObjects(
167             final Context ctx,
168             final List<LinkedResource> files,
169             final OnSelectionListener onSelectionListener,
170             final OnRequestRefreshListener onRequestRefreshListener) {
171         // Internal copy
172         copyOrMoveFileSystemObjects(
173                 ctx,
174                 COPY_MOVE_OPERATION.COPY,
175                 files,
176                 onSelectionListener,
177                 onRequestRefreshListener);
178     }
179
180     /**
181      * Method that copy an existing file system object.
182      *
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)
187      */
188     public static void moveFileSystemObjects(
189             final Context ctx,
190             final List<LinkedResource> files,
191             final OnSelectionListener onSelectionListener,
192             final OnRequestRefreshListener onRequestRefreshListener) {
193         // Internal move
194         copyOrMoveFileSystemObjects(
195                 ctx,
196                 COPY_MOVE_OPERATION.MOVE,
197                 files,
198                 onSelectionListener,
199                 onRequestRefreshListener);
200     }
201
202     /**
203      * Method that copy an existing file system object.
204      *
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)
210      */
211     private static void copyOrMoveFileSystemObjects(
212             final Context ctx,
213             final COPY_MOVE_OPERATION operation,
214             final List<LinkedResource> files,
215             final OnSelectionListener onSelectionListener,
216             final OnRequestRefreshListener onRequestRefreshListener) {
217
218         // Some previous checks prior to execute
219         // 1.- Listener couldn't be null
220         if (onSelectionListener == null) {
221             AlertDialog dialog =
222                     DialogHelper.createErrorDialog(ctx,
223                             R.string.error_title,
224                             R.string.msgs_illegal_argument);
225             DialogHelper.delegateDialogShow(ctx, dialog);
226             return;
227         }
228         // 2.- All the destination files must have the same parent and it must be currentDirectory,
229         // and not be null
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) {
235                 AlertDialog dialog =
236                         DialogHelper.createErrorDialog(ctx,
237                                 R.string.error_title,
238                                 R.string.msgs_illegal_argument);
239                 DialogHelper.delegateDialogShow(ctx, dialog);
240                 return;
241             }
242             if (linkedRes.mDst.getParent() == null ||
243                 linkedRes.mDst.getParent().compareTo(currentDirectory) != 0) {
244                 AlertDialog dialog =
245                         DialogHelper.createErrorDialog(ctx,
246                                 R.string.error_title,
247                                 R.string.msgs_illegal_argument);
248                 DialogHelper.delegateDialogShow(ctx, dialog);
249                 return;
250             }
251         }
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)) {
256                 return;
257             }
258         }
259
260         // The callable interface
261         final BackgroundCallable callable = new BackgroundCallable() {
262             // The current items
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;
268
269             final Object mSync = new Object();
270             Throwable mCause;
271
272             @Override
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;
278             }
279             @Override
280             public int getDialogIcon() {
281                 return 0;
282             }
283             @Override
284             public boolean isDialogCancellable() {
285                 return false;
286             }
287
288             @Override
289             public Spanned requestProgress() {
290                 File src = this.mFiles.get(this.mCurrent).mSrc;
291                 File dst = this.mFiles.get(this.mCurrent).mDst;
292
293                 // Return the current operation
294                 String progress =
295                       this.mCtx.getResources().
296                           getString(
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);
304             }
305
306             private void refreshUIAfterCompletion() {
307                 // Remove orphan bookmark paths
308                 if (files != null) {
309                     for (LinkedResource linkedFiles : files) {
310                         Bookmarks.deleteOrphanBookmarks(ctx, linkedFiles.mSrc.getAbsolutePath());
311                     }
312                 }
313
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);
318                 }
319             }
320
321             @Override
322             public void onSuccess() {
323                 refreshUIAfterCompletion();
324                 ActionsPolicy.showOperationSuccessMsg(ctx);
325             }
326
327             @Override
328             public void doInBackground(Object... params) throws Throwable {
329                 this.mCause = null;
330
331                 // This method expect to receive
332                 // 1.- BackgroundAsyncTask
333                 BackgroundAsyncTask task = (BackgroundAsyncTask)params[0];
334
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;
339
340                     doOperation(this.mCtx, src, dst, this.mOperation);
341
342                     // Next file
343                     this.mCurrent++;
344                     if (this.mCurrent < this.mFiles.size()) {
345                         task.onRequestProgress();
346                     }
347                 }
348             }
349
350             @Override
351             public void onCancel() {
352                 if (mSrcConsole != null) {
353                     mSrcConsole.onCancel();
354                 }
355                 if (mDstConsole != null) {
356                     mDstConsole.onCancel();
357                 }
358                 if (mOnRequestRefreshListener != null) {
359                     mOnRequestRefreshListener.onCancel();
360                 }
361                 refreshUIAfterCompletion();
362             }
363
364             // Handles required for issuing command death to the consoles
365             private Console mSrcConsole;
366             private Console mDstConsole;
367
368             /**
369              * Method that copy or move the file to another location
370              *
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
375              */
376             private void doOperation(
377                     Context ctx, File src, File dst, COPY_MOVE_OPERATION operation)
378                     throws Throwable {
379                 // If the source is the same as destiny then don't do the operation
380                 if (src.compareTo(dst) == 0) return;
381
382                 try {
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 : "");
389
390                     /*
391                         There is a possibility that the src and dst can have different consoles.
392                         A possible case:
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.
397                      */
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);
402
403                     // Copy or move?
404                     if (operation.equals(COPY_MOVE_OPERATION.MOVE)
405                             || operation.equals(COPY_MOVE_OPERATION.RENAME)) {
406                         CommandHelper.move(
407                                 ctx,
408                                 source,
409                                 dst.getAbsolutePath(),
410                                 mSrcConsole);
411                     } else {
412                         CommandHelper.copy(
413                                 ctx,
414                                 source,
415                                 dst.getAbsolutePath(),
416                                 mSrcConsole);
417                     }
418                 } catch (Exception e) {
419                     // Need to be relaunched?
420                     if (e instanceof RelaunchableException) {
421                         OnRelaunchCommandResult rl = new OnRelaunchCommandResult() {
422                             @Override
423                             @SuppressWarnings("unqualified-field-access")
424                             public void onSuccess() {
425                                 synchronized (mSync) {
426                                     mSync.notify();
427                                 }
428                             }
429
430                             @Override
431                             @SuppressWarnings("unqualified-field-access")
432                             public void onFailed(Throwable cause) {
433                                 mCause = cause;
434                                 synchronized (mSync) {
435                                     mSync.notify();
436                                 }
437                             }
438                             @Override
439                             @SuppressWarnings("unqualified-field-access")
440                             public void onCancelled() {
441                                 synchronized (mSync) {
442                                     mSync.notify();
443                                 }
444                             }
445                         };
446
447                         // Translate the exception (and wait for the result)
448                         ExceptionUtil.translateException(ctx, e, false, true, rl);
449                         synchronized (this.mSync) {
450                             this.mSync.wait();
451                         }
452
453                         // Persist the exception?
454                         if (this.mCause != null) {
455                             // The exception must be elevated
456                             throw this.mCause;
457                         }
458
459                     } else {
460                         // The exception must be elevated
461                         throw e;
462                     }
463                 }
464
465                 // Check that the operation was completed retrieving the fso modified
466                 FileSystemObject fso =
467                         CommandHelper.getFileInfo(ctx, dst.getAbsolutePath(), false, null);
468                 if (fso == null) {
469                     throw new NoSuchFileOrDirectory(dst.getAbsolutePath());
470                 }
471             }
472         };
473         final BackgroundAsyncTask task = new BackgroundAsyncTask(ctx, callable);
474
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
481                 AlertDialog dialog =
482                         DialogHelper.createTwoButtonsQuestionDialog(
483                                 ctx,
484                                 android.R.string.cancel,
485                                 R.string.overwrite,
486                                 R.string.confirm_overwrite,
487                                 ctx.getString(R.string.msgs_overwrite_files),
488                                 new DialogInterface.OnClickListener() {
489                                     @Override
490                                     public void onClick(DialogInterface alertDialog, int which) {
491                                         // NEGATIVE (overwrite)  POSITIVE (cancel)
492                                         if (which == DialogInterface.BUTTON_NEGATIVE) {
493                                             // Execute background task
494                                             task.execute(task);
495                                         }
496                                     }
497                                });
498                 DialogHelper.delegateDialogShow(ctx, dialog);
499                 return;
500             }
501         }
502
503         // Execute background task
504         task.execute(task);
505     }
506
507     /**
508      * Method that check if is needed to prompt the user for overwrite prior to do
509      * the operation.
510      *
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
514      */
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;
524
525                 // The file exists in the destination directory
526                 if (dst1.getFullPath().compareTo(dst2.getAbsolutePath()) == 0) {
527                     askUser = true;
528                     break;
529                 }
530             }
531             if (askUser) break;
532         }
533         return askUser;
534     }
535
536
537     /**
538      * Method that check the consistency of copy or move operations.<br/>
539      * <br/>
540      * The method checks the following rules:<br/>
541      * <ul>
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>
546      * </ul>
547      *
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
553      */
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();
561
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
566                 AlertDialog dialog =
567                         DialogHelper.createErrorDialog(
568                                 ctx,
569                                 R.string.error_title,
570                                 R.string.msgs_unresolved_inconsistencies);
571                 DialogHelper.delegateDialogShow(ctx, dialog);
572                 return false;
573             }
574
575             // 2.- Destination can't be a child of source
576             if (dst.startsWith(src)) {
577                 // Operation not allowed
578                 AlertDialog dialog =
579                         DialogHelper.createErrorDialog(
580                                 ctx,
581                                 R.string.error_title,
582                                 R.string.msgs_operation_not_allowed_in_current_directory);
583                 DialogHelper.delegateDialogShow(ctx, dialog);
584                 return false;
585             }
586         }
587         return true;
588     }
589 }