Merge branch 'master' into sync_full_folder
authormasensio <masensio@solidgear.es>
Tue, 27 Oct 2015 07:52:17 +0000 (08:52 +0100)
committermasensio <masensio@solidgear.es>
Tue, 27 Oct 2015 07:52:17 +0000 (08:52 +0100)
Conflicts:
owncloud-android-library
src/com/owncloud/android/files/services/FileUploader.java

1  2 
res/values/strings.xml
src/com/owncloud/android/files/FileOperationsHelper.java
src/com/owncloud/android/files/services/FileUploader.java
src/com/owncloud/android/syncadapter/FileSyncAdapter.java
src/com/owncloud/android/ui/activity/FileDisplayActivity.java
src/com/owncloud/android/ui/fragment/FileDetailFragment.java
src/com/owncloud/android/utils/FileStorageUtils.java

diff --combined res/values/strings.xml
@@@ -74,6 -74,7 +74,7 @@@
      <string name="file_list_seconds_ago">seconds ago</string>
      <string name="file_list_empty">Nothing in here. Upload something!</string>
      <string name="file_list_loading">Loading&#8230;</string>
+     <string name="file_list_no_app_for_file_type">No App found for file type!</string>
      <string name="local_file_list_empty">There are no files in this folder.</string>
      <string name="filedetails_select_file">Tap on a file to display additional information.</string>
      <string name="filedetails_size">Size:</string>
@@@ -81,7 -82,7 +82,7 @@@
      <string name="filedetails_created">Created:</string>
      <string name="filedetails_modified">Modified:</string>
      <string name="filedetails_download">Download</string>
 -    <string name="filedetails_sync_file">Refresh file</string>
 +    <string name="filedetails_sync_file">Synchronize</string>
      <string name="filedetails_renamed_in_upload_msg">File was renamed to %1$s during upload</string>
      <string name="list_layout">List Layout</string>
      <string name="action_share_file">Share link</string>
@@@ -89,7 -90,8 +90,7 @@@
      <string name="common_yes">Yes</string>
      <string name="common_no">No</string>
      <string name="common_ok">OK</string>
 -    <string name="common_cancel_download">Cancel download</string>
 -    <string name="common_cancel_upload">Cancel upload</string>
 +    <string name="common_cancel_sync">Cancel synchronization</string>
      <string name="common_cancel">Cancel</string>
      <string name="common_save_exit">Save &amp; Exit</string>
      <string name="common_error">Error</string>
        <string name="prefs_category_security">Security</string>
  
        <string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
 -    <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
 +    <string name="sync_folder_failed_content">Synchronization of %1$s folder could not be completed</string>
  
        <string name="shared_subject_header">shared</string>
        <string name="with_you_subject_header">with you</string>
  package com.owncloud.android.files;
  
  import android.accounts.Account;
+ import android.content.ActivityNotFoundException;
+ import android.content.Context;
  import android.content.Intent;
+ import android.content.pm.PackageManager;
+ import android.content.pm.ResolveInfo;
  import android.net.Uri;
  import android.support.v4.app.DialogFragment;
  import android.webkit.MimeTypeMap;
@@@ -43,6 -47,8 +47,8 @@@ import com.owncloud.android.ui.dialog.S
  
  import org.apache.http.protocol.HTTP;
  
+ import java.util.List;
  /**
   *
   */
@@@ -86,21 -92,46 +92,46 @@@ public class FileOperationsHelper 
                      );
                  }
              }
-             
-             Intent chooserIntent;
+             Intent openFileWithIntent;
              if (intentForGuessedMimeType != null) {
-                 chooserIntent = Intent.createChooser(intentForGuessedMimeType, mFileActivity.getString(R.string.actionbar_open_with));
+                 openFileWithIntent = intentForGuessedMimeType;
              } else {
-                 chooserIntent = Intent.createChooser(intentForSavedMimeType, mFileActivity.getString(R.string.actionbar_open_with));
+                 openFileWithIntent = intentForSavedMimeType;
              }
  
-             mFileActivity.startActivity(chooserIntent);
+             List<ResolveInfo> launchables = mFileActivity.getPackageManager().
+                     queryIntentActivities(openFileWithIntent, PackageManager.GET_INTENT_FILTERS);
+             if(launchables != null && launchables.size() > 0) {
+                 try {
+                     mFileActivity.startActivity(
+                             Intent.createChooser(
+                                     openFileWithIntent, mFileActivity.getString(R.string.actionbar_open_with)
+                             )
+                     );
+                 } catch (ActivityNotFoundException anfe) {
+                     showNoAppForFileTypeToast(mFileActivity.getApplicationContext());
+                 }
+             } else {
+                 showNoAppForFileTypeToast(mFileActivity.getApplicationContext());
+             }
  
          } else {
              Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
          }
      }
  
+     /**
+      * Displays a toast stating that no application could be found to open the file.
+      *
+      * @param context the context to be able to show a toast.
+      */
+     private void showNoAppForFileTypeToast(Context context) {
+         Toast.makeText(context,
+                 R.string.file_list_no_app_for_file_type, Toast.LENGTH_SHORT)
+                 .show();
+     }
  
      public void shareFileWithLink(OCFile file) {
  
          }
      }
  
 -
 +    /**
 +     * Request the synchronization of a file or folder with the OC server, including its contents.
 +     *
 +     * @param file          The file or folder to synchronize
 +     */
      public void syncFile(OCFile file) {
 -
          if (!file.isFolder()){
              Intent intent = new Intent(mFileActivity, OperationsService.class);
              intent.setAction(OperationsService.ACTION_SYNC_FILE);
              intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
              intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
              mFileActivity.startService(intent);
 +
          }
      }
  
  
          // for both files and folders
          FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
 -        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
          if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
              downloaderBinder.cancel(account, file);
 -
 -            // TODO - review why is this here, and solve in a better way
 -            // Remove etag for parent, if file is a favorite
 -            if (file.isFavorite()) {
 -                OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
 -                parent.setEtag("");
 -                mFileActivity.getStorageManager().saveFile(parent);
 -            }
 -
 -        } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
 +        }
 +        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
 +        if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
              uploaderBinder.cancel(account, file);
          }
      }
  package com.owncloud.android.files.services;
  
  import java.io.File;
 -import java.io.IOException;
  import java.util.AbstractList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Vector;
 -import java.util.concurrent.ConcurrentHashMap;
 -import java.util.concurrent.ConcurrentMap;
  
  import android.accounts.Account;
  import android.accounts.AccountManager;
 -import android.accounts.AccountsException;
  import android.accounts.OnAccountsUpdateListener;
  import android.app.NotificationManager;
  import android.app.PendingIntent;
@@@ -42,7 -46,6 +42,7 @@@ import android.os.Looper
  import android.os.Message;
  import android.os.Process;
  import android.support.v4.app.NotificationCompat;
 +import android.util.Pair;
  import android.webkit.MimeTypeMap;
  
  import com.owncloud.android.R;
@@@ -83,7 -86,6 +83,7 @@@ public class FileUploader extends Servi
      public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
      public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
      public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
 +    public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
      public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
  
      public static final String KEY_FILE = "FILE";
      private ServiceHandler mServiceHandler;
      private IBinder mBinder;
      private OwnCloudClient mUploadClient = null;
 -    private Account mLastAccount = null;
 +    private Account mCurrentAccount = null;
      private FileDataStorageManager mStorageManager;
  
 -    private ConcurrentMap<String, UploadFileOperation> mPendingUploads =
 -            new ConcurrentHashMap<String, UploadFileOperation>();
 +    private IndexedForest<UploadFileOperation> mPendingUploads = new IndexedForest<UploadFileOperation>();
      private UploadFileOperation mCurrentUpload = null;
  
      private NotificationManager mNotificationManager;
      }
  
      /**
 -     * Builds a key for mPendingUploads from the account and file to upload
 -     *
 -     * @param account   Account where the file to upload is stored
 -     * @param file      File to upload
 -     */
 -    private String buildRemoteName(Account account, OCFile file) {
 -        return account.name + file.getRemotePath();
 -    }
 -
 -    private String buildRemoteName(Account account, String remotePath) {
 -        return account.name + remotePath;
 -    }
 -
 -    /**
       * Checks if an ownCloud server version should support chunked uploads.
       *
       * @param version OwnCloud version instance corresponding to an ownCloud
       *            server.
       * @return 'True' if the ownCloud server with version supports chunked
       *         uploads.
 +     *
 +     * TODO - move to OCClient
       */
      private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
          return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
              files = new OCFile[localPaths.length];
              for (int i = 0; i < localPaths.length; i++) {
                  files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
 -                        ((mimeTypes != null) ? mimeTypes[i] : null), storageManager);
 +                        ((mimeTypes != null) ? mimeTypes[i] : null));
                  if (files[i] == null) {
                      // TODO @andomaex add failure Notification
                      return Service.START_NOT_STICKY;
          UploadFileOperation newUpload = null;
          try {
              for (int i = 0; i < files.length; i++) {
 -                uploadKey = buildRemoteName(account, files[i].getRemotePath());
 -                newUpload = new UploadFileOperation(account, files[i], chunked, isInstant,
 +                newUpload = new UploadFileOperation(
 +                        account,
 +                        files[i],
 +                        chunked,
 +                        isInstant,
                          forceOverwrite, localAction,
 -                        getApplicationContext());
 +                        getApplicationContext()
 +                );
                  if (isInstant) {
                      newUpload.setRemoteFolderToBeCreated();
                  }
 -                // Grants that the file only upload once time
 -                mPendingUploads.putIfAbsent(uploadKey, newUpload);
 -
                  newUpload.addDatatransferProgressListener(this);
 -                newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
 +                newUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder);
 +                Pair<String, String> putResult = mPendingUploads.putIfAbsent(
 +                        account, files[i].getRemotePath(), newUpload
 +                );
 +                uploadKey = putResult.first;
                  requestedUploads.add(uploadKey);
              }
  
              msg.obj = requestedUploads;
              mServiceHandler.sendMessage(msg);
          }
 -        Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
          return Service.START_NOT_STICKY;
      }
  
          /**
           * Cancels a pending or current upload of a remote file.
           *
 -         * @param account Owncloud account where the remote file will be stored.
 -         * @param file A file in the queue of pending uploads
 +         * @param account   ownCloud account where the remote file will be stored.
 +         * @param file      A file in the queue of pending uploads
           */
          public void cancel(Account account, OCFile file) {
 -            UploadFileOperation upload;
 -            synchronized (mPendingUploads) {
 -                upload = mPendingUploads.remove(buildRemoteName(account, file));
 -            }
 +            Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(account, file.getRemotePath());
 +            UploadFileOperation upload = removeResult.first;
              if (upload != null) {
                  upload.cancel();
 +            } else {
 +                if (mCurrentUpload != null && mCurrentAccount != null &&
 +                        mCurrentUpload.getRemotePath().startsWith(file.getRemotePath()) &&
 +                        account.name.equals(mCurrentAccount.name)) {
 +                    mCurrentUpload.cancel();
 +                }
              }
          }
  
          /**
 -         * Cancels a pending or current upload for an account
 +         * Cancels all the uploads for an account
           *
 -         * @param account Owncloud accountName where the remote file will be stored.
 +         * @param account   ownCloud account.
           */
          public void cancel(Account account) {
              Log_OC.d(TAG, "Account= " + account.name);
                  }
              }
              // Cancel pending uploads
 -            cancelUploadForAccount(account.name);
 +            cancelUploadsForAccount(account);
          }
  
          public void clearListeners() {
              mBoundListeners.clear();
          }
  
 +
          /**
           * Returns True when the file described by 'file' is being uploaded to
           * the ownCloud account 'account' or waiting for it
           * @param file      A file that could be in the queue of pending uploads
           */
          public boolean isUploading(Account account, OCFile file) {
 -            if (account == null || file == null)
 -                return false;
 -            String targetKey = buildRemoteName(account, file);
 -            synchronized (mPendingUploads) {
 -                if (file.isFolder()) {
 -                    // this can be slow if there are many uploads :(
 -                    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));
 -                }
 -            }
 +            if (account == null || file == null) return false;
 +            return (mPendingUploads.contains(account, file.getRemotePath()));
          }
  
  
          }
  
          /**
 -         * Review uploads and cancel it if its account doesn't exist
 +         * Builds a key for the map of listeners.
 +         *
 +         * TODO remove and replace key with file.getFileId() after changing current policy (upload file, then
 +         * add to local database) to better policy (add to local database, then upload)
 +         *
 +         * @param account       ownCloud account where the file to upload belongs.
 +         * @param file          File to upload
 +         * @return              Key
           */
 -        public void checkAccountOfCurrentUpload() {
 -            if (mCurrentUpload != null &&
 -                    !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
 -                mCurrentUpload.cancel();
 -            }
 -            // The rest of uploads are cancelled when they try to start
 +        private String buildRemoteName(Account account, OCFile file) {
 +            return account.name + file.getRemotePath();
          }
 +
      }
  
      /**
      /**
       * Core upload method: sends the file(s) to upload
       *
 -     * @param uploadKey Key to access the upload to perform, contained in
 -     *            mPendingUploads
 +     * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
       */
      public void uploadFile(String uploadKey) {
  
 -        synchronized (mPendingUploads) {
 -            mCurrentUpload = mPendingUploads.get(uploadKey);
 -        }
 +        mCurrentUpload = mPendingUploads.get(uploadKey);
  
          if (mCurrentUpload != null) {
 -
              // Detect if the account exists
              if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
                  Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists");
                  RemoteOperationResult uploadResult = null, grantResult;
  
                  try {
 -                    /// prepare client object to send requests to the ownCloud server
 -                    if (mUploadClient == null ||
 -                            !mLastAccount.equals(mCurrentUpload.getAccount())) {
 -                        mLastAccount = mCurrentUpload.getAccount();
 -                        mStorageManager =
 -                                new FileDataStorageManager(mLastAccount, getContentResolver());
 -                        OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
 -                        mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
 -                                getClientFor(ocAccount, this);
 -                    }
 +                    /// prepare client object to send the request to the ownCloud server
 +                    if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) {
 +                        mCurrentAccount = mCurrentUpload.getAccount();
 +                        mStorageManager = new FileDataStorageManager(
 +                                mCurrentAccount,
 +                                getContentResolver()
 +                        );
 +                    }   // else, reuse storage manager from previous operation
 +
 +                    // always get client from client manager, to get fresh credentials in case of update
 +                    OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
 +                    mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
 +                            getClientFor(ocAccount, this);
 +
  
                      /// check the existence of the parent folder for the file to upload
                      String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
                          uploadResult = mCurrentUpload.execute(mUploadClient);
                          if (uploadResult.isSuccess()) {
                              saveUploadedFile();
 +
 +                        } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) {
 +                            mStorageManager.saveConflict(mCurrentUpload.getFile(), true);
                          }
                      } else {
                          uploadResult = grantResult;
                      }
  
 -                } catch (AccountsException e) {
 -                    Log_OC.e(TAG, "Error while trying to get autorization for " +
 -                            mLastAccount.name, e);
 -                    uploadResult = new RemoteOperationResult(e);
 -
 -                } catch (IOException e) {
 -                    Log_OC.e(TAG, "Error while trying to get autorization for " +
 -                            mLastAccount.name, e);
 +                } catch (Exception e) {
 +                    Log_OC.e(TAG, "Error uploading", e);
                      uploadResult = new RemoteOperationResult(e);
  
                  } finally {
 -                    synchronized (mPendingUploads) {
 -                        mPendingUploads.remove(uploadKey);
 -                        Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
 -                    }
 -                    if (uploadResult != null && uploadResult.isException()) {
 -                        // enforce the creation of a new client object for next uploads;
 -                        // this grant that a new socket will be created in the future if
 -                        // the current exception is due to an abrupt lose of network connection
 -                        mUploadClient = null;
 +                    Pair<UploadFileOperation, String> removeResult;
 +                    if (mCurrentUpload.wasRenamed()) {
 +                        removeResult = mPendingUploads.removePayload(
 +                                mCurrentAccount,
 +                                mCurrentUpload.getOldFile().getRemotePath()
 +                        );
 +                    } else {
 +                        removeResult = mPendingUploads.removePayload(
 +                                mCurrentAccount,
 +                                mCurrentUpload.getRemotePath()
 +                        );
                      }
 -                }
  
 -                /// notify result
 -                notifyUploadResult(uploadResult, mCurrentUpload);
 -                sendFinalBroadcast(mCurrentUpload, uploadResult);
 +                    /// notify result
 +                    notifyUploadResult(mCurrentUpload, uploadResult);
 +
 +                    sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
 +                }
  
              } else {
                  // Cancel the transfer
                  Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() +
                          " doesn't exist");
 -                cancelUploadForAccount(mCurrentUpload.getAccount().name);
 +                cancelUploadsForAccount(mCurrentUpload.getAccount());
  
              }
          }
       * synchronized with the server, specially the modification time and Etag
       * (where available)
       *
 -     * TODO refactor this ugly thing
 +     * TODO move into UploadFileOperation
       */
      private void saveUploadedFile() {
          OCFile file = mCurrentUpload.getFile();
          if (result.isSuccess()) {
              updateOCFile(file, (RemoteFile) result.getData().get(0));
              file.setLastSyncDateForProperties(syncDate);
 +        } else {
 +            Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt...");
          }
  
          // / maybe this would be better as part of UploadFileOperation... or
              if (oldFile.fileExists()) {
                  oldFile.setStoragePath(null);
                  mStorageManager.saveFile(oldFile);
 +                mStorageManager.saveConflict(oldFile, false);
  
              } // else: it was just an automatic renaming due to a name
              // coincidence; nothing else is needed, the storagePath is right
          }
          file.setNeedsUpdateThumbnail(true);
          mStorageManager.saveFile(file);
 +        mStorageManager.saveConflict(file, false);
++        
+         mStorageManager.triggerMediaScan(file.getStoragePath());
++
      }
  
      private void updateOCFile(OCFile file, RemoteFile remoteFile) {
          file.setMimetype(remoteFile.getMimeType());
          file.setModificationTimestamp(remoteFile.getModifiedTimestamp());
          file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp());
 -        // file.setEtag(remoteFile.getEtag());    // TODO Etag, where available
 +        file.setEtag(remoteFile.getEtag());
          file.setRemoteId(remoteFile.getRemoteId());
      }
  
 -    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType,
 -                                           FileDataStorageManager storageManager) {
 +    private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) {
  
          // MIME type
          if (mimeType == null || mimeType.length() <= 0) {
      /**
       * Updates the status notification with the result of an upload operation.
       *
 -     * @param uploadResult Result of the upload operation.
 -     * @param upload Finished upload operation
 +     * @param uploadResult  Result of the upload operation.
 +     * @param upload        Finished upload operation
       */
 -    private void notifyUploadResult(
 -            RemoteOperationResult uploadResult, UploadFileOperation upload) {
 +    private void notifyUploadResult(UploadFileOperation upload,
 +                                    RemoteOperationResult uploadResult) {
          Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
          // / cancelled operation or success -> silent removal of progress notification
          mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
       * Sends a broadcast in order to the interested activities can update their
       * view
       *
 -     * @param upload Finished upload operation
 -     * @param uploadResult Result of the upload operation
 +     * @param upload                    Finished upload operation
 +     * @param uploadResult              Result of the upload operation
 +     * @param unlinkedFromRemotePath    Path in the uploads tree where the upload was unlinked from
       */
 -    private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
 +    private void sendBroadcastUploadFinished(
 +            UploadFileOperation upload,
 +            RemoteOperationResult uploadResult,
 +            String unlinkedFromRemotePath) {
 +
          Intent end = new Intent(getUploadFinishMessage());
          end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
          // path, after
          end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
          end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
          end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
 +        if (unlinkedFromRemotePath != null) {
 +            end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
 +        }
 +
          sendStickyBroadcast(end);
      }
  
       * @param localPath         Full path to a file in the local file system.
       * @param mimeType          MIME type of the file.
       * @return true if is needed to add the pdf file extension to the file
 +     *
 +     * TODO - move to OCFile or Utils class
       */
      private boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
                                                                   String mimeType) {
  
      /**
       * Remove uploads of an account
 -     * @param accountName       Name of an OC account
 +     *
 +     * @param account       Downloads account to remove
       */
 -    private void cancelUploadForAccount(String accountName){
 -        // this can be slow if there are many uploads :(
 -        Iterator<String> it = mPendingUploads.keySet().iterator();
 -        Log_OC.d(TAG, "Number of pending updloads= "  + mPendingUploads.size());
 -        while (it.hasNext()) {
 -            String key = it.next();
 -            Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
 -            if (key.startsWith(accountName)) {
 -                synchronized (mPendingUploads) {
 -                    mPendingUploads.remove(key);
 -                }
 -            }
 -        }
 +    private void cancelUploadsForAccount(Account account){
 +        // Cancel pending uploads
 +        mPendingUploads.remove(account);
      }
  }
@@@ -30,6 -30,7 +30,6 @@@ import java.util.Map
  
  import org.apache.jackrabbit.webdav.DavException;
  
 -import com.owncloud.android.MainApp;
  import com.owncloud.android.R;
  import com.owncloud.android.authentication.AuthenticatorActivity;
  import com.owncloud.android.datamodel.FileDataStorageManager;
@@@ -58,7 -59,7 +58,7 @@@ import android.support.v4.app.Notificat
   * Implementation of {@link AbstractThreadedSyncAdapter} responsible for synchronizing 
   * ownCloud files.
   * 
 - * Performs a full synchronization of the account recieved in {@link #onPerformSync(Account, Bundle,
 + * Performs a full synchronization of the account received in {@link #onPerformSync(Account, Bundle,
   * String, ContentProviderClient, SyncResult)}.
   */
  public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
@@@ -76,7 -77,9 +76,7 @@@
              ".EVENT_FULL_SYNC_END";
      public static final String EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED =
              FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED";
 -    //public static final String EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED =
 -    // FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED";
 -    
 +
      public static final String EXTRA_ACCOUNT_NAME = FileSyncAdapter.class.getName() +
              ".EXTRA_ACCOUNT_NAME";
      public static final String EXTRA_FOLDER_PATH = FileSyncAdapter.class.getName() +
          if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult))
              return;
          
 -        /*
 -        OCFile folder, 
 -        long currentSyncTime, 
 -        boolean updateFolderProperties,
 -        boolean syncFullAccount,
 -        DataStorageManager dataStorageManager, 
 -        Account account, 
 -        Context context ) {
 -        }
 -        */
          // folder synchronization
          RefreshFolderOperation synchFolderOp = new RefreshFolderOperation( folder,
                                                                                     mCurrentSyncTime,
                  // synchronize children folders 
                  List<OCFile> children = synchFolderOp.getChildren();
                  // beware of the 'hidden' recursion here!
 -                fetchChildren(folder, children, synchFolderOp.getRemoteFolderChanged());
 +                syncChildren(children);
              }
              
          } else {
  
      /**
       * Triggers the synchronization of any folder contained in the list of received files.
 +     *
 +     * No consideration of etag here because it MUST walk down anyway, in case that kept-in-sync files
 +     * have local changes.
       * 
       * @param files         Files to recursively synchronize.
       */
 -    private void fetchChildren(OCFile parent, List<OCFile> files, boolean parentEtagChanged) {
 +    private void syncChildren(List<OCFile> files) {
          int i;
 -        OCFile newFile = null;
 -        //String etag = null;
 -        //boolean syncDown = false;
 +        OCFile newFile;
          for (i=0; i < files.size() && !mCancellation; i++) {
              newFile = files.get(i);
              if (newFile.isFolder()) {
 -                /*
 -                etag = newFile.getEtag();
 -                syncDown = (parentEtagChanged || etag == null || etag.length() == 0);
 -                if(syncDown) { */
 -                    synchronizeFolder(newFile);
 -                    //sendLocalBroadcast(EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED, parent.getRemotePath(),
 -                    // null);
 -                //}
 +                synchronizeFolder(newFile);
              }
          }
         
      private NotificationCompat.Builder createNotificationBuilder() {
          NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext());
          notificationBuilder.setSmallIcon(R.drawable.notification_icon).setAutoCancel(true);
+         notificationBuilder.setColor(getContext().getResources().getColor(R.color.primary));
          return notificationBuilder;
      }
      
@@@ -1048,11 -1048,7 +1048,11 @@@ public class FileDisplayActivity extend
                          (uploadedRemotePath.startsWith(currentDir.getRemotePath()));
  
                  if (sameAccount && isDescendant) {
 -                    refreshListOfFilesFragment();
 +                    String linkedToRemotePath =
 +                            intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH);
 +                    if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
 +                        refreshListOfFilesFragment();
 +                    }
                  }
  
                  boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT,
  
          }
  
 +        // TODO refactor this receiver, and maybe DownloadFinishReceiver; this method is duplicated :S
 +        private boolean isAscendant(String linkedToRemotePath) {
 +            OCFile currentDir = getCurrentDir();
 +            return (
 +                    currentDir != null &&
 +                            currentDir.getRemotePath().startsWith(linkedToRemotePath)
 +            );
 +        }
 +
 +
      }
  
  
       */
      private class DownloadFinishReceiver extends BroadcastReceiver {
  
 -        //int refreshCounter = 0;
          @Override
          public void onReceive(Context context, Intent intent) {
              try {
 -                boolean sameAccount = isSameAccount(context, intent);
 +                boolean sameAccount = isSameAccount(intent);
                  String downloadedRemotePath =
                          intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
                  boolean isDescendant = isDescendant(downloadedRemotePath);
                      String linkedToRemotePath =
                              intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH);
                      if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
 -                        //Log_OC.v(TAG, "refresh #" + ++refreshCounter);
                          refreshListOfFilesFragment();
                      }
                      refreshSecondFragment(
              );
          }
  
 -        private boolean isSameAccount(Context context, Intent intent) {
 +        private boolean isSameAccount(Intent intent) {
              String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
              return (accountName != null && getAccount() != null &&
                      accountName.equals(getAccount().name));
                  OCFile syncedFile = operation.getLocalFile();
                  onTransferStateChanged(syncedFile, true, true);
                  invalidateOptionsMenu();
+                 refreshShowDetails();
              }
          }
      }
@@@ -22,6 -22,7 +22,7 @@@
  package com.owncloud.android.ui.fragment;
  
  import android.accounts.Account;
+ import android.graphics.Bitmap;
  import android.os.Bundle;
  import android.view.LayoutInflater;
  import android.view.Menu;
@@@ -35,9 -36,11 +36,11 @@@ import android.widget.ImageView
  import android.widget.ProgressBar;
  import android.widget.TextView;
  
+ import com.owncloud.android.MainApp;
  import com.owncloud.android.R;
  import com.owncloud.android.datamodel.FileDataStorageManager;
  import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  import com.owncloud.android.files.FileMenuFilter;
  import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
  import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
@@@ -245,8 -248,9 +248,8 @@@ public class FileDetailFragment extend
                  dialog.show(getFragmentManager(), FTAG_RENAME_FILE);
                  return true;
              }
 -            case R.id.action_cancel_download:
 -            case R.id.action_cancel_upload: {
 -                ((FileDisplayActivity) mContainerActivity).cancelTransference(getFile());
 +            case R.id.action_cancel_sync: {
 +                ((FileDisplayActivity)mContainerActivity).cancelTransference(getFile());
                  return true;
              }
              case R.id.action_download_file:
          }
      }
  
 -
      /**
       * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.
       *
  
              // set file details
              setFilename(file.getFileName());
-             setFiletype(file.getMimetype(), file.getFileName());
+             setFiletype(file);
              setFilesize(file.getFileLength());
  
              setTimeModified(file.getModificationTimestamp());
  
      /**
       * Updates the MIME type in view
-      * @param mimetype      MIME type to set
-      * @param filename      Name of the file, to deduce the icon to use in case the MIME type is not precise enough
+      * @param file : An {@link OCFile}
       */
-     private void setFiletype(String mimetype, String filename) {
+     private void setFiletype(OCFile file) {
+         String mimetype = file.getMimetype();
          TextView tv = (TextView) getView().findViewById(R.id.fdType);
          if (tv != null) {
+                       // mimetype      MIME type to set
              String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);
              tv.setText(printableMimetype);
          }
          ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);
          if (iv != null) {
-             iv.setImageResource(MimetypeIconUtil.getFileTypeIconId(mimetype, filename));
+             Bitmap thumbnail;
+             iv.setTag(file.getFileId());
+             if (file.isImage()) {
+                 String tagId = String.valueOf(file.getRemoteId());
+                 thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId);
+                 if (thumbnail != null && !file.needsUpdateThumbnail()) {
+                     iv.setImageBitmap(thumbnail);
+                 } else {
+                     // generate new Thumbnail
+                     if (ThumbnailsCacheManager.cancelPotentialWork(file, iv)) {
+                         final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+                                 new ThumbnailsCacheManager.ThumbnailGenerationTask(
+                                         iv, mContainerActivity.getStorageManager(), mAccount
+                                 );
+                         if (thumbnail == null) {
+                             thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                         }
+                         final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
+                                 new ThumbnailsCacheManager.AsyncDrawable(
+                                         MainApp.getAppContext().getResources(),
+                                         thumbnail,
+                                         task
+                                 );
+                         iv.setImageDrawable(asyncDrawable);
+                         task.execute(file);
+                     }
+                 }
+             } else {
+                               // Name of the file, to deduce the icon to use in case the MIME type is not precise enough
+                               String filename = file.getFileName();
+                 iv.setImageResource(MimetypeIconUtil.getFileTypeIconId(mimetype, filename));
+                       }
          }
      }
  
@@@ -32,7 -32,6 +32,7 @@@ import com.owncloud.android.R
  import com.owncloud.android.datamodel.OCFile;
  import com.owncloud.android.lib.resources.files.RemoteFile;
  
 +import android.accounts.Account;
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.content.SharedPreferences;
@@@ -54,6 -53,8 +54,6 @@@ public class FileStorageUtils 
      public static Boolean mSortAscending = true;
  
      
 -    //private static final String LOG_TAG = "FileStorageUtils";
 -
      public static final String getSavePath(String accountName) {
          File sdCard = Environment.getExternalStorageDirectory();
          return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@");
       * Creates and populates a new {@link OCFile} object with the data read from the server.
       * 
       * @param remote    remote file read from the server (remote file or folder).
 -     * @return          New OCFile instance representing the remote resource described by we.
 +     * @return          New OCFile instance representing the remote resource described by remote.
       */
      public static OCFile fillOCFile(RemoteFile remote) {
          OCFile file = new OCFile(remote.getRemotePath());
          Collections.sort(files, new Comparator<OCFile>() {
              public int compare(OCFile o1, OCFile o2) {
                  if (o1.isFolder() && o2.isFolder()) {
-                     return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());
+                     return val * new AlphanumComparator().compare(o1, o2);
                  } else if (o1.isFolder()) {
                      return -1;
                  } else if (o2.isFolder()) {
          String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
          return (result != null) ? result : "";
      }
 -  
 +
 +    /**
 +     * Scans the default location for saving local copies of files searching for
 +     * a 'lost' file with the same full name as the {@link OCFile} received as
 +     * parameter.
 +     *
 +     * This method helps to keep linked local copies of the files when the app is uninstalled, and then
 +     * reinstalled in the device. OR after the cache of the app was deleted in system settings.
 +     *
 +     * The method is assuming that all the local changes in the file where synchronized in the past. This is dangerous,
 +     * but assuming the contrary could lead to massive unnecessary synchronizations of downloaded file after deleting
 +     * the app cache.
 +     *
 +     * This should be changed in the near future to avoid any chance of data loss, but we need to add some options
 +     * to limit hard automatic synchronizations to wifi, unless the user wants otherwise.
 +     *
 +     * @param file      File to associate a possible 'lost' local file.
 +     * @param account   Account holding file.
 +     */
 +    public static void searchForLocalFileInDefaultPath(OCFile file, Account account) {
 +        if (file.getStoragePath() == null && !file.isFolder()) {
 +            File f = new File(FileStorageUtils.getDefaultSavePathFor(account.name, file));
 +            if (f.exists()) {
 +                file.setStoragePath(f.getAbsolutePath());
 +                file.setLastSyncDateForData(f.lastModified());
 +            }
 +        }
 +    }
 +
  }