Browse Source

Merge branch 'long_press'

David A. Velasco 12 years ago
parent
commit
a7cbc53251

+ 3 - 22
res/layout/edit_box_dialog.xml

@@ -26,29 +26,10 @@
         android:id="@+id/user_input"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:ems="10" >
+        android:ems="10" 
+		android:inputType="textNoSuggestions"
+		>
 
-        <requestFocus />
     </EditText>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:gravity="center_horizontal" >
-
-        <Button
-            android:id="@+id/cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/common_cancel" />
-
-        <Button
-            android:id="@+id/ok"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/common_ok" />
-
-    </LinearLayout>
-
 </LinearLayout>

+ 33 - 0
res/menu/file_context_menu.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu 	xmlns:android="http://schemas.android.com/apk/res/android">
+    
+	<item 	android:id="@+id/open_file_item" 
+	    	android:title="@string/filedetails_open" 
+	    	android:icon="@android:drawable/ic_menu_edit"
+	/>
+	
+	<item 	android:id="@+id/download_file_item" 
+	    	android:title="@string/filedetails_download" 
+	/>
+	
+	<item 	android:id="@+id/cancel_download_item" 
+	    	android:title="@string/common_cancel_download" 
+	    	android:icon="@android:drawable/ic_menu_close_clear_cancel"
+	/>
+	
+	<item 	android:id="@+id/cancel_upload_item" 
+	    	android:title="@string/common_cancel_upload" 
+	    	android:icon="@android:drawable/ic_menu_close_clear_cancel"
+	/>
+	
+	<item 	android:id="@+id/rename_file_item" 
+	    	android:title="@string/common_rename" 
+	    	android:icon="@android:drawable/ic_menu_set_as"
+	/>
+	
+    <item 	android:id="@+id/remove_file_item" 
+        	android:title="@string/common_remove" 
+        	android:icon="@android:drawable/ic_menu_delete"
+	/>
+    
+</menu>

+ 4 - 0
res/values-large/bools.xml

@@ -0,0 +1,4 @@
+<!-- Large screen boolean values -->
+<resources>
+    <bool name="large_layout">true</bool>
+</resources>

+ 4 - 0
res/values/bools.xml

@@ -0,0 +1,4 @@
+<!-- Default boolean values -->
+<resources>
+    <bool name="large_layout">false</bool>
+</resources>

+ 11 - 6
res/values/strings.xml

@@ -81,7 +81,9 @@
     <string name="common_yes">Yes</string>
     <string name="common_no">No</string>
     <string name="common_ok">OK</string>
-    <string name="common_cancel">Cancel</string>
+    <string name="common_cancel_download">Cancel download</string>
+    <string name="common_cancel_upload">Cancel upload</string>
+	<string name="common_cancel">Cancel</string>
     <string name="common_save_exit">Save &amp; Exit</string>
     <string name="common_exit">Leave ownCloud</string>
     <string name="common_error">Error</string>
@@ -102,7 +104,7 @@
     <string name="uploader_upload_failed_content_multiple">Upload failed: %1$d/%2$d files were upload</string>
     <string name="downloader_download_in_progress_ticker">Downloading &#8230;</string>
     <string name="downloader_download_in_progress_content">%1$d%% Downloading %2$s</string>
-    <string name="downloader_download_succeeded_ticker">Download suceeded</string>
+    <string name="downloader_download_succeeded_ticker">Download succeeded</string>
     <string name="downloader_download_succeeded_content">%1$s was successfully download</string>
     <string name="downloader_download_failed_ticker">Download failed</string>
     <string name="downloader_download_failed_content">Download of %1$s could not be completed</string>
@@ -175,14 +177,17 @@
     <string name="common_rename">Rename</string>
     <string name="common_remove">Remove</string>
     
-	<string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
-	<string name="confirmation_remove_local">Local only</string>
-	<string name="confirmation_remove_remote">Remove from server</string>
-	<string name="confirmation_remove_remote_and_local">Remote and local</string>
+	  <string name="confirmation_remove_alert">"Do you really want to remove %1$s ?"</string>
+	  <string name="confirmation_remove_folder_alert">"Do you really want to remove %1$s and its contents ?"</string>
+	  <string name="confirmation_remove_local">Local only</string>
+	  <string name="confirmation_remove_folder_local">Local contents only</string>
+	  <string name="confirmation_remove_remote">Remove from server</string>
+	  <string name="confirmation_remove_remote_and_local">Remote and local</string>
 
     <string name="remove_success_msg">"Successful removal"</string>
     <string name="remove_fail_msg">"Removal could not be completed"</string>
     
+    <string name="rename_dialog_title">Enter a new name</string>
     <string name="rename_local_fail_msg">"Local copy could not be renamed; try a differente new name"</string>
     <string name="rename_server_fail_msg">"Rename could not be completed"</string>
         

+ 4 - 0
src/com/owncloud/android/datamodel/DataStorageManager.java

@@ -40,4 +40,8 @@ public interface DataStorageManager {
     public Vector<OCFile> getDirectoryContent(OCFile f);
     
     public void removeFile(OCFile file, boolean removeLocalCopy);
+    
+    public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent);
+
+    public void moveDirectory(OCFile dir, String newPath);
 }

+ 126 - 3
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -122,9 +122,14 @@ public class FileDataStorageManager implements DataStorageManager {
         cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
         cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
 
-        if (fileExists(file.getRemotePath())) {
-            OCFile oldFile = getFileByPath(file.getRemotePath());
-            file.setFileId(oldFile.getFileId());
+        boolean sameRemotePath = fileExists(file.getRemotePath());
+        if (sameRemotePath ||
+            fileExists(file.getFileId())        ) {           // for renamed files; no more delete and create
+            
+            if (sameRemotePath) {
+                OCFile oldFile = getFileByPath(file.getRemotePath());
+                file.setFileId(oldFile.getFileId());
+            }
 
             overriden = true;
             if (getContentResolver() != null) {
@@ -207,6 +212,19 @@ public class FileDataStorageManager implements DataStorageManager {
                                         new String[] { String.valueOf(file.getFileId()) })
                         .build());
                 
+            } else if (fileExists(file.getFileId())) {
+                    OCFile oldFile = getFileById(file.getFileId());
+                    if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
+                        file.setStoragePath(oldFile.getStoragePath());
+                    if (!file.isDirectory());
+                        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
+
+                    operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                            withValues(cv).
+                            withSelection(  ProviderTableMeta._ID + "=?", 
+                                            new String[] { String.valueOf(file.getFileId()) })
+                            .build());
+                    
             } else {
                 operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build());
             }
@@ -426,6 +444,111 @@ public class FileDataStorageManager implements DataStorageManager {
         if (file.isDown() && removeLocalCopy) {
             new File(file.getStoragePath()).delete();
         }
+        if (file.isDirectory() && removeLocalCopy) {
+            File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+            if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) {
+                f.delete();
+            }
+        }
+    }
+
+    @Override
+    public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) {
+        // TODO consider possible failures
+        if (dir != null && dir.isDirectory() && dir.getFileId() != -1) {
+            Vector<OCFile> children = getDirectoryContent(dir);
+            if (children != null) {
+                OCFile child = null;
+                for (int i=0; i<children.size(); i++) {
+                    child = children.get(i);
+                    if (child.isDirectory()) {
+                        removeDirectory(child, removeDBData, removeLocalContent);
+                    } else {
+                        if (removeDBData) {
+                            removeFile(child, removeLocalContent);
+                        } else if (removeLocalContent) {
+                            if (child.isDown()) {
+                                new File(child.getStoragePath()).delete();
+                            }
+                        }
+                    }
+                }
+                if (removeDBData) {
+                    removeFile(dir, true);
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Updates database for a folder that was moved to a different location.
+     * 
+     * TODO explore better (faster) implementations
+     * TODO throw exceptions up !
+     */
+    @Override
+    public void moveDirectory(OCFile dir, String newPath) {
+        // TODO check newPath
+        
+        if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) {
+            /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir')
+            Cursor c = null;
+            if (getContentProvider() != null) {
+                try {
+                    c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, 
+                                                    null,
+                                                    ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
+                                                    new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, e.getMessage());
+                }
+            } else {
+                c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, 
+                                                null,
+                                                ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?",
+                                                new String[] { mAccount.name, dir.getRemotePath() + "%" }, null);
+            }
+
+            /// 2. prepare a batch of update operations to change all the descendants
+            ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(c.getCount());
+            int lengthOfOldPath = dir.getRemotePath().length();
+            String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name);
+            int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
+            if (c.moveToFirst()) {
+                do {
+                    ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object
+                    OCFile child = createFileInstance(c);
+                    cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath));
+                    if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) {
+                        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath));
+                    }
+                    operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                                                                        withValues(cv).
+                                                                        withSelection(  ProviderTableMeta._ID + "=?", 
+                                                                                new String[] { String.valueOf(child.getFileId()) })
+                                                                                .build());
+                } while (c.moveToNext());
+            }
+            c.close();
+            
+            /// 3. apply updates in batch
+            try {
+                if (getContentResolver() != null) {
+                    getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations);
+                
+                } else {
+                    getContentProvider().applyBatch(operations);
+                }
+                
+            } catch (OperationApplicationException e) {
+                Log.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e);
+                
+            } catch (RemoteException e) {
+                Log.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e);
+            }
+            
+        }
     }
 
 }

+ 23 - 2
src/com/owncloud/android/datamodel/OCFile.java

@@ -22,6 +22,7 @@ import java.io.File;
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 public class OCFile implements Parcelable, Comparable<OCFile> {
 
@@ -38,6 +39,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     };
 
     public static final String PATH_SEPARATOR = "/";
+
+    private static final String TAG = OCFile.class.getSimpleName();
     
     private long mId;
     private long mParentId;
@@ -217,7 +220,25 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
      */
     public String getFileName() {
         File f = new File(getRemotePath());
-        return f.getName().length() == 0 ? "/" : f.getName();
+        return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName();
+    }
+    
+    /**
+     * Sets the name of the file
+     * 
+     * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory 
+     */
+    public void setFileName(String name) {
+        Log.d(TAG, "OCFile name changin from " + mRemotePath);
+        if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) {
+            String parent = (new File(getRemotePath())).getParent();
+            parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR;
+            mRemotePath =  parent + name;
+            if (isDirectory()) {
+                mRemotePath += PATH_SEPARATOR;
+            }
+            Log.d(TAG, "OCFile name changed to " + mRemotePath);
+        }
     }
 
     /**
@@ -384,7 +405,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     @Override
     public String toString() {
         String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]";
-        asString = String.format(asString, new Long(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, new Long(mParentId), new Boolean(mKeepInSync));
+        asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync));
         return asString;
     }
 

+ 15 - 2
src/com/owncloud/android/files/services/FileDownloader.java

@@ -190,14 +190,27 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         
         
         /**
-         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download
+         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.
+         * 
+         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. 
          * 
          * @param account       Owncloud account where the remote file is stored.
          * @param file          A file that could be in the queue of downloads.
          */
         public boolean isDownloading(Account account, OCFile file) {
+            String targetKey = buildRemoteName(account, file);
             synchronized (mPendingDownloads) {
-                return (mPendingDownloads.containsKey(buildRemoteName(account, file)));
+                if (file.isDirectory()) {
+                    // this can be slow if there are many downloads :(
+                    Iterator<String> it = mPendingDownloads.keySet().iterator();
+                    boolean found = false;
+                    while (it.hasNext() && !found) {
+                        found = it.next().startsWith(targetKey);
+                    }
+                    return found;
+                } else {
+                    return (mPendingDownloads.containsKey(targetKey));
+                }
             }
         }
     }

+ 17 - 6
src/com/owncloud/android/files/services/FileUploader.java

@@ -71,7 +71,6 @@ import eu.alefzero.webdav.WebdavClient;
 public class FileUploader extends Service implements OnDatatransferProgressListener {
 
     public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
-    public static final String EXTRA_PARENT_DIR_ID = "PARENT_DIR_ID";
     public static final String EXTRA_UPLOAD_RESULT = "RESULT";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
     public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
@@ -79,8 +78,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";    
     
     public static final String KEY_FILE = "FILE";
-    public static final String KEY_LOCAL_FILE = "LOCAL_FILE";       // TODO remove this as a possible input argument ; use KEY_FILE everywhere
-    public static final String KEY_REMOTE_FILE = "REMOTE_FILE";     // TODO remove this as a possible input argument ; use KEY_FILE everywhere
+    public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
+    public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
     public static final String KEY_MIME_TYPE = "MIME_TYPE";
 
     public static final String KEY_ACCOUNT = "ACCOUNT";
@@ -313,12 +312,25 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         /**
          * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
          * 
+         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. 
+         * 
          * @param account       Owncloud account where the remote file will be stored.
          * @param file          A file that could be in the queue of pending uploads
          */
         public boolean isUploading(Account account, OCFile file) {
+            String targetKey = buildRemoteName(account, file);
             synchronized (mPendingUploads) {
-                return (mPendingUploads.containsKey(buildRemoteName(account, file)));
+                if (file.isDirectory()) {
+                    // this can be slow if there are many downloads :(
+                    Iterator<String> it = mPendingUploads.keySet().iterator();
+                    boolean found = false;
+                    while (it.hasNext() && !found) {
+                        found = it.next().startsWith(targetKey);
+                    }
+                    return found;
+                } else {
+                    return (mPendingUploads.containsKey(targetKey));
+                }
             }
         }
     }
@@ -541,7 +553,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         
         // parent dir
         String parentPath = new File(remotePath).getParent();
-        parentPath = parentPath.endsWith("/")?parentPath:parentPath+"/" ;
+        parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR ;
         OCFile parentDir = storageManager.getFileByPath(parentPath);
         if (parentDir == null) {
             throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath);
@@ -682,7 +694,6 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         end.putExtra(EXTRA_FILE_PATH, upload.getStoragePath());
         end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
         end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
-        end.putExtra(EXTRA_PARENT_DIR_ID, upload.getFile().getParentId());
         sendStickyBroadcast(end);
     }
 

+ 18 - 3
src/com/owncloud/android/operations/RemoveFileOperation.java

@@ -18,6 +18,7 @@
 
 package com.owncloud.android.operations;
 
+import org.apache.commons.httpclient.HttpStatus;
 import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
 
 import android.util.Log;
@@ -59,6 +60,16 @@ public class RemoveFileOperation extends RemoteOperation {
     }
     
     
+    /**
+     * Getter for the file to remove (or removed, if the operation was successfully performed).
+     * 
+     * @return      File to remove or already removed.
+     */
+    public OCFile getFile() {
+        return mFileToRemove;
+    }
+    
+    
     /**
      * Performs the remove operation
      * 
@@ -71,11 +82,15 @@ public class RemoveFileOperation extends RemoteOperation {
         try {
             delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath()));
             int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
-            if (delete.succeeded()) {
-                mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy);
+            if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) {
+                if (mFileToRemove.isDirectory()) {
+                    mDataStorageManager.removeDirectory(mFileToRemove, true, mDeleteLocalCopy);
+                } else {
+                    mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy);
+                }
             }
             delete.getResponseBodyAsString();   // exhaust the response, although not interesting
-            result = new RemoteOperationResult(delete.succeeded(), status);
+            result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status);
             Log.i(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage());
             
         } catch (Exception e) {

+ 68 - 50
src/com/owncloud/android/operations/RenameFileOperation.java

@@ -24,6 +24,7 @@ import java.io.IOException;
 import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
 //import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
 
+import android.accounts.Account;
 import android.util.Log;
 
 import com.owncloud.android.datamodel.DataStorageManager;
@@ -48,7 +49,9 @@ public class RenameFileOperation extends RemoteOperation {
     
 
     private OCFile mFile;
+    private Account mAccount;
     private String mNewName;
+    private String mNewRemotePath;
     private DataStorageManager mStorageManager;
     
     
@@ -56,12 +59,15 @@ public class RenameFileOperation extends RemoteOperation {
      * Constructor
      * 
      * @param file                  OCFile instance describing the remote file or folder to rename
+     * @param account               OwnCloud account containing the remote file 
      * @param newName               New name to set as the name of file.
      * @param storageManager        Reference to the local database corresponding to the account where the file is contained. 
      */
-    public RenameFileOperation(OCFile file, String newName, DataStorageManager storageManager) {
+    public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) {
         mFile = file;
+        mAccount = account;
         mNewName = newName;
+        mNewRemotePath = null;
         mStorageManager = storageManager;
     }
   
@@ -80,60 +86,63 @@ public class RenameFileOperation extends RemoteOperation {
         RemoteOperationResult result = null;
         
         LocalMoveMethod move = null;
-        //MoveMethod move = null;   // TODO find out why not use this
-        String newRemotePath = null;
+        mNewRemotePath = null;
         try {
             if (mNewName.equals(mFile.getFileName())) {
                 return new RemoteOperationResult(ResultCode.OK);
             }
         
-            newRemotePath = (new File(mFile.getRemotePath())).getParent() + mNewName;
+            String parent = (new File(mFile.getRemotePath())).getParent();
+            parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; 
+            mNewRemotePath =  parent + mNewName;
+            if (mFile.isDirectory()) {
+                mNewRemotePath += OCFile.PATH_SEPARATOR;
+            }
             
             // check if the new name is valid in the local file system
             if (!isValidNewName()) {
                 return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME);
             }
         
-            // check if a remote file with the new name already exists
-            if (client.existsFile(newRemotePath)) {
+            // check if a file with the new name already exists
+            if (client.existsFile(mNewRemotePath) ||                             // remote check could fail by network failure, or by indeterminate behavior of HEAD for folders ... 
+                    mStorageManager.getFileByPath(mNewRemotePath) != null) {     // ... so local check is convenient
                 return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE);
             }
-            /*move = new MoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), 
-                                                client.getBaseUri() + WebdavUtils.encodePath(newRemotePath),
-                                                false);*/
             move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()),
-                                        client.getBaseUri() + WebdavUtils.encodePath(newRemotePath));
+                                        client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath));
             int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT);
             if (move.succeeded()) {
 
-                // create new OCFile instance for the renamed file
-                OCFile newFile = obtainUpdatedFile();
-                OCFile oldFile = mFile;
-                mFile = newFile; 
-                
-                // try to rename the local copy of the file
-                if (oldFile.isDown()) {
-                    File f = new File(oldFile.getStoragePath());
-                    String newStoragePath = f.getParent() + mNewName;
-                    if (f.renameTo(new File(newStoragePath))) {
-                        mFile.setStoragePath(newStoragePath);
-                    }
-                    // else - NOTHING: the link to the local file is kept although the local name can't be updated
-                    // TODO - study conditions when this could be a problem
+                if (mFile.isDirectory()) {
+                    saveLocalDirectory();
+                    
+                } else {
+                    saveLocalFile();
+                    
                 }
-                
-                mStorageManager.removeFile(oldFile, false);
-                mStorageManager.saveFile(mFile);
+             
+            /* 
+             *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) {
+             *   // TODO 
+             *   // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others
+             *   // stayed in the old folder;
+             *   //
+             *   // easiest and heaviest solution is synchronizing the parent folder (or the full account);
+             *   //
+             *   // a better solution is synchronizing the folders with the old and new names;
+             *}
+             */
                 
             }
             
             move.getResponseBodyAsString(); // exhaust response, although not interesting
             result = new RemoteOperationResult(move.succeeded(), status);
-            Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + newRemotePath + ": " + result.getLogMessage());
+            Log.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage());
             
         } catch (Exception e) {
             result = new RemoteOperationResult(e);
-            Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((newRemotePath==null) ? mNewName : newRemotePath) + ": " + result.getLogMessage(), e);
+            Log.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e);
             
         } finally {
             if (move != null)
@@ -143,6 +152,35 @@ public class RenameFileOperation extends RemoteOperation {
     }
 
     
+    private void saveLocalDirectory() {
+        mStorageManager.moveDirectory(mFile, mNewRemotePath);
+        String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
+        File localDir = new File(localPath);
+        if (localDir.exists()) {
+            localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath));
+            // TODO - if renameTo fails, children files that are already down will result unlinked
+        }
+    }
+
+    private void saveLocalFile() {
+        mFile.setFileName(mNewName);
+        
+        // try to rename the local copy of the file
+        if (mFile.isDown()) {
+            File f = new File(mFile.getStoragePath());
+            String parentStoragePath = f.getParent();
+            if (!parentStoragePath.endsWith(File.separator))
+                parentStoragePath += File.separator;
+            if (f.renameTo(new File(parentStoragePath + mNewName))) {
+                mFile.setStoragePath(parentStoragePath + mNewName);
+            }
+            // else - NOTHING: the link to the local file is kept although the local name can't be updated
+            // TODO - study conditions when this could be a problem
+        }
+        
+        mStorageManager.saveFile(mFile);
+    }
+
     /**
      * Checks if the new name to set is valid in the file system 
      * 
@@ -180,27 +218,7 @@ public class RenameFileOperation extends RemoteOperation {
     }
 
 
-    /**
-     * Creates a new OCFile for the new remote name of the renamed file.
-     * 
-     * @return      OCFile object with the same information than mFile, but the renamed remoteFile and the storagePath (empty)
-     */
-    private OCFile obtainUpdatedFile() {
-        OCFile file = new OCFile(mStorageManager.getFileById(mFile.getParentId()).getRemotePath() + mNewName);
-        file.setCreationTimestamp(mFile.getCreationTimestamp());
-        file.setFileId(mFile.getFileId());
-        file.setFileLength(mFile.getFileLength());
-        file.setKeepInSync(mFile.keepInSync());
-        file.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties());
-        file.setLastSyncDateForData(mFile.getLastSyncDateForData());
-        file.setMimetype(mFile.getMimetype());
-        file.setModificationTimestamp(mFile.getModificationTimestamp());
-        file.setParentId(mFile.getParentId());
-        return file;
-    }
-
-
-    // move operation - TODO: find out why org.apache.jackrabbit.webdav.client.methods.MoveMethod is not used instead ¿?
+    // move operation
     private class LocalMoveMethod extends DavMethodBase {
 
         public LocalMoveMethod(String uri, String dest) {

+ 5 - 0
src/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -223,4 +223,9 @@ public class SynchronizeFileOperation extends RemoteOperation {
         return mTransferWasRequested;
     }
 
+
+    public OCFile getLocalFile() {
+        return mLocalFile;
+    }
+
 }

+ 1 - 1
src/com/owncloud/android/ui/activity/AccountSelectActivity.java

@@ -166,7 +166,7 @@ public class AccountSelectActivity extends SherlockListActivity implements
             }
         }
 
-        return false;
+        return true;
     }
 
     private void populateAccountList() {

+ 5 - 2
src/com/owncloud/android/ui/activity/FileDetailActivity.java

@@ -114,7 +114,7 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
             }
             FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
             if (fragment != null)
-                fragment.updateFileDetails();   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
+                fragment.updateFileDetails(false);   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
         }
 
         @Override
@@ -152,6 +152,9 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         case android.R.id.home:
             backToDisplayActivity();
             returnValue = true;
+            break;
+        default:
+        	returnValue = super.onOptionsItemSelected(item);
         }
         
         return returnValue;
@@ -165,7 +168,7 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         super.onResume();
         if (!mConfigurationChangedToLandscape) { 
             FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
-            fragment.updateFileDetails();
+            fragment.updateFileDetails(false);
         }
     }
     

+ 163 - 15
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -74,7 +74,13 @@ import com.owncloud.android.files.services.FileObserverService;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.syncadapter.FileSyncService;
 import com.owncloud.android.ui.dialog.SslValidatorDialog;
 import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
@@ -92,7 +98,7 @@ import eu.alefzero.webdav.WebdavClient;
  */
 
 public class FileDisplayActivity extends SherlockFragmentActivity implements
-    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener {
+    OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener {
     
     private ArrayAdapter<String> mDirectories;
     private OCFile mCurrentDir = null;
@@ -332,7 +338,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
                 break;
             }
             default:
-                retval = false;
+                retval = super.onOptionsItemSelected(item);
         }
         return retval;
     }
@@ -896,16 +902,11 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
          */
         @Override
         public void onReceive(Context context, Intent intent) {
-            long parentDirId = intent.getLongExtra(FileUploader.EXTRA_PARENT_DIR_ID, -1);
-            OCFile parentDir = mStorageManager.getFileById(parentDirId);
+            String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
             String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
-
-            if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
-                    parentDir != null && 
-                    (   (mCurrentDir == null && parentDir.getFileName().equals("/")) ||
-                            parentDir.equals(mCurrentDir)
-                    )
-                ) {
+            boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);
+            boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+            if (sameAccount && isDescendant) {
                 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
                 if (fileListFragment != null) { 
                     fileListFragment.listDirectory();
@@ -924,9 +925,9 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
         public void onReceive(Context context, Intent intent) {
             String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
             String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
-
-            if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name) &&
-                     mCurrentDir != null && mCurrentDir.getFileId() == mStorageManager.getFileByPath(downloadedRemotePath).getParentId()) {
+            boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name);
+            boolean isDescendant = (mCurrentDir != null) && (downloadedRemotePath != null) && (downloadedRemotePath.startsWith(mCurrentDir.getRemotePath()));
+            if (sameAccount && isDescendant) {
                 OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
                 if (fileListFragment != null) { 
                     fileListFragment.listDirectory();
@@ -1051,7 +1052,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
             if (mDualPane) {
                 FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
                 if (fragment != null)
-                    fragment.updateFileDetails();
+                    fragment.updateFileDetails(false);
             }
         }
 
@@ -1096,4 +1097,151 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
     }
 
 
+    /**
+     * Updates the view associated to the activity after the finish of some operation over files
+     * in the current account.
+     * 
+     * @param operation     Removal operation performed.
+     * @param result        Result of the removal.
+     */
+    @Override
+    public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+        if (operation instanceof RemoveFileOperation) {
+            onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
+                
+        } else if (operation instanceof RenameFileOperation) {
+            onRenameFileOperationFinish((RenameFileOperation)operation, result);
+            
+        } else if (operation instanceof SynchronizeFileOperation) {
+            onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);
+        }
+    }
+
+
+    /**
+     * Updates the view associated to the activity after the finish of an operation trying to remove a 
+     * file. 
+     * 
+     * @param operation     Removal operation performed.
+     * @param result        Result of the removal.
+     */
+    private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        if (result.isSuccess()) {
+            Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG);
+            msg.show();
+            OCFile removedFile = operation.getFile();
+            if (mDualPane) {
+                FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+                if (details != null && removedFile.equals(details.getDisplayedFile()) ) {
+                    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+                    transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment
+                    transaction.commit();
+                }
+            }
+            if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) {
+                mFileList.listDirectory();
+            }
+                
+        } else {
+            Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); 
+            msg.show();
+            if (result.isSslRecoverableException()) {
+                mLastSslUntrustedServerResult = result;
+                showDialog(DIALOG_SSL_VALIDATOR); 
+            }
+        }
+    }
+
+    /**
+     * Updates the view associated to the activity after the finish of an operation trying to rename a 
+     * file. 
+     * 
+     * @param operation     Renaming operation performed.
+     * @param result        Result of the renaming.
+     */
+    private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        OCFile renamedFile = operation.getFile();
+        if (result.isSuccess()) {
+            if (mDualPane) {
+                FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+                if (details != null && renamedFile.equals(details.getDisplayedFile()) ) {
+                    details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this));
+                }
+            }
+            if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) {
+                mFileList.listDirectory();
+            }
+            
+        } else {
+            if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {
+                Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                // TODO throw again the new rename dialog
+            } else {
+                Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+                if (result.isSslRecoverableException()) {
+                    mLastSslUntrustedServerResult = result;
+                    showDialog(DIALOG_SSL_VALIDATOR); 
+                }
+            }
+        }
+    }
+
+
+    private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {
+        dismissDialog(DIALOG_SHORT_WAIT);
+        OCFile syncedFile = operation.getLocalFile();
+        if (!result.isSuccess()) {
+            if (result.getCode() == ResultCode.SYNC_CONFLICT) {
+                Intent i = new Intent(this, ConflictsResolveActivity.class);
+                i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile);
+                i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this));
+                startActivity(i);
+                
+            } else {
+                Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+            
+        } else {
+            if (operation.transferWasRequested()) {
+                mFileList.listDirectory();
+                onTransferStateChanged(syncedFile, true, true);
+                
+            } else {
+                Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); 
+                msg.show();
+            }
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) {
+        /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList);
+        if (fileListFragment != null) { 
+            fileListFragment.listDirectory();
+        }*/
+        if (mDualPane) {
+            FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+            if (details != null && file.equals(details.getDisplayedFile()) ) {
+                if (downloading || uploading) {
+                    details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this));
+                } else {
+                    details.updateFileDetails(downloading || uploading);
+                }
+            }
+        }
+    }
+
+
+    
+
+
 }

+ 1 - 1
src/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -118,7 +118,7 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements
                 break;
             }
             default:
-                retval = false;
+                retval = onOptionsItemSelected(item);
         }
         return retval;
     }

+ 0 - 1
src/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -121,7 +121,6 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                 fileIcon.setImageResource(R.drawable.ic_menu_archive);
             }
             ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);
-            //if (FileDownloader.isDownloading(mAccount, file.getRemotePath())) {
             FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
             if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {

+ 81 - 26
src/com/owncloud/android/ui/dialog/EditNameDialog.java

@@ -19,13 +19,13 @@
 
 package com.owncloud.android.ui.dialog;
 
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.view.View.OnClickListener;
 import android.view.WindowManager.LayoutParams;
-import android.widget.Button;
 import android.widget.TextView;
 
 import com.actionbarsherlock.app.SherlockDialogFragment;
@@ -33,50 +33,93 @@ import com.owncloud.android.R;
 
 
 /**
- * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
+ * Dialog to request the user to input a name, optionally initialized with a former name.
  * 
  * @author Bartek Przybylski
+ * @author David A. Velasco
  */
-public class EditNameDialog extends SherlockDialogFragment implements OnClickListener {
+public class EditNameDialog extends SherlockDialogFragment implements DialogInterface.OnClickListener {
 
+    public static final String TAG = EditNameDialog.class.getSimpleName();
+    
+    protected static final String ARG_TITLE = "title";
+    protected static final String ARG_NAME = "name";
+    
     private String mNewFilename;
     private boolean mResult;
     private EditNameDialogListener mListener;
     
-    static public EditNameDialog newInstance(String filename) {
+    /**
+     * Public factory method to get dialog instances.
+     * 
+     * @param title         Text to show as title in the dialog.
+     * @param name          Optional text to include in the text input field when the dialog is shown.
+     * @param listener      Instance to notify when the dialog is dismissed.
+     * @return              New dialog instance, ready to show.
+     */
+    static public EditNameDialog newInstance(String title, String name, EditNameDialogListener listener) {
         EditNameDialog f = new EditNameDialog();
         Bundle args = new Bundle();
-        args.putString("filename", filename);
+        args.putString(ARG_TITLE, title);
+        args.putString(ARG_NAME, name);
         f.setArguments(args);
+        f.setOnDismissListener(listener);
         return f;
     }
     
+    
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View v = inflater.inflate(R.layout.edit_box_dialog, container, false);
-
-        String currentName = getArguments().getString("filename");
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        String currentName = getArguments().getString(ARG_NAME);
         if (currentName == null)
             currentName = "";
+        String title = getArguments().getString(ARG_TITLE);
         
-        ((Button)v.findViewById(R.id.cancel)).setOnClickListener(this);
-        ((Button)v.findViewById(R.id.ok)).setOnClickListener(this);
-        ((TextView)v.findViewById(R.id.user_input)).setText(currentName);
-        ((TextView)v.findViewById(R.id.user_input)).requestFocus();
-        getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        // Inflate the layout for the dialog
+        LayoutInflater inflater = getSherlockActivity().getLayoutInflater();
+        View v = inflater.inflate(R.layout.edit_box_dialog, null);  // null parent view because it will go in the dialog layout
+        TextView inputText = ((TextView)v.findViewById(R.id.user_input));
+        inputText.setText(currentName);
+        
+        // Set it to the dialog 
+        AlertDialog.Builder builder = new AlertDialog.Builder(getSherlockActivity());
+        builder.setView(v)
+               .setPositiveButton(R.string.common_ok, this)
+               .setNegativeButton(R.string.common_cancel, this);
 
+        if (title != null) {
+            builder.setTitle(title);
+        }
+        
         mResult = false;
-        return v;
-    }
+        
+        Dialog d = builder.create();
+
+        inputText.requestFocus();
+        d.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+        return d;
+    }    
+
     
+    /**
+     * Performs the corresponding action when a dialog button is clicked.
+     * 
+     * Saves the text in the input field to be accessed through {@link #getNewFilename()} when the positive
+     * button is clicked.
+     * 
+     * Notify the current listener in any case.
+     */
     @Override
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.ok: {
-                mNewFilename = ((TextView)getView().findViewById(R.id.user_input)).getText().toString();
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case AlertDialog.BUTTON_POSITIVE: {
+                mNewFilename = ((TextView)(getDialog().findViewById(R.id.user_input))).getText().toString();
                 mResult = true;
             }
-            case R.id.cancel: { // fallthought
+            case AlertDialog.BUTTON_NEGATIVE: { // fall through
                 dismiss();
                 if (mListener != null)
                     mListener.onDismiss(this);
@@ -84,23 +127,35 @@ public class EditNameDialog extends SherlockDialogFragment implements OnClickLis
         }
     }
     
-    public void setOnDismissListener(EditNameDialogListener listener) {
+    protected void setOnDismissListener(EditNameDialogListener listener) {
         mListener = listener;
     }
     
+    /**
+     * Returns the text in the input field after the user clicked the positive button.
+     * 
+     * @return      Text in the input field.
+     */
     public String getNewFilename() {
         return mNewFilename;
     }
     
-    // true if user clicked ok
+    /**
+     * 
+     * @return      True when the user clicked the positive button.
+     */
     public boolean getResult() {
         return mResult;
     }
 
     
+    /**
+     * Interface to receive a notification when any button in the dialog is clicked.
+     */
     public interface EditNameDialogListener {
         public void onDismiss(EditNameDialog dialog);
     }
-    
+
+
 }
 

+ 24 - 13
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -191,7 +191,7 @@ public class FileDetailFragment extends SherlockFragment implements
             mPreview = (ImageView)mView.findViewById(R.id.fdPreview);
         }
         
-        updateFileDetails();
+        updateFileDetails(false);
         return view;
     }
     
@@ -274,7 +274,6 @@ public class FileDetailFragment extends SherlockFragment implements
     public void onClick(View v) {
         switch (v.getId()) {
             case R.id.fdDownloadBtn: {
-                //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath())) {
                 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
                 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
                 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
@@ -342,8 +341,7 @@ public class FileDetailFragment extends SherlockFragment implements
                 break;
             }
             case R.id.fdRenameBtn: {
-                EditNameDialog dialog = EditNameDialog.newInstance(mFile.getFileName());
-                dialog.setOnDismissListener(this);
+                EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this);
                 dialog.show(getFragmentManager(), "nameeditdialog");
                 break;
             }   
@@ -374,8 +372,13 @@ public class FileDetailFragment extends SherlockFragment implements
                     try {
                         Intent i = new Intent(Intent.ACTION_VIEW);
                         mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
-                        if (mimeType != null && !mimeType.equals(mFile.getMimetype())) {
-                            i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                        if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
+                            if (mimeType != null) {
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                            } else {
+                                // desperate try
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
+                            }
                             i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                             startActivity(i);
                             toastIt = false;
@@ -475,14 +478,21 @@ public class FileDetailFragment extends SherlockFragment implements
             mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
         }
         mAccount = ocAccount;
-        updateFileDetails();
+        updateFileDetails(false);
     }
     
 
     /**
      * Updates the view with all relevant details about that file.
+     *
+     * TODO Remove parameter when the transferring state of files is kept in database. 
+     * 
+     * @param transferring      Flag signaling if the file should be considered as downloading or uploading, 
+     *                          although {@link FileDownloaderBinder#isDownloading(Account, OCFile)}  and 
+     *                          {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.
+     * 
      */
-    public void updateFileDetails() {
+    public void updateFileDetails(boolean transferring) {
 
         if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {
             
@@ -504,7 +514,7 @@ public class FileDetailFragment extends SherlockFragment implements
             //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) {
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
-            if ((downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
+            if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
                 setButtonsForTransferring();
                 
             } else if (mFile.isDown()) {
@@ -517,9 +527,11 @@ public class FileDetailFragment extends SherlockFragment implements
                 setButtonsForDown();
                 
             } else {
+                // TODO load default preview image; when the local file is removed, the preview remains there
                 setButtonsForRemote();
             }
         }
+        getView().invalidate();
     }
     
     
@@ -602,7 +614,6 @@ public class FileDetailFragment extends SherlockFragment implements
         if (!isEmpty()) {
             Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
             downloadButton.setText(R.string.filedetails_sync_file);
-            //downloadButton.setEnabled(true);
         
             ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);
             ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
@@ -687,7 +698,7 @@ public class FileDetailFragment extends SherlockFragment implements
                     if (downloadWasFine) {
                         mFile = mStorageManager.getFileByPath(downloadedRemotePath);
                     }
-                    updateFileDetails();    // it updates the buttons; must be called although !downloadWasFine
+                    updateFileDetails(false);    // it updates the buttons; must be called although !downloadWasFine
                 }
             }
         }
@@ -707,7 +718,6 @@ public class FileDetailFragment extends SherlockFragment implements
     private class UploadFinishReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Log.d(TAG, "Received broacast! : " + intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH));
             String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
 
             if (!isEmpty() && accountName.equals(mAccount.name)) {
@@ -724,7 +734,7 @@ public class FileDetailFragment extends SherlockFragment implements
                         Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG);
                         msg.show();
                     }
-                    updateFileDetails();    // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server
+                    updateFileDetails(false);    // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server
                 }
             }
         }
@@ -840,6 +850,7 @@ public class FileDetailFragment extends SherlockFragment implements
             String newFilename = dialog.getNewFilename();
             Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
             mLastRemoteOperation = new RenameFileOperation( mFile, 
+                                                            mAccount, 
                                                             newFilename, 
                                                             new FileDataStorageManager(mAccount, getActivity().getContentResolver()));
             WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());

+ 304 - 2
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -17,17 +17,49 @@
  */
 package com.owncloud.android.ui.fragment;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.R;
 import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
+import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoveFileOperation;
+import com.owncloud.android.operations.RenameFileOperation;
+import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.ui.FragmentListView;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.activity.TransferServiceGetter;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
+import com.owncloud.android.ui.dialog.EditNameDialog;
+import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
+import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
+
+import eu.alefzero.webdav.WebdavClient;
+import eu.alefzero.webdav.WebdavUtils;
 
+import android.accounts.Account;
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
+import android.webkit.MimeTypeMap;
 import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
 
 /**
  * A Fragment that lists all files and folders in a given path.
@@ -35,7 +67,7 @@ import android.widget.AdapterView;
  * @author Bartek Przybylski
  * 
  */
-public class OCFileListFragment extends FragmentListView {
+public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
     private static final String TAG = "FileListFragment";
     private static final String SAVED_LIST_POSITION = "LIST_POSITION"; 
     
@@ -43,6 +75,9 @@ public class OCFileListFragment extends FragmentListView {
     
     private OCFile mFile = null;
     private FileListListAdapter mAdapter;
+    
+    private Handler mHandler;
+    private OCFile mTargetFile;
 
     
     /**
@@ -76,6 +111,11 @@ public class OCFileListFragment extends FragmentListView {
             setReferencePosition(position);
         }
         
+        registerForContextMenu(getListView());
+        getListView().setOnCreateContextMenuListener(this);        
+        
+        mHandler = new Handler();
+        
         Log.i(TAG, "onActivityCreated() stop");
     }
     
@@ -112,6 +152,197 @@ public class OCFileListFragment extends FragmentListView {
         }
         
     }
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+        super.onCreateContextMenu(menu, v, menuInfo);
+        MenuInflater inflater = getActivity().getMenuInflater();
+        inflater.inflate(R.menu.file_context_menu, menu);
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
+        OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
+        List<Integer> toHide = new ArrayList<Integer>();    
+        List<Integer> toDisable = new ArrayList<Integer>();  
+        
+        MenuItem item = null;
+        if (targetFile.isDirectory()) {
+            // contextual menu for folders
+            toHide.add(R.id.open_file_item);
+            toHide.add(R.id.download_file_item);
+            toHide.add(R.id.cancel_download_item);
+            toHide.add(R.id.cancel_upload_item);
+            if (    mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
+                    mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)           ) {
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                
+            }
+            
+        } else {
+            // contextual menu for regular files
+            if (targetFile.isDown()) {
+                toHide.add(R.id.cancel_download_item);
+                toHide.add(R.id.cancel_upload_item);
+                item = menu.findItem(R.id.download_file_item);
+                if (item != null) {
+                    item.setTitle(R.string.filedetails_sync_file);
+                }
+            } else {
+                toHide.add(R.id.open_file_item);
+            }
+            if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
+                toHide.add(R.id.download_file_item);
+                toHide.add(R.id.cancel_upload_item);
+                toDisable.add(R.id.open_file_item);
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                    
+            } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
+                toHide.add(R.id.download_file_item);
+                toHide.add(R.id.cancel_download_item);
+                toDisable.add(R.id.open_file_item);
+                toDisable.add(R.id.rename_file_item);
+                toDisable.add(R.id.remove_file_item);
+                    
+            } else {
+                toHide.add(R.id.cancel_download_item);
+                toHide.add(R.id.cancel_upload_item);
+            }
+        }
+
+        for (int i : toHide) {
+            item = menu.findItem(i);
+            if (item != null) {
+                item.setVisible(false);
+                item.setEnabled(false);
+            }
+        }
+        
+        for (int i : toDisable) {
+            item = menu.findItem(i);
+            if (item != null) {
+                item.setEnabled(false);
+            }
+        }
+    }
+    
+    
+    /**
+     * {@inhericDoc}
+     */
+    @Override
+    public boolean onContextItemSelected (MenuItem item) {
+        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();        
+        mTargetFile = (OCFile) mAdapter.getItem(info.position);
+        switch (item.getItemId()) {
+            case R.id.rename_file_item: {
+                EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
+                dialog.show(getFragmentManager(), EditNameDialog.TAG);
+                return true;
+            }
+            case R.id.remove_file_item: {
+                int messageStringId = R.string.confirmation_remove_alert;
+                int posBtnStringId = R.string.confirmation_remove_remote;
+                int neuBtnStringId = -1;
+                if (mTargetFile.isDirectory()) {
+                    messageStringId = R.string.confirmation_remove_folder_alert;
+                    posBtnStringId = R.string.confirmation_remove_remote_and_local;
+                    neuBtnStringId = R.string.confirmation_remove_folder_local;
+                } else if (mTargetFile.isDown()) {
+                    posBtnStringId = R.string.confirmation_remove_remote_and_local;
+                    neuBtnStringId = R.string.confirmation_remove_local;
+                }
+                ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
+                        messageStringId,
+                        new String[]{mTargetFile.getFileName()},
+                        posBtnStringId,
+                        neuBtnStringId,
+                        R.string.common_cancel);
+                confDialog.setOnConfirmationListener(this);
+                confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
+                return true;
+            }
+            case R.id.open_file_item: {
+                String storagePath = mTargetFile.getStoragePath();
+                String encodedStoragePath = WebdavUtils.encodePath(storagePath);
+                try {
+                    Intent i = new Intent(Intent.ACTION_VIEW);
+                    i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
+                    i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                    startActivity(i);
+                    
+                } catch (Throwable t) {
+                    Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
+                    boolean toastIt = true; 
+                    String mimeType = "";
+                    try {
+                        Intent i = new Intent(Intent.ACTION_VIEW);
+                        mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
+                        if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
+                            if (mimeType != null) {
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
+                            } else {
+                                // desperate try
+                                i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
+                            }
+                            i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+                            startActivity(i);
+                            toastIt = false;
+                        }
+                        
+                    } catch (IndexOutOfBoundsException e) {
+                        Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
+                        
+                    } catch (ActivityNotFoundException e) {
+                        Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
+                        
+                    } catch (Throwable th) {
+                        Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
+                        
+                    } finally {
+                        if (toastIt) {
+                            Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
+                        }
+                    }
+                    
+                }
+                return true;
+            }
+            case R.id.download_file_item: {
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
+                RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
+                WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
+                operation.execute(wc, mContainerActivity, mHandler);
+                getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+                return true;
+            }
+            case R.id.cancel_download_item: {
+                FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
+                if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
+                    downloaderBinder.cancel(account, mTargetFile);
+                    listDirectory();
+                    mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+                }
+                return true;
+            }
+            case R.id.cancel_upload_item: {
+                FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+                Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
+                if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
+                    uploaderBinder.cancel(account, mTargetFile);
+                    listDirectory();
+                    mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+                }
+                return true;
+            }
+            default:
+                return super.onContextItemSelected(item); 
+        }
+    }
+    
 
     /**
      * Call this, when the user presses the up button
@@ -186,7 +417,7 @@ public class OCFileListFragment extends FragmentListView {
      * 
      * @author David A. Velasco
      */
-    public interface ContainerActivity extends TransferServiceGetter {
+    public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
 
         /**
          * Callback method invoked when a directory is clicked by the user on the files list
@@ -216,6 +447,77 @@ public class OCFileListFragment extends FragmentListView {
         public OCFile getInitialDirectory();
         
         
+        /**
+         * Callback method invoked when a the 'transfer state' of a file changes.
+         * 
+         * This happens when a download or upload is started or ended for a file.
+         * 
+         * This method is necessary by now to update the user interface of the double-pane layout in tablets
+         * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
+         * won't provide the needed response before the method where this is called finishes. 
+         * 
+         * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
+         * 
+         * @param file          OCFile which state changed.
+         * @param downloading   Flag signaling if the file is now downloading.
+         * @param uploading     Flag signaling if the file is now uploading.
+         */
+        public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
+        
     }
+    
+    
+    @Override
+    public void onDismiss(EditNameDialog dialog) {
+        if (dialog.getResult()) {
+            String newFilename = dialog.getNewFilename();
+            Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
+            RemoteOperation operation = new RenameFileOperation(mTargetFile, 
+                                                                AccountUtils.getCurrentOwnCloudAccount(getActivity()), 
+                                                                newFilename, 
+                                                                mContainerActivity.getStorageManager());
+            WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
+            operation.execute(wc, mContainerActivity, mHandler);
+            getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+        }
+    }
+
+    
+    @Override
+    public void onConfirmation(String callerTag) {
+        if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
+            if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
+                RemoteOperation operation = new RemoveFileOperation( mTargetFile, 
+                                                                    true, 
+                                                                    mContainerActivity.getStorageManager());
+                WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
+                operation.execute(wc, mContainerActivity, mHandler);
+                
+                getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
+            }
+        }
+    }
+    
+    @Override
+    public void onNeutral(String callerTag) {
+        File f = null;
+        if (mTargetFile.isDirectory()) {
+            // TODO run in a secondary thread?
+            mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
+            
+        } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
+            f.delete();
+            mTargetFile.setStoragePath(null);
+            mContainerActivity.getStorageManager().saveFile(mTargetFile);
+        }
+        listDirectory();
+        mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
+    }
+    
+    @Override
+    public void onCancel(String callerTag) {
+        Log.d(TAG, "REMOVAL CANCELED");
+    }
+
 
 }