Browse Source

Merge remote-tracking branch 'owncloud/multiSelect' into multiSelectOwncloud

# Conflicts:
#	res/drawable-hdpi/ic_checkbox_marked.png
#	res/drawable-mdpi/ic_checkbox_marked.png
#	res/drawable-xhdpi/ic_checkbox_marked.png
#	res/drawable-xxhdpi/ic_checkbox_marked.png
#	res/layout/grid_image.xml
#	res/values/strings.xml
#	src/com/owncloud/android/notifications/NotificationBuilderWithProgressBar.java
#	src/com/owncloud/android/ui/activity/FileDisplayActivity.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
tobiasKaminsky 8 years ago
parent
commit
1183e51c19

+ 10 - 0
res/layout/grid_image.xml

@@ -64,6 +64,16 @@
             android:layout_marginBottom="4dp"
             android:layout_marginRight="4dp"
             android:src="@drawable/ic_available_offline" />
+
+        <ImageView
+            android:id="@+id/custom_checkbox"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|top"
+            android:layout_marginLeft="4dp"
+            android:layout_marginRight="4dp"
+            android:gravity=""
+            android:src="@android:drawable/checkbox_off_background" />
     </FrameLayout>
 
 </LinearLayout>

+ 9 - 1
res/layout/grid_item.xml

@@ -65,7 +65,15 @@
             android:layout_marginRight="2dp"
             android:src="@drawable/ic_available_offline" />
 
-
+        <ImageView
+            android:id="@+id/custom_checkbox"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|top"
+            android:layout_marginLeft="4dp"
+            android:layout_marginRight="4dp"
+            android:src="@android:drawable/checkbox_off_background"
+            android:elevation="30dp" />
 
     </FrameLayout>
 

+ 26 - 17
res/layout/list_item.xml

@@ -121,26 +121,35 @@
 
         </LinearLayout>
 
-        <ImageView
-            android:id="@+id/sharedIcon"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:layout_marginTop="4dp"
-            android:layout_marginLeft="4dp"
-            android:layout_marginBottom="4dp"
-            android:layout_marginRight="4dp"
-            android:src="@drawable/shared_via_link" />
-
-        <ImageView
-            android:id="@+id/custom_checkbox"
+        <RelativeLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
-            android:layout_marginLeft="4dp"
-            android:layout_marginRight="@dimen/standard_margin"
-            android:gravity=""
-            android:src="@drawable/ic_checkbox_blank_outline" />
+            android:paddingRight="@dimen/standard_padding">
+
+            <ImageView
+                android:id="@+id/sharedIcon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginTop="4dp"
+                android:layout_marginLeft="4dp"
+                android:layout_marginBottom="4dp"
+                android:layout_marginRight="4dp"
+                android:src="@drawable/shared_via_link" />
+
+            <ImageView
+                android:id="@+id/custom_checkbox"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginLeft="4dp"
+
+                android:src="@drawable/ic_checkbox_blank_outline"
+                android:layout_toRightOf="@id/sharedIcon"/>
+        </RelativeLayout>
+
+
     </LinearLayout>
 
     <View

+ 6 - 6
res/menu/file_actions_menu.xml

@@ -22,13 +22,13 @@
     <item
         android:id="@+id/action_share_file"
         android:title="@string/action_share"
-        android:icon="@android:drawable/ic_menu_share"
+        android:icon="@drawable/ic_share"
         android:orderInCategory="1" />
 
     <item
         android:id="@+id/action_open_file_with"
         android:title="@string/actionbar_open_with"
-        android:icon="@android:drawable/ic_menu_set_as"
+        android:icon="@drawable/ic_export"
         android:orderInCategory="1" />
     <item
         android:id="@+id/action_download_file"
@@ -48,22 +48,22 @@
     <item
         android:id="@+id/action_rename_file"
         android:title="@string/common_rename"
-        android:icon="@android:drawable/ic_menu_edit"
+        android:icon="@drawable/ic_pencil"
         android:orderInCategory="1" />
     <item
         android:id="@+id/action_move"
         android:title="@string/actionbar_move"
-        android:icon="@android:drawable/ic_menu_set_as"
+        android:icon="@drawable/ic_export"
         android:orderInCategory="1" />
     <item
         android:id="@+id/action_copy"
         android:title="@android:string/copy"
-        android:icon="@android:drawable/ic_menu_set_as"
+        android:icon="@drawable/ic_copy"
         android:orderInCategory="1" />
     <item
         android:id="@+id/action_remove_file"
         android:title="@string/common_remove"
-        android:icon="@android:drawable/ic_menu_delete"
+        android:icon="@drawable/ic_delete"
         android:orderInCategory="1" />
     <item
         android:id="@+id/action_send_file"

+ 6 - 1
res/values/setup.xml

@@ -46,7 +46,12 @@
 
     <!-- Button -->
     <color name="button_text_color">#000000</color>
-    
+
+    <!-- Multiselect backgrounds -->
+    <color name="action_mode_background">#757575</color>
+    <color name="action_mode_status_bar_background">#616161</color>
+    <color name="selected_item_background">#ECECEC</color>
+
     <!-- Multiaccount support -->
     <bool name="multiaccount_support">true</bool>
     

+ 4 - 1
res/values/strings.xml

@@ -234,7 +234,7 @@
     <string name="unfavorite">Unset as available offline</string>
     <string name="common_rename">Rename</string>
     <string name="common_remove">Remove</string>
-    <string name="confirmation_remove_alert">"Do you really want to remove %1$s?"</string>
+    <string name="confirmation_remove_file_alert">"Do you really want to remove %1$s?"</string>
     <string name="confirmation_remove_folder_alert">"Do you really want to remove %1$s and its contents?"</string>
     <string name="confirmation_remove_local">Local only</string>
     <string name="remove_success_msg">"Removal succeeded"</string>
@@ -455,5 +455,8 @@
 
     <string name="permission_storage_access">Additional permissions required to upload &amp; download files.</string>
     <string name="local_file_not_found_toast">The file was not found in the local file system</string>
+    <string name="confirmation_remove_files_alert">Do you really want to remove the selected items?</string>
+    <string name="confirmation_remove_folders_alert">Do you really want to remove the selected items and their contents?</string>
+    <string name="confirmation_remove_files">selected items</string>
 
 </resources>

+ 2 - 0
res/values/styles.xml

@@ -24,6 +24,8 @@
 	<style name="Theme.ownCloud" parent="Theme.AppCompat.Light.DarkActionBar">
     	<item name="android:actionBarStyle">@style/Theme.ownCloud.Widget.ActionBar</item>
     	<item name="actionBarStyle">@style/Theme.ownCloud.Widget.ActionBar</item>
+		<item name="actionModeBackground">@color/action_mode_background</item>
+		<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
 		<item name="colorPrimary">@color/primary</item>
 		<item name="colorPrimaryDark">@color/primary_dark</item>
 		<item name="colorAccent">@color/color_accent</item>

+ 80 - 20
src/com/owncloud/android/files/FileMenuFilter.java

@@ -35,6 +35,7 @@ import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -43,7 +44,9 @@ import java.util.List;
  */
 public class FileMenuFilter {
 
-    private OCFile mFile;
+    private static final int SINGLE_SELECT_ITEMS = 1;
+
+    private List<OCFile> mFiles;
     private ComponentsGetter mComponentsGetter;
     private Account mAccount;
     private Context mContext;
@@ -51,20 +54,33 @@ public class FileMenuFilter {
     /**
      * Constructor
      *
-     * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
+     * @param targetFiles       List of {@link OCFile} file targets of the action to filter in the {@link Menu}.
      * @param account           ownCloud {@link Account} holding targetFile.
      * @param cg                Accessor to app components, needed to access the
      *                          {@link FileUploader} and {@link FileDownloader} services
      * @param context           Android {@link Context}, needed to access build setup resources.
      */
-    public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg,
+    public FileMenuFilter(List<OCFile> targetFiles, Account account, ComponentsGetter cg,
                           Context context) {
-        mFile = targetFile;
+        mFiles = targetFiles;
         mAccount = account;
         mComponentsGetter = cg;
         mContext = context;
     }
 
+    /**
+     * Constructor
+     *
+     * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
+     * @param account           ownCloud {@link Account} holding targetFile.
+     * @param cg                Accessor to app components, needed to access the
+     *                          {@link FileUploader} and {@link FileDownloader} services
+     * @param context           Android {@link Context}, needed to access build setup resources.
+     */
+    public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg,
+                          Context context) {
+        this(Arrays.asList(new OCFile[]{targetFile}), account, cg, context);
+    }
 
     /**
      * Filters out the file actions available in the passed {@link Menu} taken into account
@@ -107,24 +123,24 @@ public class FileMenuFilter {
      */
     private void filter(List<Integer> toShow, List <Integer> toHide) {
         boolean synchronizing = false;
-        if (mComponentsGetter != null && mFile != null && mAccount != null) {
+        if (mComponentsGetter != null && !mFiles.isEmpty() && mAccount != null) {
             OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder();
             FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder();
             FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
             synchronizing = (
                 // comparing local and remote
-                (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath())) ||
+                (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFiles)) ||
                 // downloading
-                (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) ||
+                (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFiles)) ||
                 // uploading
-                (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))
+                (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFiles))
             );
         }
 
         /// decision is taken for each possible action on a file in the menu
 
         // DOWNLOAD 
-        if (mFile == null || mFile.isDown() || mFile.isFolder() || synchronizing) {
+        if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
             toHide.add(R.id.action_download_file);
 
         } else {
@@ -132,7 +148,7 @@ public class FileMenuFilter {
         }
 
         // RENAME
-        if (mFile == null || synchronizing) {
+        if (!isSingleSelect() || synchronizing) {
             toHide.add(R.id.action_rename_file);
 
         } else {
@@ -140,7 +156,7 @@ public class FileMenuFilter {
         }
 
         // MOVE & COPY
-        if (mFile == null || synchronizing) {
+        if (mFiles.isEmpty() || synchronizing) {
             toHide.add(R.id.action_move);
             toHide.add(R.id.action_copy);
         } else {
@@ -149,7 +165,7 @@ public class FileMenuFilter {
         }
 
         // REMOVE
-        if (mFile == null || synchronizing) {
+        if (mFiles.isEmpty() || synchronizing) {
             toHide.add(R.id.action_remove_file);
 
         } else {
@@ -157,7 +173,7 @@ public class FileMenuFilter {
         }
 
         // OPEN WITH (different to preview!)
-        if (mFile == null || mFile.isFolder() || !mFile.isDown() || synchronizing) {
+        if (!isFile() || !anyFileDown() || synchronizing) {
             toHide.add(R.id.action_open_file_with);
 
         } else {
@@ -165,7 +181,7 @@ public class FileMenuFilter {
         }
 
         // CANCEL SYNCHRONIZATION
-        if (mFile == null || !synchronizing) {
+        if (mFiles.isEmpty() || !synchronizing) {
             toHide.add(R.id.action_cancel_sync);
 
         } else {
@@ -173,7 +189,7 @@ public class FileMenuFilter {
         }
 
         // SYNC CONTENTS (BOTH FILE AND FOLDER)
-        if (mFile == null || (!mFile.isFolder() && !mFile.isDown()) || synchronizing) {
+        if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
             toHide.add(R.id.action_sync_file);
 
         } else {
@@ -191,14 +207,14 @@ public class FileMenuFilter {
                 (capability.getFilesSharingApiEnabled().isTrue() ||
                         capability.getFilesSharingApiEnabled().isUnknown()
                 );
-        if ((!shareViaLinkAllowed && !shareWithUsersAllowed) ||  mFile == null || !shareApiEnabled) {
+        if ((!shareViaLinkAllowed && !shareWithUsersAllowed) || !isSingleSelect() || !shareApiEnabled) {
             toHide.add(R.id.action_share_file);
         } else {
             toShow.add(R.id.action_share_file);
         }
 
         // SEE DETAILS
-        if (mFile == null || mFile.isFolder()) {
+        if (!isFile()) {
             toHide.add(R.id.action_see_details);
         } else {
             toShow.add(R.id.action_see_details);
@@ -207,21 +223,21 @@ public class FileMenuFilter {
         // SEND
         boolean sendAllowed = (mContext != null &&
                 mContext.getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on"));
-        if (mFile == null || !sendAllowed || mFile.isFolder() || synchronizing) {
+        if (!isFile() || !sendAllowed || synchronizing) {
             toHide.add(R.id.action_send_file);
         } else {
             toShow.add(R.id.action_send_file);
         }
 
         // FAVORITES
-        if (mFile == null || synchronizing || mFile.isFolder() || mFile.isFavorite()) {
+        if (!isFile() || synchronizing || allFavorites()) {
             toHide.add(R.id.action_favorite_file);
         } else {
             toShow.add(R.id.action_favorite_file);
         }
 
         // UNFAVORITES
-        if (mFile == null || synchronizing || mFile.isFolder() || !mFile.isFavorite()) {
+        if (!isFile() || synchronizing || allUnfavorites()) {
             toHide.add(R.id.action_unfavorite_file);
         } else {
             toShow.add(R.id.action_unfavorite_file);
@@ -229,4 +245,48 @@ public class FileMenuFilter {
 
     }
 
+    private boolean isSingleSelect() {
+        return mFiles.size() == SINGLE_SELECT_ITEMS;
+    }
+
+    private boolean isFile() {
+        return isSingleSelect() && !mFiles.get(0).isFolder();
+    }
+
+    private boolean containsFolder() {
+        for(OCFile file: mFiles) {
+            if(file.isFolder()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean anyFileDown() {
+        for(OCFile file: mFiles) {
+            if(file.isDown()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean allFavorites() {
+        for(OCFile file: mFiles) {
+            if(!file.isFavorite()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean allUnfavorites() {
+        for(OCFile file: mFiles) {
+            if(file.isFavorite()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
 }

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

@@ -53,8 +53,11 @@ import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.dialog.ShareLinkToDialog;
 import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
 
+import java.util.Collection;
 import java.util.List;
 
+import java.util.ArrayList;
+
 /**
  *
  */
@@ -426,6 +429,12 @@ public class FileOperationsHelper {
         }
     }
 
+    public void syncFiles(Collection<OCFile> files) {
+        for (OCFile file: files) {
+            syncFile(file);
+        }
+    }
+
     /**
      * Request the synchronization of a file or folder with the OC server, including its contents.
      *
@@ -452,6 +461,12 @@ public class FileOperationsHelper {
         }
     }
 
+    public void toggleFavorites(Collection<OCFile> files, boolean isFavorite){
+        for (OCFile file: files) {
+            toggleFavorite(file, isFavorite);
+        }
+    }
+
     public void toggleFavorite(OCFile file, boolean isFavorite) {
         file.setFavorite(isFavorite);
         mFileActivity.getStorageManager().saveFile(file);
@@ -551,8 +566,6 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
 
-        mFileActivity.showLoadingDialog(mFileActivity.getApplicationContext().
-                getString(R.string.wait_a_moment));
     }
 
     /**

+ 21 - 0
src/com/owncloud/android/files/services/FileDownloader.java

@@ -62,6 +62,7 @@ import java.io.File;
 import java.util.AbstractList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
@@ -301,6 +302,26 @@ public class FileDownloader extends Service
             return (mPendingDownloads.contains(account.name, file.getRemotePath()));
         }
 
+        /**
+         * Returns True when the file described by 'file' in the ownCloud account 'account'
+         * is downloading or waiting to download.
+         *
+         * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
+         * waiting to download.
+         *
+         * @param account ownCloud account where the remote file is stored.
+         * @param files    A list of files that could contains someone in the queue of downloads.
+         */
+        public boolean isDownloading(Account account, List<OCFile> files) {
+            if (account == null || files.isEmpty()) return false;
+            for(OCFile file: files) {
+                if(mPendingDownloads.contains(account.name, file.getRemotePath())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
 
         /**
          * Adds a listener interested in the progress of the download for a concrete file.

+ 21 - 0
src/com/owncloud/android/files/services/FileUploader.java

@@ -70,6 +70,7 @@ import com.owncloud.android.utils.ErrorMessageAdapter;
 import java.util.AbstractList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Vector;
 
@@ -734,6 +735,26 @@ public class FileUploader extends Service
             );
         }
 
+        /**
+         * Returns True when the file described by 'file' is being uploaded to
+         * the ownCloud account 'account' or waiting for it
+         *
+         * If 'file' is a directory, returns 'true' if some of its descendant files
+         * is uploading or waiting to upload.
+         *
+         * @param account   ownCloud account where the remote file will be stored.
+         * @param files      A list of files that could contains someone in the queue of pending uploads
+         */
+        public boolean isUploading(Account account, List<OCFile> files) {
+            if (account == null || files.isEmpty()) return false;
+            for(OCFile file: files) {
+                if(mPendingUploads.contains(account.name, file.getRemotePath())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
 
         /**
          * Adds a listener interested in the progress of the upload for a concrete file.

+ 24 - 3
src/com/owncloud/android/services/OperationsService.java

@@ -71,6 +71,7 @@ import com.owncloud.android.operations.common.SyncOperation;
 
 import java.io.IOException;
 import java.util.Iterator;
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ConcurrentMap;
@@ -379,11 +380,31 @@ public class OperationsService extends Service {
          * or waiting to download.
          * 
          * @param account       ownCloud account where the remote file is stored.
-         * @param remotePath    Path of the folder to check if something is synchronizing
+         * @param file          File to check if something is synchronizing
          *                      / downloading / uploading inside.
          */
-        public boolean isSynchronizing(Account account, String remotePath) {
-            return mSyncFolderHandler.isSynchronizing(account, remotePath);
+        public boolean isSynchronizing(Account account, OCFile file) {
+            return mSyncFolderHandler.isSynchronizing(account, file.getRemotePath());
+        }
+
+        /**
+         * Returns True when the file described by 'file' in the ownCloud account 'account' is
+         * downloading or waiting to download.
+         *
+         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading
+         * or waiting to download.
+         *
+         * @param account       ownCloud account where the remote file is stored.
+         * @param files         List of files to check if something is synchronizing
+         *                      / downloading / uploading inside.
+         */
+        public boolean isSynchronizing(Account account, List<OCFile> files) {
+            for(OCFile file: files) {
+                if(isSynchronizing(account, file)) {
+                    return true;
+                }
+            }
+            return false;
         }
 
     }

+ 10 - 5
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -912,11 +912,15 @@ public class FileActivity extends AppCompatActivity
         // grant that only one waiting dialog is shown
         dismissLoadingDialog();
         // Construct dialog
-        LoadingDialog loading = new LoadingDialog(message);
-        FragmentManager fm = getSupportFragmentManager();
-        FragmentTransaction ft = fm.beginTransaction();
-        loading.show(ft, DIALOG_WAIT_TAG);
-
+        Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG);
+        if (frag == null) {
+            Log_OC.d(TAG, "show loading dialog");
+            LoadingDialog loading = new LoadingDialog(message);
+            FragmentManager fm = getSupportFragmentManager();
+            FragmentTransaction ft = fm.beginTransaction();
+            loading.show(ft, DIALOG_WAIT_TAG);
+            fm.executePendingTransactions();
+        }
     }
 
 
@@ -926,6 +930,7 @@ public class FileActivity extends AppCompatActivity
     public void dismissLoadingDialog() {
         Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG);
         if (frag != null) {
+            Log_OC.d(TAG, "dismiss loading dialog");
             LoadingDialog loading = (LoadingDialog) frag;
             loading.dismiss();
         }

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

@@ -58,7 +58,6 @@ 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.db.PreferenceManager;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
@@ -93,6 +92,9 @@ import com.owncloud.android.utils.PermissionUtil;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.List;
+
+import static com.owncloud.android.db.PreferenceManager.*;
 
 /**
  * Displays, what files the user has available in his ownCloud. This is the main view.
@@ -562,7 +564,7 @@ public class FileDisplayActivity extends HookActivity
                 break;
             }
             case R.id.action_sort: {
-                Integer sortOrder = PreferenceManager.getSortOrder(this);
+                Integer sortOrder = getSortOrder(this);
 
                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
                 builder.setTitle(R.string.actionbar_sort_title)
@@ -764,8 +766,12 @@ public class FileDisplayActivity extends HookActivity
      */
     private void requestMoveOperation(Intent data, int resultCode) {
         OCFile folderToMoveAt = (OCFile) data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        OCFile targetFile = (OCFile) data.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
-        getFileOperationsHelper().moveFile(folderToMoveAt, targetFile);
+
+        ArrayList<OCFile> files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
+
+        for (Parcelable file : files) {
+            getFileOperationsHelper().moveFile(folderToMoveAt, (OCFile) file);
+        }
     }
 
     /**
@@ -776,8 +782,12 @@ public class FileDisplayActivity extends HookActivity
      */
     private void requestCopyOperation(Intent data, int resultCode) {
         OCFile folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
-        OCFile targetFile = data.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
-        getFileOperationsHelper().copyFile(folderToMoveAt, targetFile);
+
+        ArrayList<OCFile> files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
+
+        for (Parcelable file : files) {
+            getFileOperationsHelper().copyFile(folderToMoveAt, (OCFile) file);
+        }
     }
 
     @Override
@@ -975,22 +985,28 @@ public class FileDisplayActivity extends HookActivity
                                         .equals(event));
 
                         if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
-                            equals(event) &&/// TODO refactor and make common
+                            equals(event)) {
 
-                            synchResult != null && !synchResult.isSuccess()) {
+                            if (synchResult != null && !synchResult.isSuccess()) {
+                                /// TODO refactor and make common
 
-                            if(ResultCode.UNAUTHORIZED.equals(synchResult.getCode()) ||
-                                (synchResult.isException() && synchResult.getException()
-                                    instanceof AuthenticatorException)) {
+                                if (ResultCode.UNAUTHORIZED.equals(synchResult.getCode()) ||
+                                    (synchResult.isException() && synchResult.getException()
+                                        instanceof AuthenticatorException)) {
 
-                                requestCredentialsUpdate(context);
+                                    requestCredentialsUpdate(context);
 
-                            } else if(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED.equals(
-                                synchResult.getCode())) {
+                                } else if (RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED.equals(
+                                    synchResult.getCode())) {
+
+                                    showUntrustedCertDialog(synchResult);
+                                }
 
-                                showUntrustedCertDialog(synchResult);
                             }
 
+                            if (synchFolderRemotePath.equals(OCFile.ROOT_PATH)) {
+                                setUsernameInDrawer(mDrawerLayout, getAccount());
+                            }
                         }
 
                     }
@@ -1734,6 +1750,11 @@ public class FileDisplayActivity extends HookActivity
     }
 
 
+    /**
+     * Request stopping the upload/download operation in progress over the given {@link OCFile} file.
+     *
+     * @param file {@link OCFile} file which operation are wanted to be cancel
+     */
     public void cancelTransference(OCFile file) {
         getFileOperationsHelper().cancelTransference(file);
         if (mWaitingToPreview != null &&
@@ -1747,6 +1768,17 @@ public class FileDisplayActivity extends HookActivity
         onTransferStateChanged(file, false, false);
     }
 
+    /**
+     * Request stopping all upload/download operations in progress over the given {@link OCFile} files.
+     *
+     * @param files list of {@link OCFile} files which operations are wanted to be cancel
+     */
+    public void cancelTransference(List<OCFile> files) {
+        for(OCFile file: files) {
+            cancelTransference(file);
+        }
+    }
+
     @Override
     public void onRefresh(boolean ignoreETag) {
         refreshList(ignoreETag);

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

@@ -55,13 +55,17 @@ import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 
-public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity, 
+import java.util.ArrayList;
+
+public class FolderPickerActivity extends FileActivity implements FileFragment.ContainerActivity,
     OnClickListener, OnEnforceableRefreshListener {
 
     public static final String EXTRA_FOLDER = UploadFilesActivity.class.getCanonicalName()
                                                             + ".EXTRA_FOLDER";
     public static final String EXTRA_FILE = UploadFilesActivity.class.getCanonicalName()
                                                             + ".EXTRA_FILE";
+    public static final String EXTRA_FILES = UploadFilesActivity.class.getCanonicalName()
+            + ".EXTRA_FILES";
     //TODO: Think something better
 
     private SyncBroadcastReceiver mSyncBroadcastReceiver;
@@ -370,12 +374,16 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         } else if (v == mChooseBtn) {
             Intent i = getIntent();
             Parcelable targetFile = i.getParcelableExtra(FolderPickerActivity.EXTRA_FILE);
+            ArrayList<Parcelable> targetFiles = i.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES);
 
             Intent data = new Intent();
             data.putExtra(EXTRA_FOLDER, getCurrentFolder());
             if (targetFile != null) {
                 data.putExtra(EXTRA_FILE, targetFile);
             }
+            if (targetFiles != null){
+                data.putParcelableArrayListExtra(EXTRA_FILES, targetFiles);
+            }
             setResult(RESULT_OK, data);
 
             finish();

+ 7 - 5
src/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -35,7 +35,6 @@ import android.content.IntentFilter;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.res.Resources.NotFoundException;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -59,6 +58,7 @@ import com.owncloud.android.authentication.AccountAuthenticator;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.lib.common.OwnCloudAccount;
 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;
@@ -315,7 +315,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
         Log_OC.d(TAG, "on item click");
         // TODO Enable when "On Device" is recovered ?
         Vector<OCFile> tmpfiles = getStorageManager().getFolderContent(mFile /*, false*/);
-        sortFileList(tmpfiles);
+        tmpfiles = sortFileList(tmpfiles);
 
         if (tmpfiles.size() <= 0) return;
         // filter on dirtype
@@ -404,7 +404,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
         if (mFile != null) {
             // TODO Enable when "On Device" is recovered ?
             Vector<OCFile> files = getStorageManager().getFolderContent(mFile/*, false*/);
-            sortFileList(files);
+            files = sortFileList(files);
 
             List<HashMap<String, Object>> data = new LinkedList<>();
             for (OCFile f : files) {
@@ -454,11 +454,13 @@ public class ReceiveExternalFilesActivity extends FileActivity
         synchFolderOp.execute(getAccount(), this, null, null);
     }
 
-    private void sortFileList(Vector<OCFile> files) {
+    private Vector<OCFile> sortFileList(Vector<OCFile> files) {
         // Read sorting order, default to sort by name ascending
         FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(this);
         FileStorageUtils.mSortAscending = PreferenceManager.getSortAscending(this);
-        FileStorageUtils.sortFolder(files);
+
+        files = FileStorageUtils.sortFolder(files);
+        return files;
     }
 
     private String generatePath(Stack<String> dirs) {

+ 155 - 56
src/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -24,15 +24,26 @@
 package com.owncloud.android.ui.adapter;
 
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Vector;
+
 import android.accounts.Account;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.os.Build;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.BaseAdapter;
+import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListAdapter;
@@ -52,6 +63,9 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimetypeIconUtil;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Vector;
 
 
@@ -61,6 +75,8 @@ import java.util.Vector;
  */
 public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
+    private static final String SELECTION_KEY = "multiFileSelectionsKey";
+
     private Context mContext;
     private OCFile mFile = null;
     private Vector<OCFile> mFiles = null;
@@ -71,21 +87,24 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
     private Account mAccount;
     private ComponentsGetter mTransferServiceGetter;
     private boolean mGridMode;
+    private boolean isGridViewSelectionRestored = true;
+
+    private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM};
+
+    private HashSet<Long> mSelection = new LinkedHashSet<Long>();
 
-    private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM };
-    
     public FileListListAdapter(
             boolean justFolders, 
             Context context,
             ComponentsGetter transferServiceGetter
-            ) {
-        
+    ) {
+
         mJustFolders = justFolders;
         mContext = context;
         mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
 
         mTransferServiceGetter = transferServiceGetter;
-        
+
         // Read sorting order, default to sort by name ascending
         FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(mContext);
         FileStorageUtils.mSortAscending = PreferenceManager.getSortAscending(mContext);
@@ -95,7 +114,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
         mGridMode = false;
     }
-    
+
     @Override
     public boolean areAllItemsEnabled() {
         return true;
@@ -132,6 +151,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
+        restoreGridViewSelection((AbsListView) parent);
 
         View view = convertView;
         OCFile file = null;
@@ -144,9 +164,9 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
         // Find out which layout should be displayed
         ViewType viewType;
-        if (!mGridMode){
+        if (!mGridMode) {
             viewType = ViewType.LIST_ITEM;
-        } else if (file.isImage()){
+        } else if (file.isImage()) {
             viewType = ViewType.GRID_IMAGE;
         } else {
             viewType = ViewType.GRID_ITEM;
@@ -172,7 +192,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
         view.invalidate();
 
-        if (file != null){
+        if (file != null) {
 
             ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
 
@@ -183,40 +203,22 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
             LinearLayout linearLayout = (LinearLayout) view.findViewById(R.id.ListItemLayout);
             linearLayout.setContentDescription("LinearLayout-" + name);
 
-            switch (viewType){
+            switch (viewType) {
                 case LIST_ITEM:
                     TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
                     TextView fileSizeSeparatorV = (TextView) view.findViewById(R.id.file_separator);
                     TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
-                    ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
+
 
                     lastModV.setVisibility(View.VISIBLE);
                     lastModV.setText(DisplayUtils.getRelativeTimestamp(mContext, file.getModificationTimestamp()));
 
-                    checkBoxV.setVisibility(View.GONE);
 
                     fileSizeSeparatorV.setVisibility(View.VISIBLE);
                     fileSizeV.setVisibility(View.VISIBLE);
                     fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
 
-                    if (!file.isFolder()) {
-                        AbsListView parentList = (AbsListView)parent;
-                        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-                            if (parentList.getChoiceMode() == AbsListView.CHOICE_MODE_NONE) {
-                                checkBoxV.setVisibility(View.GONE);
-                            } else {
-                                if (parentList.isItemChecked(position)) {
-                                    checkBoxV.setImageResource(
-                                            R.drawable.ic_checkbox_marked);
-                                } else {
-                                    checkBoxV.setImageResource(
-                                            R.drawable.ic_checkbox_blank_outline);
-                                }
-                                checkBoxV.setVisibility(View.VISIBLE);
-                            }
-                        }
-
-                    } else { //Folder
+                    if (file.isFolder()) {
                         fileSizeSeparatorV.setVisibility(View.GONE);
                         fileSizeV.setVisibility(View.GONE);
                     }
@@ -234,7 +236,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                         sharedIconV.setImageResource(R.drawable.shared_via_link);
                         sharedIconV.setVisibility(View.VISIBLE);
                         sharedIconV.bringToFront();
-                    } else if (file.isSharedWithSharee() || file.isSharedWithMe() ) {
+                    } else if (file.isSharedWithSharee() || file.isSharedWithMe()) {
                         sharedIconV.setImageResource(R.drawable.shared_via_users);
                         sharedIconV.setVisibility(View.VISIBLE);
                         sharedIconV.bringToFront();
@@ -258,22 +260,22 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                     localStateView.setVisibility(View.INVISIBLE);   // default first
 
                     if ( //synchronizing
-                                opsBinder != null &&
-                                opsBinder.isSynchronizing(mAccount, file.getRemotePath())
+                            opsBinder != null &&
+                                    opsBinder.isSynchronizing(mAccount, file)
                             ) {
                         localStateView.setImageResource(R.drawable.ic_synchronizing);
                         localStateView.setVisibility(View.VISIBLE);
 
                     } else if ( // downloading
-                                downloaderBinder != null &&
-                                downloaderBinder.isDownloading(mAccount, file)
+                            downloaderBinder != null &&
+                                    downloaderBinder.isDownloading(mAccount, file)
                             ) {
                         localStateView.setImageResource(R.drawable.ic_synchronizing);
                         localStateView.setVisibility(View.VISIBLE);
 
                     } else if ( //uploading
-                                uploaderBinder != null &&
-                                uploaderBinder.isUploading(mAccount, file)
+                            uploaderBinder != null &&
+                                    uploaderBinder.isUploading(mAccount, file)
                             ) {
                         localStateView.setImageResource(R.drawable.ic_synchronizing);
                         localStateView.setVisibility(View.VISIBLE);
@@ -289,9 +291,29 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 
                     break;
             }
-            
+
+            ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
+            checkBoxV.setVisibility(View.GONE);
+            view.setBackgroundColor(Color.WHITE);
+
+            AbsListView parentList = (AbsListView) parent;
+            if (parentList.getChoiceMode() != AbsListView.CHOICE_MODE_NONE
+                    && parentList.getCheckedItemCount() > 0) {
+                if (isItemSelected(position)) {
+                    view.setBackgroundColor(mContext.getResources().getColor(
+                            R.color.selected_item_background));
+                    checkBoxV.setImageResource(
+                            R.drawable.ic_checkbox_marked);
+                } else {
+                    view.setBackgroundColor(Color.WHITE);
+                    checkBoxV.setImageResource(
+                            R.drawable.ic_checkbox_blank_outline);
+                }
+                checkBoxV.setVisibility(View.VISIBLE);
+            }
+
             // For all Views
-            
+
             // this if-else is needed even though favorite icon is visible by default
             // because android reuses views in listview
             if (!file.isFavorite()) {
@@ -299,15 +321,15 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
             } else {
                 view.findViewById(R.id.favoriteIcon).setVisibility(View.VISIBLE);
             }
-            
+
             // No Folder
             if (!file.isFolder()) {
-                if (file.isImage() && file.getRemoteId() != null){
+                if (file.isImage() && file.getRemoteId() != null) {
                     // Thumbnail in Cache?
                     Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
                             String.valueOf(file.getRemoteId())
-                            );
-                    if (thumbnail != null && !file.needsUpdateThumbnail()){
+                    );
+                    if (thumbnail != null && !file.needsUpdateThumbnail()) {
                         fileIcon.setImageBitmap(thumbnail);
                     } else {
                         // generate new Thumbnail
@@ -315,15 +337,15 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                             final ThumbnailsCacheManager.ThumbnailGenerationTask task =
                                     new ThumbnailsCacheManager.ThumbnailGenerationTask(
                                             fileIcon, mStorageManager, mAccount
-                                            );
+                                    );
                             if (thumbnail == null) {
                                 thumbnail = ThumbnailsCacheManager.mDefaultImg;
                             }
                             final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
                                     new ThumbnailsCacheManager.AsyncDrawable(
-                                    mContext.getResources(), 
-                                    thumbnail, 
-                                    task
+                                            mContext.getResources(),
+                                            thumbnail,
+                                            task
                                     );
                             fileIcon.setImageDrawable(asyncDrawable);
                             task.execute(file);
@@ -352,10 +374,22 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                 );
             }
         }
-
         return view;
     }
 
+    private void restoreGridViewSelection(AbsListView parent) {
+        if (parent instanceof GridView && !isGridViewSelectionRestored) {
+            isGridViewSelectionRestored = true;
+            parent.clearChoices();
+            final Vector<OCFile> files = mFiles;
+            for (int i = 0; i < files.size(); ++i) {
+                if(mSelection.contains(files.get(i).getFileId())){
+                    parent.setItemChecked(i, true);
+                }
+            }
+        }
+    }
+
     @Override
     public int getViewTypeCount() {
         return 1;
@@ -390,7 +424,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
             mFiles = mStorageManager.getFolderContent(mFile/*, onlyOnDevice*/);
             mFilesOrig.clear();
             mFilesOrig.addAll(mFiles);
-            
+
             if (mJustFolders) {
                 mFiles = getFolders(mFiles);
             }
@@ -401,17 +435,17 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         mFiles = FileStorageUtils.sortFolder(mFiles);
         notifyDataSetChanged();
     }
-    
 
     /**
      * Filter for getting only the folders
+     *
      * @param files
      * @return Vector<OCFile>
      */
     public Vector<OCFile> getFolders(Vector<OCFile> files) {
-        Vector<OCFile> ret = new Vector<OCFile>(); 
-        OCFile current = null; 
-        for (int i=0; i<files.size(); i++) {
+        Vector<OCFile> ret = new Vector<OCFile>();
+        OCFile current = null;
+        for (int i = 0; i < files.size(); i++) {
             current = files.get(i);
             if (current.isFolder()) {
                 ret.add(current);
@@ -419,9 +453,10 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         }
         return ret;
     }
-    
-    
+
+
     public void setSortOrder(Integer order, boolean ascending) {
+
         PreferenceManager.setSortOrder(mContext, order);
         PreferenceManager.setSortAscending(mContext, ascending);
         
@@ -432,10 +467,74 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         notifyDataSetChanged();
     }
 
+
+    private CharSequence showRelativeTimestamp(OCFile file) {
+        return DisplayUtils.getRelativeDateTimeString(mContext, file.getModificationTimestamp(),
+                DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);
+    }
+
     public void setGridMode(boolean gridMode) {
         mGridMode = gridMode;
     }
 
+    public boolean isItemSelected(int position) {
+        return mSelection.contains(getItemId(position));
+    }
+
+    public void setNewSelection(int position, boolean checked) {
+        if (checked) {
+            mSelection.add(getItemId(position));
+            notifyDataSetChanged();
+        } else {
+            removeSelection(position);
+        }
+    }
+
+    public void removeSelection(int position) {
+        mSelection.remove(getItemId(position));
+        notifyDataSetChanged();
+    }
+
+    public void clearSelection() {
+        mSelection.clear();
+        notifyDataSetChanged();
+    }
+
+    public ArrayList<OCFile> getCheckedItems() {
+        ArrayList<OCFile> files = new ArrayList<OCFile>();
+        if (mFiles != null && mFiles.size() != 0) {
+            for (OCFile file : mFiles) {
+                if (mSelection.contains(file.getFileId())) {
+                    files.add(file);
+                }
+            }
+        }
+        return files;
+    }
+
+    public void restoreSelectionState(Bundle savedInstanceState) {
+        if (savedInstanceState == null) {
+            return;
+        }
+        long[] selectionState = savedInstanceState.getLongArray(SELECTION_KEY);
+        mSelection.clear();
+        if (selectionState != null) {
+            for (long id : selectionState) {
+                mSelection.add(id);
+            }
+        }
+        isGridViewSelectionRestored = false;
+    }
+
+    public void saveSelectionState(Bundle outState) {
+        long[] selectionStatePrimitive = new long[mSelection.size()];
+        int i = 0;
+        for (Long id : mSelection) {
+            selectionStatePrimitive[i++] = id;
+        }
+        outState.putLongArray(SELECTION_KEY, selectionStatePrimitive);
+    }
+
     public boolean isGridMode() {
         return mGridMode;
     }

+ 5 - 6
src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java

@@ -52,14 +52,13 @@ implements ConfirmationDialogFragmentListener {
         RemoveFileDialogFragment frag = new RemoveFileDialogFragment();
         Bundle args = new Bundle();
         
-        int messageStringId = R.string.confirmation_remove_alert;
+        int messageStringId = (file.isFolder()) ?
+            R.string.confirmation_remove_folder_alert :
+            R.string.confirmation_remove_file_alert;
         
         int localRemoveButton = (!file.isFavorite() && (file.isFolder() || file.isDown())) ?
-            R.string.confirmation_remove_local : -1;
-
-        if (file.isFolder()) {
-            messageStringId = R.string.confirmation_remove_folder_alert;
-        }
+            R.string.confirmation_remove_local :
+            -1;
 
         args.putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId);
         args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[]{file.getFileName()});

+ 8 - 11
src/com/owncloud/android/ui/fragment/ExtendedListFragment.java

@@ -165,11 +165,14 @@ public class ExtendedListFragment extends Fragment
 
         mListView = (ExtendedListView)(v.findViewById(R.id.list_root));
         mListView.setOnItemClickListener(this);
+        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
         mListFooterView = inflater.inflate(R.layout.list_footer, null, false);
 
         mGridView = (GridViewWithHeaderAndFooter) (v.findViewById(R.id.grid_root));
         mGridView.setNumColumns(GridView.AUTO_FIT);
         mGridView.setOnItemClickListener(this);
+        mGridView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+
         mGridFooterView = inflater.inflate(R.layout.list_footer, null, false);
 
         if (savedInstanceState != null) {
@@ -404,19 +407,13 @@ public class ExtendedListFragment extends Fragment
     }
 
     protected void setChoiceMode(int choiceMode) {
-        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-            mListView.setChoiceMode(choiceMode);
-            mGridView.setChoiceMode(choiceMode);
-        } else {
-            ((ListView)mListView).setChoiceMode(choiceMode);
-        }
+        mListView.setChoiceMode(choiceMode);
+        mGridView.setChoiceMode(choiceMode);
     }
 
-    protected void registerForContextMenu() {
-        registerForContextMenu(mListView);
-        registerForContextMenu(mGridView);
-        mListView.setOnCreateContextMenuListener(this);
-        mGridView.setOnCreateContextMenuListener(this);
+    protected void setMultiChoiceModeListener(AbsListView.MultiChoiceModeListener listener) {
+        mListView.setMultiChoiceModeListener(listener);
+        mGridView.setMultiChoiceModeListener(listener);
     }
 
     /**

+ 199 - 154
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -30,16 +30,17 @@ import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.support.v4.widget.SwipeRefreshLayout;
-import android.view.ContextMenu;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.PopupMenu;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -60,6 +61,7 @@ import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.dialog.FileActionsDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
+import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.ui.preview.PreviewMediaFragment;
@@ -67,14 +69,15 @@ import com.owncloud.android.ui.preview.PreviewTextFragment;
 import com.owncloud.android.utils.FileStorageUtils;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A Fragment that lists all files and folders in a given path.
  *
  * TODO refactor to get rid of direct dependency on FileDisplayActivity
  */
-public class OCFileListFragment extends ExtendedListFragment
-        implements FileActionsDialogFragment.FileActionsDialogFragmentListener {
+public class OCFileListFragment extends ExtendedListFragment {
 
     private static final String TAG = OCFileListFragment.class.getSimpleName();
 
@@ -98,14 +101,19 @@ public class OCFileListFragment extends ExtendedListFragment
     private FileListListAdapter mAdapter;
     private boolean mJustFolders;
 
-    private OCFile mTargetFile;
+    private int mStatusBarColorActionMode;
+    private int mStatusBarColor;
 
+    private boolean hideFab = true;
     private boolean miniFabClicked = false;
-   
+    private ActionMode mActiveActionMode;
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setHasOptionsMenu(true);
+        mStatusBarColorActionMode = getResources().getColor(R.color.action_mode_status_bar_background);
+        mStatusBarColor = getResources().getColor(R.color.primary_dark);
     }
 
     /**
@@ -176,11 +184,12 @@ public class OCFileListFragment extends ExtendedListFragment
                 getActivity(),
                 mContainerActivity
         );
+        mAdapter.restoreSelectionState(savedInstanceState);
         setListAdapter(mAdapter);
 
         registerLongClickListener();
 
-        boolean hideFab = (args != null) && args.getBoolean(ARG_HIDE_FAB, false);
+        hideFab = (args != null) && args.getBoolean(ARG_HIDE_FAB, false);
         if (hideFab) {
             setFabEnabled(false);
         } else {
@@ -200,7 +209,7 @@ public class OCFileListFragment extends ExtendedListFragment
                 removeFabLabels();
             }
         }
-  }
+    }
 
     /**
      * adds labels to all mini FABs.
@@ -228,7 +237,7 @@ public class OCFileListFragment extends ExtendedListFragment
         getFabUpload().setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                UploadFilesActivity.startUploadActivityForResult(getActivity(), ((FileActivity)getActivity())
+                UploadFilesActivity.startUploadActivityForResult(getActivity(), ((FileActivity) getActivity())
                         .getAccount(), FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM);
                 getFabMain().collapse();
                 recordMiniFabClick();
@@ -335,57 +344,76 @@ public class OCFileListFragment extends ExtendedListFragment
     }
 
     private void registerLongClickListener() {
-        getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
-            public boolean onItemLongClick(AdapterView<?> arg0, View v,
-                                           int index, long arg3) {
-                showFileAction(index);
-                return true;
+        setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
+
+            @Override
+            public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+                mAdapter.setNewSelection(position, checked);
+                mode.invalidate();
             }
-        });
-    }
 
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                mActiveActionMode = mode;
 
-    private void showFileAction(int fileIndex) {
-        Bundle args = getArguments();
-        PopupMenu pm = new PopupMenu(getActivity(),null);
-        Menu menu = pm.getMenu();
+                createContextActionBar(menu);
+                mode.invalidate();
 
-        boolean allowContextualActions =
-                (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
+                //set gray color
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    getActivity().getWindow().setStatusBarColor(mStatusBarColorActionMode);
+                }
 
-        if (allowContextualActions) {
-            MenuInflater inflater = getActivity().getMenuInflater();
+                // hide FAB in multi selection mode
+                setFabEnabled(false);
 
-            inflater.inflate(R.menu.file_actions_menu, menu);
-            OCFile targetFile = (OCFile) mAdapter.getItem(fileIndex);
-
-            if (mContainerActivity.getStorageManager() != null) {
-                FileMenuFilter mf = new FileMenuFilter(
-                        targetFile,
-                        mContainerActivity.getStorageManager().getAccount(),
-                        mContainerActivity,
-                        getActivity()
-                );
-                mf.filter(menu);
+                return true;
             }
 
-            /// TODO break this direct dependency on FileDisplayActivity... if possible
-            MenuItem item = menu.findItem(R.id.action_open_file_with);
-            FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
-            if (frag != null && frag instanceof FileDetailFragment &&
-                    frag.getFile().getFileId() == targetFile.getFileId()) {
-                item = menu.findItem(R.id.action_see_details);
-                if (item != null) {
-                    item.setVisible(false);
-                    item.setEnabled(false);
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+
+                final int checkedCount = getListView().getCheckedItemCount();
+                mode.setTitle(checkedCount + " selected");
+
+                if (checkedCount > 0) {
+                    List<OCFile> targetFiles = mAdapter.getCheckedItems();
+
+                    if (mContainerActivity.getStorageManager() != null) {
+                        FileMenuFilter mf = new FileMenuFilter(
+                            targetFiles,
+                            mContainerActivity.getStorageManager().getAccount(),
+                            mContainerActivity,
+                            getActivity()
+                        );
+                        mf.filter(menu);
+                    }
                 }
+                return true;
             }
 
-            FileActionsDialogFragment dialog = FileActionsDialogFragment.newInstance(menu,
-                    fileIndex, targetFile.getFileName());
-            dialog.setTargetFragment(this, 0);
-            dialog.show(getFragmentManager(), FileActionsDialogFragment.FTAG_FILE_ACTIONS);
-        }
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                return onFileActionChosen(item.getItemId());
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+                mActiveActionMode = null;
+                getListView().clearChoices();
+                mAdapter.clearSelection();
+
+                // reset to primary dark color
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    getActivity().getWindow().setStatusBarColor(mStatusBarColor);
+                }
+
+                // show FAB on multi selection mode exit
+                if(!hideFab) {
+                    setFabEnabled(true);
+                }
+            }
+        });
     }
 
     /**
@@ -395,6 +423,7 @@ public class OCFileListFragment extends ExtendedListFragment
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putParcelable(KEY_FILE, mFile);
+        mAdapter.saveSelectionState(outState);
     }
 
     @Override
@@ -490,123 +519,135 @@ public class OCFileListFragment extends ExtendedListFragment
     }
 
     /**
-     * {@inheritDoc}
+     * Create the context bar with the file actions
+     * @param menu
      */
-    @Override
-    public void onCreateContextMenu(
-            ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+    public void createContextActionBar(Menu menu) {
         Bundle args = getArguments();
         boolean allowContextualActions =
                 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
         if (allowContextualActions) {
             MenuInflater inflater = getActivity().getMenuInflater();
             inflater.inflate(R.menu.file_actions_menu, menu);
-            AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
-            OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
-
-            if (mContainerActivity.getStorageManager() != null) {
-                FileMenuFilter mf = new FileMenuFilter(
-                    targetFile,
-                    mContainerActivity.getStorageManager().getAccount(),
-                    mContainerActivity,
-                    getActivity()
-                );
-                mf.filter(menu);
-            }
-
-            /// TODO break this direct dependency on FileDisplayActivity... if possible
-            MenuItem item = menu.findItem(R.id.action_open_file_with);
-            FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
-            if (frag != null && frag instanceof FileDetailFragment &&
-                    frag.getFile().getFileId() == targetFile.getFileId()) {
-                item = menu.findItem(R.id.action_see_details);
-                if (item != null) {
-                    item.setVisible(false);
-                    item.setEnabled(false);
-                }
-            }
-
-//            String.format(mContext.getString(R.string.subject_token),
-//                    getClient().getCredentials().getUsername(), file.getFileName()));
-
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean onFileActionChosen(int menuId, int filePosition) {
-        mTargetFile = (OCFile) mAdapter.getItem(filePosition);
-        switch (menuId) {
-            case R.id.action_share_file: {
-                mContainerActivity.getFileOperationsHelper().showShareFile(mTargetFile);
-                return true;
-            }
-            case R.id.action_open_file_with: {
-                mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
-                return true;
-            }
-            case R.id.action_rename_file: {
-                RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
-                dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
-                return true;
-            }
-            case R.id.action_remove_file: {
-                RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
-                dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
-                return true;
-            }
-            case R.id.action_download_file:
-            case R.id.action_sync_file: {
-                mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
-                return true;
-            }
-            case R.id.action_cancel_sync: {
-                ((FileDisplayActivity)mContainerActivity).cancelTransference(mTargetFile);
-                return true;
-            }
-            case R.id.action_see_details: {
-                mContainerActivity.showDetails(mTargetFile);
-                return true;
-            }
-            case R.id.action_send_file: {
-                // Obtain the file
-                if (!mTargetFile.isDown()) {  // Download the file
-                    Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
-                    ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile);
+    public boolean onFileActionChosen(int menuId) {
+        final ArrayList<OCFile> checkedItems = mAdapter.getCheckedItems();
+        if (checkedItems.size() == 1){
+            OCFile mTargetFile = checkedItems.get(0);
 
-                } else {
-                    mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
+            switch (menuId) {
+                case R.id.action_share_file: {
+                    mContainerActivity.getFileOperationsHelper().showShareFile(mTargetFile);
+                    return true;
                 }
-                return true;
-            }
-            case R.id.action_move: {
-                Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+                case R.id.action_open_file_with: {
+                    mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
+                    return true;
+                }
+                case R.id.action_rename_file: {
+                    RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
+                    dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
+                    return true;
+                }
+                case R.id.action_remove_file: {
+                    RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
+                    dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
+                    return true;
+                }
+                case R.id.action_download_file:
+                case R.id.action_sync_file: {
+                    mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
+                    return true;
+                }
+                case R.id.action_cancel_sync: {
+                    ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFile);
+                    return true;
+                }
+                case R.id.action_see_details: {
+                    mContainerActivity.showDetails(mTargetFile);
+                    return true;
+                }
+                case R.id.action_send_file: {
+                    // Obtain the file
+                    if (!mTargetFile.isDown()) {  // Download the file
+                        Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
+                        ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile);
 
-                // Pass mTargetFile that contains info of selected file/folder
-                action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
-                getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
-                return true;
-            }
-            case R.id.action_favorite_file: {
-                mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true);
-                return true;
-            }
-            case R.id.action_unfavorite_file: {
-                mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false);
-                return true;
+                    } else {
+                        mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
+                    }
+                    return true;
+                }
+                case R.id.action_move: {
+                    Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+                    ArrayList files = new ArrayList();
+                    files.add(mTargetFile);
+                    action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, files);
+                    getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
+                    return true;
+                }
+                case R.id.action_favorite_file: {
+                    mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true);
+                    return true;
+                }
+                case R.id.action_unfavorite_file: {
+                    mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false);
+                    return true;
+                }
+                case R.id.action_copy:
+                    Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+                    ArrayList files = new ArrayList();
+                    files.add(mTargetFile);
+                    action.putExtra(FolderPickerActivity.EXTRA_FILES, files);
+                    getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
+                    return true;
+                default:
+                    return false;
             }
-            case R.id.action_copy:
-                Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+        } else {
+            ArrayList<OCFile> mTargetFiles = checkedItems;
 
-                // Pass mTargetFile that contains info of selected file/folder
-                action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
-                getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
-                return true;
-            default:
-                return false;
+            switch (menuId) {
+                case R.id.action_remove_file: {
+                    RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(mTargetFiles);
+                    dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
+                    return true;
+                }
+                case R.id.action_download_file:
+                case R.id.action_sync_file: {
+                    mContainerActivity.getFileOperationsHelper().syncFiles(mTargetFiles);
+                    return true;
+                }
+                case R.id.action_cancel_sync: {
+                    ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFiles);
+                    return true;
+                }
+                case R.id.action_move: {
+                    Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+                    action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, mTargetFiles);
+                    getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES);
+                    return true;
+                }
+                case R.id.action_favorite_file: {
+                    mContainerActivity.getFileOperationsHelper().toggleFavorites(mTargetFiles, true);
+                    return true;
+                }
+                case R.id.action_unfavorite_file: {
+                    mContainerActivity.getFileOperationsHelper().toggleFavorites(mTargetFiles, false);
+                    return true;
+                }
+                case R.id.action_copy:
+                    Intent action = new Intent(getActivity(), FolderPickerActivity.class);
+                    action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, mTargetFiles);
+                    getActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES);
+                    return true;
+                default:
+                    return false;
+            }
         }
+
     }
 
     /**
@@ -615,8 +656,7 @@ public class OCFileListFragment extends ExtendedListFragment
     @Override
     public boolean onContextItemSelected (MenuItem item) {
         AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
-        boolean matched = onFileActionChosen(item.getItemId(),
-                ((AdapterContextMenuInfo) item.getMenuInfo()).position);
+        boolean matched = onFileActionChosen(item.getItemId());
         if(!matched) {
             return super.onContextItemSelected(item);
         } else {
@@ -624,7 +664,6 @@ public class OCFileListFragment extends ExtendedListFragment
         }
     }
 
-
     /**
      * Use this to query the {@link OCFile} that is currently
      * being displayed by this fragment
@@ -717,11 +756,17 @@ public class OCFileListFragment extends ExtendedListFragment
             if (version != null && version.supportsRemoteThumbnails() &&
                     isGridViewPreferred(mFile)) {
                 switchToGridView();
-                registerLongClickListener();
             } else {
                 switchToListView();
             }
         }
+        invalidateActionMode();
+    }
+
+    private void invalidateActionMode() {
+        if(mActiveActionMode != null){
+            mActiveActionMode.invalidate();
+        }
     }
 
     private String generateFooterText(int filesCount, int foldersCount) {

+ 1 - 1
user_manual/ocdoc

@@ -1 +1 @@
-Subproject commit a4dcad98f92eeb4caef89b4e860d4359385f0697
+Subproject commit 343496c792616459e8204b6614fd42a1b16a6d68