Merge remote-tracking branch 'upstream/develop' into navigationDrawer_update
authortobiasKaminsky <tobias@kaminsky.me>
Thu, 21 May 2015 19:12:44 +0000 (21:12 +0200)
committertobiasKaminsky <tobias@kaminsky.me>
Thu, 21 May 2015 19:12:44 +0000 (21:12 +0200)
22 files changed:
1  2 
res/layout/files.xml
res/layout/uploader_layout.xml
res/layout/uploader_list_item_layout.xml
res/menu/main_menu.xml
res/values/colors.xml
res/values/strings.xml
res/xml/preferences.xml
src/com/owncloud/android/MainApp.java
src/com/owncloud/android/datamodel/FileDataStorageManager.java
src/com/owncloud/android/operations/RefreshFolderOperation.java
src/com/owncloud/android/operations/SynchronizeFolderOperation.java
src/com/owncloud/android/ui/activity/FileDisplayActivity.java
src/com/owncloud/android/ui/activity/FolderPickerActivity.java
src/com/owncloud/android/ui/activity/Preferences.java
src/com/owncloud/android/ui/activity/UploadPathActivity.java
src/com/owncloud/android/ui/activity/Uploader.java
src/com/owncloud/android/ui/adapter/FileListListAdapter.java
src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java
src/com/owncloud/android/ui/fragment/OCFileListFragment.java
src/com/owncloud/android/ui/preview/PreviewImageActivity.java
src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java
src/com/owncloud/android/utils/BitmapUtils.java

Simple merge
Simple merge
          android:icon="@drawable/ic_action_create_dir"
          android:orderInCategory="2"
          android:showAsAction="always"
-         android:title="@string/actionbar_mkdir"/>
+         android:title="@string/actionbar_mkdir"
+         android:contentDescription="@string/actionbar_mkdir"/>
      <item
 -        android:id="@+id/action_sync_account"
 -        android:icon="@drawable/ic_action_refresh"
 -        android:orderInCategory="2"
 -        android:showAsAction="never"
 -        android:title="@string/actionbar_sync"
 -        android:contentDescription="@string/actionbar_sync"/>
 -    <item
 -        android:id="@+id/action_settings"
 -        android:icon="@drawable/ic_action_settings"
 -        android:orderInCategory="2"
 -        android:showAsAction="never"
 -        android:title="@string/actionbar_settings"
 -        android:contentDescription="@string/actionbar_settings"/>
 -    <item
 -        android:id="@+id/action_logger"
 -        android:icon="@drawable/ic_action_settings"
 -        android:orderInCategory="2"
 -        android:showAsAction="never"
 -        android:title="@string/actionbar_logger"
 -        android:contentDescription="@string/actionbar_logger"/>
 -      <item
          android:id="@+id/action_sort"
 -        android:icon="@android:drawable/ic_menu_sort_alphabetically"
 +        android:icon="@android:drawable/ic_menu_sort_by_size"
          android:orderInCategory="2"
 -        android:showAsAction="never"
 +        android:showAsAction="always"
-         android:title="@string/actionbar_sort"/>
+         android:title="@string/actionbar_sort"
 -        android:contentDescription="@string/actionbar_sort"/>
++        android:contentDescription="@string/actionbar_sort"/>/>
  
      <!-- <item android:id="@+id/search" android:title="@string/actionbar_search" android:icon="@drawable/ic_action_search"></item> -->
  
@@@ -22,6 -22,7 +22,8 @@@
      <color name="filelist_icon_backgorund">#DDDDDD</color>
      <color name="owncloud_blue_bright">#00ddff</color>
      <color name="list_item_lastmod_and_filesize_text">#989898</color>
 +    <color name="black">#000000</color>
+     <color name="textColor">#303030</color>
+     <color name="list_divider_background">#fff0f0f0</color>
      
  </resources>
        <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="subject_token">%1$s shared \"%2$s\" with you</string>
 +      <string name="shared_subject_header">shared</string>
 +      <string name="with_you_subject_header">with you</string>
++    
 +      <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
-       <string name="username">Username</string>
  
+     <string name="auth_refresh_button">Refresh connection</string>
+     <string name="auth_host_address">Server address</string>
+     <string name="common_error_out_memory">Not enough memory</string>
++    <string name="username">Username</string>
  </resources>
Simple merge
@@@ -119,12 -170,26 +174,34 @@@ public class MainApp extends Applicatio
      public static String getLogName() {
          return getAppContext().getResources().getString(R.string.log_name);
      }
 +    
 +    public static void showOnlyFilesOnDevice(boolean state){
 +        mOnlyOnDevice = state;
 +    }
 +    
 +    public static boolean getOnlyOnDevice(){
 +        return mOnlyOnDevice;
 +    }
+     // user agent
+     public static String getUserAgent() {
+         String appString = getAppContext().getResources().getString(R.string.user_agent);
+         String packageName = getAppContext().getPackageName();
+         String version = "";
+         PackageInfo pInfo = null;
+         try {
+             pInfo = getAppContext().getPackageManager().getPackageInfo(packageName, 0);
+             if (pInfo != null) {
+                 version = pInfo.versionName;
+             }
+         } catch (PackageManager.NameNotFoundException e) {
+             Log_OC.e(TAG, "Trying to get packageName", e.getCause());
+         }
+         // Mozilla/5.0 (Android) ownCloud-android/1.7.0
+         String userAgent = String.format(appString, version);
+         return userAgent;
+     }
  }
@@@ -38,15 -48,8 +40,16 @@@ import android.content.OperationApplica
  import android.database.Cursor;
  import android.net.Uri;
  import android.os.RemoteException;
+ import android.provider.MediaStore;
  
 +import com.owncloud.android.MainApp;
 +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 +import com.owncloud.android.lib.common.utils.Log_OC;
 +import com.owncloud.android.lib.resources.files.FileUtils;
 +import com.owncloud.android.lib.resources.shares.OCShare;
 +import com.owncloud.android.lib.resources.shares.ShareType;
 +import com.owncloud.android.utils.FileStorageUtils;
 +
  public class FileDataStorageManager {
  
      public static final int ROOT_PARENT_ID = 0;
  
      private boolean removeLocalFolder(OCFile folder) {
          boolean success = true;
-         File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder));
+         String localFolderPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder);
+         File localFolder = new File(localFolderPath);
          if (localFolder.exists()) {
              // stage 1: remove the local files already registered in the files database
 -            Vector<OCFile> files = getFolderContent(folder.getFileId());
 +            Vector<OCFile> files = getFolderContent(folder.getFileId(), false);
              if (files != null) {
                  for (OCFile file : files) {
                      if (file.isFolder()) {
index 0000000,50a35fd..ecc642e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,613 +1,613 @@@
 -                mChildren = mStorageManager.getFolderContent(mLocalFolder);
+ /**
+  *   ownCloud Android client application
+  *
+  *   @author David A. Velasco
+  *   Copyright (C) 2015 ownCloud Inc.
+  *
+  *   This program is free software: you can redistribute it and/or modify
+  *   it under the terms of the GNU General Public License version 2,
+  *   as published by the Free Software Foundation.
+  *
+  *   This program is distributed in the hope that it will be useful,
+  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  *   GNU General Public License for more details.
+  *
+  *   You should have received a copy of the GNU General Public License
+  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  *
+  */
+ package com.owncloud.android.operations;
+ import java.io.File;
+ import java.io.FileInputStream;
+ import java.io.FileOutputStream;
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.io.OutputStream;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Vector;
+ import org.apache.http.HttpStatus;
+ import android.accounts.Account;
+ import android.content.Context;
+ import android.content.Intent;
+ import android.util.Log;
+ //import android.support.v4.content.LocalBroadcastManager;
+ import com.owncloud.android.MainApp;
+ import com.owncloud.android.datamodel.FileDataStorageManager;
+ import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.lib.common.OwnCloudClient;
+ import com.owncloud.android.lib.resources.shares.OCShare;
+ import com.owncloud.android.lib.common.operations.RemoteOperation;
+ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+ import com.owncloud.android.lib.common.utils.Log_OC;
+ import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
+ import com.owncloud.android.lib.resources.files.FileUtils;
+ import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+ import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
+ import com.owncloud.android.lib.resources.files.RemoteFile;
+ import com.owncloud.android.syncadapter.FileSyncAdapter;
+ import com.owncloud.android.utils.FileStorageUtils;
+ /**
+  *  Remote operation performing the synchronization of the list of files contained 
+  *  in a folder identified with its remote path.
+  *  
+  *  Fetches the list and properties of the files contained in the given folder, including their 
+  *  properties, and updates the local database with them.
+  *  
+  *  Does NOT enter in the child folders to synchronize their contents also.
+  */
+ public class RefreshFolderOperation extends RemoteOperation {
+     private static final String TAG = RefreshFolderOperation.class.getSimpleName();
+     public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED  = 
+             RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
+     public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED    = 
+             RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
+     
+     /** Time stamp for the synchronization process in progress */
+     private long mCurrentSyncTime;
+     
+     /** Remote folder to synchronize */
+     private OCFile mLocalFolder;
+     
+     /** Access to the local database */
+     private FileDataStorageManager mStorageManager;
+     
+     /** Account where the file to synchronize belongs */
+     private Account mAccount;
+     
+     /** Android context; necessary to send requests to the download service */
+     private Context mContext;
+     
+     /** Files and folders contained in the synchronized folder after a successful operation */
+     private List<OCFile> mChildren;
+     /** Counter of conflicts found between local and remote files */
+     private int mConflictsFound;
+     /** Counter of failed operations in synchronization of kept-in-sync files */
+     private int mFailsInFavouritesFound;
+     /**
+      * Map of remote and local paths to files that where locally stored in a location 
+      * out of the ownCloud folder and couldn't be copied automatically into it 
+      **/
+     private Map<String, String> mForgottenLocalFiles;
+     /** 'True' means that this operation is part of a full account synchronization */ 
+     private boolean mSyncFullAccount;
+     /** 'True' means that Share resources bound to the files into should be refreshed also */
+     private boolean mIsShareSupported;
+     
+     /** 'True' means that the remote folder changed and should be fetched */
+     private boolean mRemoteFolderChanged;
+     /** 'True' means that Etag will be ignored */
+     private boolean mIgnoreETag;
+     
+     /**
+      * Creates a new instance of {@link RefreshFolderOperation}.
+      * 
+      * @param   folder                  Folder to synchronize.
+      * @param   currentSyncTime         Time stamp for the synchronization process in progress.
+      * @param   syncFullAccount         'True' means that this operation is part of a full account 
+      *                                  synchronization.
+      * @param   isShareSupported        'True' means that the server supports the sharing API.           
+      * @param   ignoreETag              'True' means that the content of the remote folder should
+      *                                  be fetched and updated even though the 'eTag' did not 
+      *                                  change.  
+      * @param   dataStorageManager      Interface with the local database.
+      * @param   account                 ownCloud account where the folder is located. 
+      * @param   context                 Application context.
+      */
+     public RefreshFolderOperation(OCFile folder,
+                                   long currentSyncTime,
+                                   boolean syncFullAccount,
+                                   boolean isShareSupported,
+                                   boolean ignoreETag,
+                                   FileDataStorageManager dataStorageManager,
+                                   Account account,
+                                   Context context) {
+         mLocalFolder = folder;
+         mCurrentSyncTime = currentSyncTime;
+         mSyncFullAccount = syncFullAccount;
+         mIsShareSupported = isShareSupported;
+         mStorageManager = dataStorageManager;
+         mAccount = account;
+         mContext = context;
+         mForgottenLocalFiles = new HashMap<String, String>();
+         mRemoteFolderChanged = false;
+         mIgnoreETag = ignoreETag;
+     }
+     
+     
+     public int getConflictsFound() {
+         return mConflictsFound;
+     }
+     
+     public int getFailsInFavouritesFound() {
+         return mFailsInFavouritesFound;
+     }
+     
+     public Map<String, String> getForgottenLocalFiles() {
+         return mForgottenLocalFiles;
+     }
+     
+     /**
+      * Returns the list of files and folders contained in the synchronized folder, 
+      * if called after synchronization is complete.
+      * 
+      * @return  List of files and folders contained in the synchronized folder.
+      */
+     public List<OCFile> getChildren() {
+         return mChildren;
+     }
+     
+     /**
+      * Performs the synchronization.
+      * 
+      * {@inheritDoc}
+      */
+     @Override
+     protected RemoteOperationResult run(OwnCloudClient client) {
+         RemoteOperationResult result = null;
+         mFailsInFavouritesFound = 0;
+         mConflictsFound = 0;
+         mForgottenLocalFiles.clear();
+         
+         if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
+             updateOCVersion(client);
+         }
+         
+         result = checkForChanges(client);
+         
+         if (result.isSuccess()) {
+             if (mRemoteFolderChanged) {
+                 result = fetchAndSyncRemoteFolder(client);
+             } else {
 -        List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
++                mChildren = mStorageManager.getFolderContent(mLocalFolder, false);
+             }
+         }
+         
+         if (!mSyncFullAccount) {            
+             sendLocalBroadcast(
+                     EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
+             );
+         }
+         
+         if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
+             refreshSharesForFolder(client); // share result is ignored 
+         }
+         
+         if (!mSyncFullAccount) {            
+             sendLocalBroadcast(
+                     EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
+             );
+         }
+         
+         return result;
+         
+     }
+     private void updateOCVersion(OwnCloudClient client) {
+         UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
+         RemoteOperationResult result = update.execute(client);
+         if (result.isSuccess()) {
+             mIsShareSupported = update.getOCVersion().isSharedSupported();
+         }
+     }
+     
+     private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+         mRemoteFolderChanged = true;
+         RemoteOperationResult result = null;
+         String remotePath = null;
+         remotePath = mLocalFolder.getRemotePath();
+         Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
+         
+         // remote request 
+         ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+         result = operation.execute(client);
+         if (result.isSuccess()){
+             OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+             if (!mIgnoreETag) {
+                 // check if remote and local folder are different
+                 mRemoteFolderChanged = 
+                         !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
+             }
+             result = new RemoteOperationResult(ResultCode.OK);
+         
+             Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                     (mRemoteFolderChanged ? "changed" : "not changed"));
+             
+         } else {
+             // check failed
+             if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+                 removeLocalFolder();
+             }
+             if (result.isException()) {
+                 Log_OC.e(TAG, "Checked " + mAccount.name + remotePath  + " : " + 
+                         result.getLogMessage(), result.getException());
+             } else {
+                 Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                         result.getLogMessage());
+             }
+         }
+         
+         return result;
+     }
+     private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
+         String remotePath = mLocalFolder.getRemotePath();
+         ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
+         RemoteOperationResult result = operation.execute(client);
+         Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+         
+         if (result.isSuccess()) {
+             synchronizeData(result.getData(), client);
+             if (mConflictsFound > 0  || mFailsInFavouritesFound > 0) { 
+                 result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);   
+                     // should be a different result code, but will do the job
+             }
+         } else {
+             if (result.getCode() == ResultCode.FILE_NOT_FOUND)
+                 removeLocalFolder();
+         }
+         
+         return result;
+     }
+     
+     private void removeLocalFolder() {
+         if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+             String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
+             mStorageManager.removeFolder(
+                     mLocalFolder, 
+                     true, 
+                     (   mLocalFolder.isDown() && 
+                             mLocalFolder.getStoragePath().startsWith(currentSavePath)
+                     )
+             );
+         }
+     }
+     /**
+      *  Synchronizes the data retrieved from the server about the contents of the target folder 
+      *  with the current data in the local database.
+      *  
+      *  Grants that mChildren is updated with fresh data after execution.
+      *  
+      *  @param folderAndFiles   Remote folder and children files in Folder 
+      *  
+      *  @param client           Client instance to the remote server where the data were 
+      *                          retrieved.  
+      *  @return                 'True' when any change was made in the local data, 'false' otherwise
+      */
+     private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
+         // get 'fresh data' from the database
+         mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
+         // parse data from remote folder 
+         OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
+         remoteFolder.setParentId(mLocalFolder.getParentId());
+         remoteFolder.setFileId(mLocalFolder.getFileId());
+         
+         Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() 
+                 + " changed - starting update of local data ");
+         
+         List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
+         List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+         // get current data about local contents of the folder to synchronize
++        List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder, true);
+         Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
+         for (OCFile file : localFiles) {
+             localFilesMap.put(file.getRemotePath(), file);
+         }
+         
+         // loop to update every child
+         OCFile remoteFile = null, localFile = null;
+         for (int i=1; i<folderAndFiles.size(); i++) {
+             /// new OCFile instance with the data from the server
+             remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
+             remoteFile.setParentId(mLocalFolder.getFileId());
+             /// retrieve local data for the read file 
+             //  localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+             localFile = localFilesMap.remove(remoteFile.getRemotePath());
+             
+             /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
+             remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
+             if (localFile != null) {
+                 // some properties of local state are kept unmodified
+                 remoteFile.setFileId(localFile.getFileId());
+                 remoteFile.setKeepInSync(localFile.keepInSync());
+                 remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
+                 remoteFile.setModificationTimestampAtLastSyncForData(
+                         localFile.getModificationTimestampAtLastSyncForData()
+                 );
+                 remoteFile.setStoragePath(localFile.getStoragePath());
+                 // eTag will not be updated unless contents are synchronized 
+                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
+                 remoteFile.setEtag(localFile.getEtag());    
+                 if (remoteFile.isFolder()) {
+                     remoteFile.setFileLength(localFile.getFileLength()); 
+                         // TODO move operations about size of folders to FileContentProvider
+                 } else if (mRemoteFolderChanged && remoteFile.isImage() &&
+                         remoteFile.getModificationTimestamp() !=
+                                 localFile.getModificationTimestamp()) {
+                     remoteFile.setNeedsUpdateThumbnail(true);
+                     Log.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
+                 }
+                 remoteFile.setPublicLink(localFile.getPublicLink());
+                 remoteFile.setShareByLink(localFile.isShareByLink());
+             } else {
+                 // remote eTag will not be updated unless contents are synchronized 
+                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
+                 remoteFile.setEtag(""); 
+             }
+             /// check and fix, if needed, local storage path
+             checkAndFixForeignStoragePath(remoteFile);      // policy - local files are COPIED 
+                                                             // into the ownCloud local folder;
+             searchForLocalFileInDefaultPath(remoteFile);    // legacy   
+             /// prepare content synchronization for kept-in-sync files
+             if (remoteFile.keepInSync()) {
+                 SynchronizeFileOperation operation = new SynchronizeFileOperation(  localFile,        
+                                                                                     remoteFile, 
+                                                                                     mAccount, 
+                                                                                     true, 
+                                                                                     mContext
+                                                                                     );
+                 
+                 filesToSyncContents.add(operation);
+             }
+             
+             updatedFiles.add(remoteFile);
+         }
+         // save updated contents in local database
+         mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+         // request for the synchronization of file contents AFTER saving current remote properties
+         startContentSynchronizations(filesToSyncContents, client);
+         mChildren = updatedFiles;
+     }
+     /**
+      * Performs a list of synchronization operations, determining if a download or upload is needed
+      * or if exists conflict due to changes both in local and remote contents of the each file.
+      * 
+      * If download or upload is needed, request the operation to the corresponding service and goes 
+      * on.
+      * 
+      * @param filesToSyncContents       Synchronization operations to execute.
+      * @param client                    Interface to the remote ownCloud server.
+      */
+     private void startContentSynchronizations(
+             List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
+         ) {
+         RemoteOperationResult contentsResult = null;
+         for (SynchronizeFileOperation op: filesToSyncContents) {
+             contentsResult = op.execute(mStorageManager, mContext);   // async
+             if (!contentsResult.isSuccess()) {
+                 if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
+                     mConflictsFound++;
+                 } else {
+                     mFailsInFavouritesFound++;
+                     if (contentsResult.getException() != null) {
+                         Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                                 +  contentsResult.getLogMessage(), contentsResult.getException());
+                     } else {
+                         Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                                 + contentsResult.getLogMessage());
+                     }
+                 }
+             }   // won't let these fails break the synchronization process
+         }
+     }
+     public boolean isMultiStatus(int status) {
+         return (status == HttpStatus.SC_MULTI_STATUS); 
+     }
+     /**
+      * 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.
+      */
+     private OCFile fillOCFile(RemoteFile remote) {
+         OCFile file = new OCFile(remote.getRemotePath());
+         file.setCreationTimestamp(remote.getCreationTimestamp());
+         file.setFileLength(remote.getLength());
+         file.setMimetype(remote.getMimeType());
+         file.setModificationTimestamp(remote.getModifiedTimestamp());
+         file.setEtag(remote.getEtag());
+         file.setPermissions(remote.getPermissions());
+         file.setRemoteId(remote.getRemoteId());
+         return file;
+     }
+     
+     /**
+      * Checks the storage path of the OCFile received as parameter. 
+      * If it's out of the local ownCloud folder, tries to copy the file inside it. 
+      * 
+      * If the copy fails, the link to the local file is nullified. The account of forgotten 
+      * files is kept in {@link #mForgottenLocalFiles}
+      *) 
+      * @param file      File to check and fix.
+      */
+     private void checkAndFixForeignStoragePath(OCFile file) {
+         String storagePath = file.getStoragePath();
+         String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+         if (storagePath != null && !storagePath.equals(expectedPath)) {
+             /// fix storagePaths out of the local ownCloud folder
+             File originalFile = new File(storagePath);
+             if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+                 mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+                 file.setStoragePath(null);
+                     
+             } else {
+                 InputStream in = null;
+                 OutputStream out = null;
+                 try {
+                     File expectedFile = new File(expectedPath);
+                     File expectedParent = expectedFile.getParentFile();
+                     expectedParent.mkdirs();
+                     if (!expectedParent.isDirectory()) {
+                         throw new IOException(
+                                 "Unexpected error: parent directory could not be created"
+                         );
+                     }
+                     expectedFile.createNewFile();
+                     if (!expectedFile.isFile()) {
+                         throw new IOException("Unexpected error: target file could not be created");
+                     }                    
+                     in = new FileInputStream(originalFile);
+                     out = new FileOutputStream(expectedFile);
+                     byte[] buf = new byte[1024];
+                     int len;
+                     while ((len = in.read(buf)) > 0){
+                         out.write(buf, 0, len);
+                     }
+                     file.setStoragePath(expectedPath);
+                     
+                 } catch (Exception e) {
+                     Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
+                     mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+                     file.setStoragePath(null);
+                     
+                 } finally {
+                     try {
+                         if (in != null) in.close();
+                     } catch (Exception e) {
+                         Log_OC.d(TAG, "Weird exception while closing input stream for " 
+                                 + storagePath + " (ignoring)", e);
+                     }
+                     try {
+                         if (out != null) out.close();
+                     } catch (Exception e) {
+                         Log_OC.d(TAG, "Weird exception while closing output stream for " 
+                                 + expectedPath + " (ignoring)", e);
+                     }
+                 }
+             }
+         }
+     }
+     
+     
+     private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
+         RemoteOperationResult result = null;
+         
+         // remote request 
+         GetRemoteSharesForFileOperation operation = 
+                 new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
+         result = operation.execute(client);
+         
+         if (result.isSuccess()) {
+             // update local database
+             ArrayList<OCShare> shares = new ArrayList<OCShare>();
+             for(Object obj: result.getData()) {
+                 shares.add((OCShare) obj);
+             }
+             mStorageManager.saveSharesInFolder(shares, mLocalFolder);
+         }
+         return 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.
+      *  
+      * @param file      File to associate a possible 'lost' local file.
+      */
+     private void searchForLocalFileInDefaultPath(OCFile file) {
+         if (file.getStoragePath() == null && !file.isFolder()) {
+             File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+             if (f.exists()) {
+                 file.setStoragePath(f.getAbsolutePath());
+                 file.setLastSyncDateForData(f.lastModified());
+             }
+         }
+     }
+     
+     /**
+      * Sends a message to any application component interested in the progress 
+      * of the synchronization.
+      * 
+      * @param event
+      * @param dirRemotePath     Remote path of a folder that was just synchronized 
+      *                          (with or without success)
+      * @param result
+      */
+     private void sendLocalBroadcast(
+             String event, String dirRemotePath, RemoteOperationResult result
+         ) {
+         Log_OC.d(TAG, "Send broadcast " + event);
+         Intent intent = new Intent(event);
+         intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name);
+         if (dirRemotePath != null) {
+             intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
+         }
+         intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
+         mContext.sendStickyBroadcast(intent);
+         //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+     }
+     public boolean getRemoteFolderChanged() {
+         return mRemoteFolderChanged;
+     }
+ }
@@@ -35,12 -24,13 +38,14 @@@ import android.accounts.Account
  import android.content.Context;
  import android.content.Intent;
  import android.util.Log;
- //import android.support.v4.content.LocalBroadcastManager;
  
+ import com.owncloud.android.MainApp;
  import com.owncloud.android.datamodel.FileDataStorageManager;
  import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.files.services.FileDownloader;
  import com.owncloud.android.lib.common.OwnCloudClient;
+ import com.owncloud.android.lib.common.operations.OperationCancelledException;
 +import com.owncloud.android.lib.common.operations.RemoteOperation;
  import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  import com.owncloud.android.lib.common.utils.Log_OC;
@@@ -48,11 -37,19 +53,22 @@@ import com.owncloud.android.lib.resourc
  import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
  import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
  import com.owncloud.android.lib.resources.files.RemoteFile;
 +import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
 +import com.owncloud.android.lib.resources.shares.OCShare;
 +import com.owncloud.android.syncadapter.FileSyncAdapter;
+ import com.owncloud.android.operations.common.SyncOperation;
+ import com.owncloud.android.services.OperationsService;
  import com.owncloud.android.utils.FileStorageUtils;
  
+ import java.io.File;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Vector;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ //import android.support.v4.content.LocalBroadcastManager;
  
  
  /**
@@@ -330,15 -283,21 +302,21 @@@ public class SynchronizeFolderOperatio
          OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
          remoteFolder.setParentId(mLocalFolder.getParentId());
          remoteFolder.setFileId(mLocalFolder.getFileId());
-         
-         Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() 
+         Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
                  + " changed - starting update of local data ");
-         
          List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
-         List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+         mFilesForDirectDownload.clear();
+         mFilesToSyncContentsWithoutUpload.clear();
+         mFavouriteFilesToSyncContents.clear();
+         if (mCancellationRequested.get()) {
+             throw new OperationCancelledException();
+         }
  
          // get current data about local contents of the folder to synchronize
-         List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder, false);
 -        List<OCFile> localFiles = storageManager.getFolderContent(mLocalFolder);
++        List<OCFile> localFiles = storageManager.getFolderContent(mLocalFolder, true);
          Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
          for (OCFile file : localFiles) {
              localFilesMap.put(file.getRemotePath(), file);
          }
  
          // save updated contents in local database
-         mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+         storageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+     }
+     
+     
+     private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
 -        List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder);
++        // TODO TOBI ist das richtig?
++        List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, true);
+         for (OCFile child : children) {
+             /// classify file to sync/download contents later
+             if (child.isFolder()) {
+                 /// to download children files recursively
+                 synchronized(mCancellationRequested) {
+                     if (mCancellationRequested.get()) {
+                         throw new OperationCancelledException();
+                     }
+                     startSyncFolderOperation(child.getRemotePath());
+                 }
  
-         // request for the synchronization of file contents AFTER saving current remote properties
-         startContentSynchronizations(filesToSyncContents, client);
+             } else {
+                 /// prepare limited synchronization for regular files
+                 if (!child.isDown()) {
+                     mFilesForDirectDownload.add(child);
+                 }
+             }
+         }
+     }
  
-         mChildren = updatedFiles;
+     private void syncContents(OwnCloudClient client) throws OperationCancelledException {
+         startDirectDownloads();
+         startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client);
+         startContentSynchronizations(mFavouriteFilesToSyncContents, client);
+     }
+     
+     private void startDirectDownloads() throws OperationCancelledException {
+         for (OCFile file : mFilesForDirectDownload) {
+             synchronized(mCancellationRequested) {
+                 if (mCancellationRequested.get()) {
+                     throw new OperationCancelledException();
+                 }
+                 Intent i = new Intent(mContext, FileDownloader.class);
+                 i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+                 i.putExtra(FileDownloader.EXTRA_FILE, file);
+                 mContext.startService(i);
+             }
+         }
      }
  
      /**
@@@ -24,11 -27,8 +27,9 @@@ import java.io.File
  import android.accounts.Account;
  import android.accounts.AccountManager;
  import android.accounts.AuthenticatorException;
 +import android.accounts.OperationCanceledException;
  import android.annotation.TargetApi;
  import android.app.AlertDialog;
- import android.app.Dialog;
- import android.app.ProgressDialog;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.ContentResolver;
@@@ -46,13 -44,9 +46,10 @@@ import android.database.Cursor
  import android.net.Uri;
  import android.os.Build;
  import android.os.Bundle;
- import android.os.Environment;
  import android.os.IBinder;
  import android.preference.PreferenceManager;
- import android.provider.DocumentsContract;
- import android.provider.MediaStore;
  import android.provider.OpenableColumns;
 +import android.support.v4.app.ActionBarDrawerToggle;
  import android.support.v4.app.Fragment;
  import android.support.v4.app.FragmentManager;
  import android.support.v4.app.FragmentTransaction;
@@@ -104,8 -90,7 +101,9 @@@ import com.owncloud.android.operations.
  import com.owncloud.android.operations.UnshareLinkOperation;
  import com.owncloud.android.services.observer.FileObserverService;
  import com.owncloud.android.syncadapter.FileSyncAdapter;
 +import com.owncloud.android.ui.adapter.FileListListAdapter;
 +import com.owncloud.android.ui.adapter.NavigationDrawerListAdapter;
+ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
  import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
  import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
  import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener;
@@@ -166,30 -146,22 +159,26 @@@ OnSslUntrustedCertListener, OnEnforceab
      
      private boolean mSyncInProgress = false;
  
-     private String DIALOG_UNTRUSTED_CERT;
-     
+     private static String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT";
+     private static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER";
+     private static String DIALOG_UPLOAD_SOURCE = "DIALOG_UPLOAD_SOURCE";
+     private static String DIALOG_CERT_NOT_SAVED = "DIALOG_CERT_NOT_SAVED";
      private OCFile mWaitingToSend;
      
-     
 +    private DrawerLayout mDrawerLayout;
 +    private ActionBarDrawerToggle mDrawerToggle;
 +    private boolean showAccounts = false;
 +    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
-         Log_OC.d(TAG, "onCreate() start");
+         Log_OC.v(TAG, "onCreate() start");
          requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
 -
 +        
          super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid
  
-         // PIN CODE request ;  best location is to decide, let's try this first
-         if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) {
-             requestPinCode();
-         } else if (getIntent().getAction() == null && savedInstanceState == null) {
-             requestPinCode();
-         }
-         /// grant that FileObserverService is watching favourite files
+         /// grant that FileObserverService is watching favorite files
          if (savedInstanceState == null) {
              Intent initObserversIntent = FileObserverService.makeInitIntent(this);
              startService(initObserversIntent);
  
          // Action bar setup
          mDirectories = new CustomArrayAdapter<String>(this, R.layout.sherlock_spinner_dropdown_item);
 +        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
          getSupportActionBar().setHomeButtonEnabled(true);       // mandatory since Android ICS, according to the official documentation
 +        getSupportActionBar().setDisplayShowCustomEnabled(true); // CRUCIAL - for displaying your custom actionbar
 +        getSupportActionBar().setDisplayShowTitleEnabled(true);
          setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/);    // always AFTER setContentView(...) ; to work around bug in its implementation
          
 +        mDrawerToggle.syncState();
 +        
          setBackgroundText();
  
-         Log_OC.d(TAG, "onCreate() end");
+         Log_OC.v(TAG, "onCreate() end");
      }
      
      @Override
      protected void onStart() {
+         Log_OC.v(TAG, "onStart() start");
          super.onStart();
          getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId());
+         Log_OC.v(TAG, "onStart() end");
      }
 +    
 +    @Override
 +    protected void onPostCreate(Bundle savedInstanceState) {
 +        super.onPostCreate(savedInstanceState);
 +        // Sync the toggle state after onRestoreInstanceState has occurred.
 +        mDrawerToggle.syncState();
 +    }
 +    
 +    @Override
 +    public void onConfigurationChanged(Configuration newConfig) {
 +        super.onConfigurationChanged(newConfig);
 +        mDrawerToggle.onConfigurationChanged(newConfig);
 +    }
  
      @Override
      protected void onDestroy() {
          boolean retval = true;
          switch (item.getItemId()) {
          case R.id.action_create_dir: {
-             CreateFolderDialogFragment dialog = 
-                     CreateFolderDialogFragment.newInstance(getCurrentDir());
-             dialog.show(getSupportFragmentManager(), "createdirdialog");
+             CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(getCurrentDir());
+             dialog.show(getSupportFragmentManager(), DIALOG_CREATE_FOLDER);
              break;
          }
 -        case R.id.action_sync_account: {
 -            startSynchronization();
 -            break;
 -        }
          case R.id.action_upload: {
-             showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE);
+             UploadSourceDialogFragment dialog = UploadSourceDialogFragment.newInstance(getAccount());
+             dialog.show(getSupportFragmentManager(), DIALOG_UPLOAD_SOURCE);
              break;
          }
 -        case R.id.action_settings: {
 -            Intent settingsIntent = new Intent(this, Preferences.class);
 -            startActivity(settingsIntent);
 -            break;
 -        }
 -        case R.id.action_logger: {
 -            Intent loggerIntent = new Intent(getApplicationContext(),LogHistoryActivity.class);
 -            startActivity(loggerIntent);
 -            break;
 -        }
          case android.R.id.home: {
 -            FileFragment second = getSecondFragment();
 -            OCFile currentDir = getCurrentDir();
 -            if((currentDir != null && currentDir.getParentId() != 0) || 
 -                    (second != null && second.getFile() != null)) {                
 -                onBackPressed(); 
 -                
 +            if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
 +                mDrawerLayout.closeDrawer(GravityCompat.START);
 +            } else {
 +                mDrawerLayout.openDrawer(GravityCompat.START);
              }
 +            // TODO add hamburger to left of android.R.id.home
              break;
          }
          case R.id.action_sort: {
              // only list of files - set for browsing through folders
              OCFile currentDir = getCurrentDir();
              boolean noRoot = (currentDir != null && currentDir.getParentId() != 0);
 -            actionBar.setDisplayHomeAsUpEnabled(noRoot);
 -            actionBar.setDisplayShowTitleEnabled(!noRoot); 
 +//            actionBar.setDisplayHomeAsUpEnabled(noRoot);
 +//            actionBar.setDisplayShowTitleEnabled(!noRoot); 
 +            actionBar.setDisplayHomeAsUpEnabled(true);
 +            actionBar.setDisplayShowTitleEnabled(true);
              if (!noRoot) {
                  actionBar.setTitle(getString(R.string.default_display_name_for_root_folder));
+                 View actionBarTitleView = getWindow().getDecorView().findViewById(actionBarTitleId);
+                 if (actionBarTitleView != null) {    // it's null in Android 2.x
+                     actionBarTitleView.setContentDescription(getString(R.string.default_display_name_for_root_folder));
+                 }
              }
              actionBar.setNavigationMode(!noRoot ? ActionBar.NAVIGATION_MODE_STANDARD : ActionBar.NAVIGATION_MODE_LIST);
              actionBar.setListNavigationCallbacks(mDirectories, this);   // assuming mDirectories is updated
       */
      public void showUntrustedCertDialog(RemoteOperationResult result) {
          // Show a dialog with the certificate info
-         SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError((CertificateCombinedException)result.getException());
+         SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError(
 -                (CertificateCombinedException)result.getException());
++                (CertificateCombinedException) result.getException());
          FragmentManager fm = getSupportFragmentManager();
          FragmentTransaction ft = fm.beginTransaction();
          dialog.show(ft, DIALOG_UNTRUSTED_CERT);
      private void sortByName(boolean ascending){
          getListOfFilesFragment().sortByName(ascending);
      }
 +    
 +    public void restart(){
 +        Intent i = new Intent(this, FileDisplayActivity.class);
 +        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 +        startActivity(i);
 +    }
 +
 +    public void closeDrawer() {
 +        mDrawerLayout.closeDrawers();
 +    }
  }
@@@ -60,11 -70,18 +70,25 @@@ import com.actionbarsherlock.view.MenuI
  import com.owncloud.android.MainApp;
  import com.owncloud.android.R;
  import com.owncloud.android.authentication.AccountAuthenticator;
 +import com.owncloud.android.datamodel.FileDataStorageManager;
 +import com.owncloud.android.datamodel.OCFile;
 +import com.owncloud.android.files.services.FileUploader;
 +import com.owncloud.android.lib.common.utils.Log_OC;
++import com.owncloud.android.MainApp;
++import com.owncloud.android.R;
++import com.owncloud.android.authentication.AccountAuthenticator;
+ import com.owncloud.android.datamodel.OCFile;
+ import com.owncloud.android.files.services.FileUploader;
+ import com.owncloud.android.lib.common.operations.RemoteOperation;
+ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+ import com.owncloud.android.lib.common.utils.Log_OC;
+ import com.owncloud.android.operations.CreateFolderOperation;
+ import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
+ import com.owncloud.android.ui.dialog.LoadingDialog;
+ import com.owncloud.android.utils.CopyTmpFileAsyncTask;
  import com.owncloud.android.utils.DisplayUtils;
+ import com.owncloud.android.utils.ErrorMessageAdapter;
  
  /**
   * This can be used to upload things to an ownCloud instance.
@@@ -240,7 -325,7 +332,7 @@@ public class Uploader extends FileActiv
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
          // click on folder in the list
          Log_OC.d(TAG, "on item click");
-         Vector<OCFile> tmpfiles = mStorageManager.getFolderContent(mFile, false);
 -        Vector<OCFile> tmpfiles = getStorageManager().getFolderContent(mFile);
++        Vector<OCFile> tmpfiles = getStorageManager().getFolderContent(mFile, false);
          if (tmpfiles.size() <= 0) return;
          // filter on dirtype
          Vector<OCFile> files = new Vector<OCFile>();
          actionBar.setHomeButtonEnabled(notRoot);
  
          String full_path = generatePath(mParents);
-         
          Log_OC.d(TAG, "Populating view with content of : " + full_path);
  
-         mFile = mStorageManager.getFileByPath(full_path);
+         mFile = getStorageManager().getFileByPath(full_path);
          if (mFile != null) {
-             Vector<OCFile> files = mStorageManager.getFolderContent(mFile, false);
 -            Vector<OCFile> files = getStorageManager().getFolderContent(mFile);
++            Vector<OCFile> files = getStorageManager().getFolderContent(mFile, false);
              List<HashMap<String, Object>> data = new LinkedList<HashMap<String,Object>>();
              for (OCFile f : files) {
                  HashMap<String, Object> h = new HashMap<String, Object>();
@@@ -351,7 -414,10 +414,10 @@@ public class FileListListAdapter extend
              mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);\r
          }\r
          if (mStorageManager != null) {\r
-             mFiles = mStorageManager.getFolderContent(mFile, onlyOnDevice);\r
 -            mFiles = mStorageManager.getFolderContent(mFile);\r
++            mFiles = mStorageManager.getFolderContent(mFile, false);\r
+             mFilesOrig.clear();\r
+             mFilesOrig.addAll(mFiles);\r
+             \r
              if (mJustFolders) {\r
                  mFiles = getFolders(mFiles);\r
              }\r
@@@ -75,11 -74,10 +77,11 @@@ public class OCFileListFragment extend
     
      private OCFile mFile = null;
      private FileListListAdapter mAdapter;
-     private View mFooterView;
+     private boolean mJustFolders;
      
      private OCFile mTargetFile;
 -
 +    
 +   
      
      /**
       * {@inheritDoc}
                  directory = storageManager.getFileById(directory.getParentId());
              }
  
 -            mAdapter.swapDirectory(directory, storageManager);
 +            mAdapter.swapDirectory(directory, storageManager, onlyOnDevice);
              if (mFile == null || !mFile.equals(directory)) {
-                 mList.setSelectionFromTop(0, 0);
+                 mCurrentListView.setSelection(0);
              }
              mFile = directory;
-             
-             // Update Footer
-             TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText);
-             Log_OC.d("footer", String.valueOf(System.currentTimeMillis()));
-             footerText.setText(generateFooterText(directory, onlyOnDevice));
-             Log_OC.d("footer", String.valueOf(System.currentTimeMillis()));
+             updateLayout();
          }
      }
-     
-     private String generateFooterText(OCFile directory, boolean onlyOnDevice) {
-         Integer files = 0;
-         Integer folders = 0;
  
-         FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
-         Vector<OCFile> mFiles = storageManager.getFolderContent(mFile, onlyOnDevice);
+     private void updateLayout() {
+         if (!mJustFolders) {
+             int filesCount = 0, foldersCount = 0, imagesCount = 0;
+             int count = mAdapter.getCount();
+             OCFile file;
+             for (int i=0; i < count ; i++) {
+                 file = (OCFile) mAdapter.getItem(i);
+                 if (file.isFolder()) {
+                     foldersCount++;
+                 } else {
+                     filesCount++;
+                     if (file.isImage()){
+                         imagesCount++;
+                     }
+                 }
+             }
+             // set footer text
+             setFooterText(generateFooterText(filesCount, foldersCount));
  
-         for (OCFile ocFile : mFiles) {
-             if (ocFile.isFolder()) {
-                 folders++;
+             // decide grid vs list view
+             if (((double)imagesCount / (double)filesCount) >= THUMBNAIL_THRESHOLD) {
+                 switchToGridView();
              } else {
-                 files++;
+                 switchToListView();
              }
          }
+     }
  
+     private String generateFooterText(int filesCount, int foldersCount) {
          String output = "";
-        
-         if (files > 0){
-             if (files == 1) {
-                 output = output + files.toString() + " " + getResources().getString(R.string.file_list_file);
+         if (filesCount > 0){
+             if (filesCount == 1) {
+                 output = output + filesCount + " " + getResources().getString(R.string.file_list_file);
              } else {
-                 output = output + files.toString() + " " + getResources().getString(R.string.file_list_files);
+                 output = output + filesCount + " " + getResources().getString(R.string.file_list_files);
              }
          }
-         if (folders > 0 && files > 0){
+         if (foldersCount > 0 && filesCount > 0){
              output = output + ", ";
          }
-         if (folders == 1) {
-             output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder);
-         } else if (folders > 1) {
-             output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders);
+         if (foldersCount == 1) {
+             output = output + foldersCount + " " + getResources().getString(R.string.file_list_folder);
+         } else if (foldersCount > 1) {
+             output = output + foldersCount + " " + getResources().getString(R.string.file_list_folders);
          }
-         
          return output;
      }
-     
      public void sortByName(boolean descending) {
-         mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending);
+         mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
      }
  
      public void sortByDate(boolean descending) {
      }
  
      public void sortBySize(boolean descending) {
-         mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending);
+         mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
      }  
 -
 +    
 +   
 +    
  }
@@@ -72,7 -77,10 +77,10 @@@ public class PreviewImagePagerAdapter e
  
          mAccount = account;
          mStorageManager = storageManager;
-         mImageFiles = mStorageManager.getFolderImages(parentFolder, onlyOnDevice); 
 -        mImageFiles = mStorageManager.getFolderImages(parentFolder); 
++        mImageFiles = mStorageManager.getFolderImages(parentFolder, false);
+         
+         mImageFiles = FileStorageUtils.sortFolder(mImageFiles);
+         
          mObsoleteFragments = new HashSet<Object>();
          mObsoletePositions = new HashSet<Integer>();
          mDownloadErrors = new HashSet<Integer>();
@@@ -171,43 -178,16 +178,56 @@@ public class BitmapUtils 
      }
  
      /**
 +     * Converts an HSL color value to RGB. Conversion formula
 +     * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 +     * Assumes h, s, and l are contained in the set [0, 1] and
 +     * returns r, g, and b in the set [0, 255].
 +     * from: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
 +     *
 +     * @param   integer  h       The hue
 +     * @param   Integer  s       The saturation
 +     * @param   Integer  l       The lightness
 +     * @return  Array           The RGB representation
 +     */
 +    public static int[] hslToRgb(Double h, Double s, Double l){
 +        Double r, g, b;
 +
 +        if(s == 0){
 +            r = g = b = l; // achromatic
 +        } else {
 +            Double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
 +            Double p = 2 * l - q;
 +            r = hue2rgb(p, q, h + 1/3) * 255;
 +            g = hue2rgb(p, q, h) * 255;
 +            b = hue2rgb(p, q, h - 1/3) * 255;
 +        }
 +
 +
 +        int[] array = {r.intValue(), g.intValue(), b.intValue()};
 +        return array;
 +    }
 +
 +    private static Double hue2rgb(Double p, Double q, Double t){
 +        if(t < 0) t += 1;
 +        if(t > 1) t -= 1;
 +        if(t < 1/6) return p + (q - p) * 6 * t;
 +        if(t < 1/2) return q;
 +        if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
 +        return p;
 +    }
 +    
++
++    /**
+      * Checks if file passed is an image
+      * @param file
+      * @return true/false
+      */
+     public static boolean isImage(File file) {
+         Uri selectedUri = Uri.fromFile(file);
+         String fileExtension = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString().toLowerCase());
+         String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
+         return (mimeType != null && mimeType.startsWith("image/"));
+     }
      
  }